====>>> 文章汇总(有代码汇总) <<<====
STM32F103 普中-准端-Z100,主控 STM32F103ZET6.
IIC(Inter Integrated Circuit)
数据线SDA
和 时钟SCL
)三种类型信号(看看就行,这里用的硬件IIC,也不用自己写)
起始信号是必需的,结束信号和应答信号,都可以不要。
配置时钟源
配置debug模式(如果需要ST-Link下载及调试可以勾选)
配置时钟树(可以直接在HCLK那里输入72,然后敲回车会自动配置)
EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。
24C02后面的 02 表示的是可存储 2Kbit 的数据,转换为字节的存储量为2*1024/8 = 256byte;256个字节一共分为32页,每页8个字节。
在AT24C02的参考手册第9页 可以看到下图
AT24C设备地址为如下:
结论:
片内地址寻址:
芯片寻址可对 内部256个字节 中的任一个进行 读/写操作,其寻址范围为00~FF,共256个寻址单位。
写一个字节
从时序图上可以看出(上面是MCU的信号,下面是存储芯片的信号),写一个字节数据的操作顺序为:
读一个字节
从时序图上可以看出(上面是MCU的信号,下面是存储芯片的信号),读一个字节数据的操作顺序为:
写一页数据
时序图和写单个字节差不多,只是每个字节写完之后存储器都会给MCU发送应答信号,之后继续发送下一个字节,写完之后,MCU发送停止信号即可。
256个字节一共分为32页,每页8个字节。AT24C02页写入只支持8个byte,所以需要分32次写入。如果按照上述时序连续写入8个字节后,会重复的继续往该页写数据。(当然也可以 往256个地址中分别写入一个字节。。。)
连续读数据
时序图和都单个字节差不多,存储器给MCU发送完每个字节,MCU要发送应答信号给存储器,直到MCU发送停止信号。且读数据没有8个字节的限制。
虽然时序看起来很复杂,但是不用担心,很多都已经有实现了。
在生成的工程中,打开stm32f1xx_hal.h
,可以看到已经生成了轮询,中断和DMA三种控制方式的代码。
我们只看轮询的,其他的也都差不多,只是应用场景不一样。
// 作为主机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 作为主机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 作为从机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 作为从机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 直接发送两个字节数据(就用于我们现在的情况,发送命令 + 发送数据)
// 参数:iic接口、设备地址、发送的数据1、发送的数据2、数据长度、超时时间
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 直接发送两个字节数据,并接受数据(就用于我们现在的情况,发送命令 + 接收数据)
// 参数:iic接口、设备地址、发送的数据1、发送的数据2、存储读取到的数据、数据长度、超时时间
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
// 查询设备是否就绪
HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);
我们可以直接看HAL_I2C_Mem_Write
、HAL_I2C_Mem_Read
,刚好可以满足我们这里需要发送 指令 + 地址
的情况,其在发送或者读取数据的过程中,地址还可以自己增加,很方便。
在i2c.h
中声明如下代码
/* USER CODE BEGIN Prototypes */
#define AT24C02_ADDR_WRITE 0xA0 // 写命令
#define AT24C02_ADDR_READ 0xA1 // 读命令
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat);
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf);
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size);
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size);
/* USER CODE END Prototypes */
在i2c.c
中添加如下代码
/* USER CODE BEGIN 1 */
#include
/**
* @brief AT24C02任意地址写一个字节数据
* @param addr —— 写数据的地址(0-255)
* @param dat —— 存放写入数据的地址
* @retval 成功 —— HAL_OK
*/
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat)
{
HAL_StatusTypeDef result;
result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, 1, 0xFFFF);
HAL_Delay(5); // 写一个字节,延迟一段时间,不能连续写
return result;
}
/**
* @brief AT24C02任意地址读一个字节数据
* @param addr —— 读数据的地址(0-255)
* @param read_buf —— 存放读取数据的地址
* @retval 成功 —— HAL_OK
*/
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf)
{
return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, read_buf, 1, 0xFFFF);
}
/**
* @brief AT24C02任意地址连续写多个字节数据
* @param addr —— 写数据的地址(0-255)
* @param dat —— 存放写入数据的地址
* @retval 成功 —— HAL_OK
*/
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size)
{
uint8_t i = 0;
uint16_t cnt = 0; // 写入字节计数
HAL_StatusTypeDef result; // 返回是否写入成功
/* 对于起始地址,有两种情况,分别判断 */
if(0 == addr % 8)
{
/* 起始地址刚好是页开始地址 */
/* 对于写入的字节数,有两种情况,分别判断 */
if(size <= 8)
{
// 写入的字节数不大于一页,直接写入
result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
HAL_Delay(20); // 写完八个字节(最多八个字节),延迟久一点
return result;
}
else
{
// 写入的字节数大于一页,先将整页循环写入
for(i = 0; i < size/8; i++)
{
HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
// 一次写入了八个字节,延迟久一点
HAL_Delay(20); // 写完八个字节,延迟久一点
addr += 8;
cnt += 8;
}
// 将剩余的字节写入
result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
HAL_Delay(20); // 写完八个字节(最多八个字节),延迟久一点
return result;
}
}
else
{
/* 起始地址偏离页开始地址 */
/* 对于写入的字节数,有两种情况,分别判断 */
if(size <= (8 - addr%8))
{
/* 在该页可以写完 */
result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
HAL_Delay(20); // 写完八个字节(最多八个字节),延迟久一点
return result;
}
else
{
/* 该页写不完 */
// 先将该页写完
cnt += 8 - addr%8;
HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, cnt, 0xFFFF);
HAL_Delay(20); // 写完八个字节(最多八个字节),延迟久一点
addr += cnt;
// 循环写整页数据
for(i = 0;i < (size - cnt)/8; i++)
{
HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
HAL_Delay(20); // 写完八个字节,延迟久一点
addr += 8;
cnt += 8;
}
// 将剩下的字节写入
result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
HAL_Delay(20); // 写完八个字节(最多八个字节),延迟久一点
return result;
}
}
}
/**
* @brief AT24C02任意地址连续读多个字节数据
* @param addr —— 读数据的地址(0-255)
* @param dat —— 存放读出数据的地址
* @retval 成功 —— HAL_OK
*/
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size)
{
return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, recv_buf, size, 0xFFFF);
}
/* USER CODE END 1 */
在main.c
中添加如下代码
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
// 单个字节 读写测试
uint8_t simple_write_dat = 0xa5; // 一个字节
uint8_t simple_recv_buf = 0;
if(HAL_OK == At24c02_Write_Byte(10, &simple_write_dat)){
printf("Simple data write success \r\n");
} else {
printf("Simple data write fail \r\n");
}
HAL_Delay(50); // 写一次和读一次之间需要短暂的延时
if(HAL_OK == At24c02_Read_Byte(10, &simple_recv_buf)){
printf("Simple data read success, recv_buf = 0x%02X \r\n", simple_recv_buf);
} else {
printf("Simple data read fail \r\n");
}
printf("--------------------- \r\n");
// 单个字节读写 测试结束
// 浮点数 读写测试
union float_union{
float float_write_dat; // 浮点数占4个字节
double double_write_dat; // 双精度浮点数占8个字节
uint8_t buf[8]; // 定义 8个字节 的空间
};
union float_union send_float_data; // 用来发送
union float_union rev_float_data; // 用来接收
// 先测试第一个 浮点数
send_float_data.float_write_dat = 3.1415f;
if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 4)){
printf("Float data write success \r\n");
} else {
printf("Float data write fail \r\n");
}
HAL_Delay(50);
if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 4)){
// 默认输出六位小数
printf("Float data read success, recv_buf = %f \r\n", rev_float_data.float_write_dat);
} else {
printf("Float data read fail \r\n");
}
// 测试第二个 双精度浮点数
send_float_data.double_write_dat = 3.1415f;
if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 8)){
printf("Double data write success \r\n");
} else {
printf("Double data write fail \r\n");
}
HAL_Delay(50);
if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 8)){
// 最多15位小数
printf("Double data read success, recv_buf = %.15f \r\n", rev_float_data.double_write_dat);
} else {
printf("Double data read fail \r\n");
}
printf("--------------------- \r\n");
// 浮点数读写测试 测试结束
// 连续数据读写测试
uint8_t write_dat[22] = {0}; // 22个字节
uint8_t recv_buf[22] = {0};
printf("正在往数组中填充数据... \r\n");
for(int i = 0; i < 22; i++){
write_dat[i] = i;
printf("%02X ", write_dat[i]);
}
printf("\r\n 数组中数据填充完毕... \r\n");
if(HAL_OK == At24c02_Write_Amount_Byte(0, write_dat, 22)){
printf("24c02 write success \r\n");
} else {
printf("24c02 write fail \r\n");
}
HAL_Delay(50); // 写一次和读一次之间需要短暂的延时
if(HAL_OK == At24c02_Read_Amount_Byte(0, recv_buf, 22)){
printf("read success \r\n");
for(int i = 0; i < 22; i++) {
printf("0x%02X ", recv_buf[i]);
}
} else {
printf("read fail\r\n");
}
// 连续数据读写 测试结束
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
因为每次发送或者接收只能按照一个字节的单位进行,因此对于 uint8_t 类型的整数没什么问题。但是对于浮点数等,占用多个字节的,就可以通过共用体的方式进行。
此方法在许多应用场景中都有应用,比如串口发送浮点数,也可以用这样的方式进行。
四线OLED采用IIC通信。该模块集成了 SSD1306存储芯片
。
从模块数据手册可以看到下图。GDDRAM是一个位映射静态RAM,保存要显示的位模式。RAM的大小为128 x 64位,RAM分为八页,从第0页到第7页,用于单色128x64点阵显示器。
存储大小刚好对应了我们屏幕的分辨大小(128*64)。简单来说,就是只要在对应的存储位上存储有效电平,对应的像素点就可以亮起来。
具体的显示方式和上个章节的差不多,这里直接放代码,抄来用即可。
因为这里的代码比较多,因此我们另外建个文件夹放我们的代码。
icode
文件夹,用来存放我们自己的代码。icode
文件夹下,创建OLED
文件夹,存放oled相关代码。OLED
文件夹下, 创建如下几个文件:oled.c
、oled.h
、oledfont.h
、bmp.h
。oled.c:源文件
oled.h:头文件
oledfont.h:字库文件
bmp.h:图片库文件
代码如下(我总觉得我手上的东西有问题,等有个靠谱的再写把)
oled.h
oled.c
oledfont.h
bmp.h