STM32CubeMX HAL库:I2C详解——读取和写入EEPROM
在之前的标准库中,STM32的硬件IIC非常复杂,更重要的是它并不稳定,所以都不推荐使用。但是在我们的HAL库中,对硬件IIC做了全新的优化,使得之前软件IIC几百行代码,在HAL库中,只需要寥寥几行就可以完成 那么这篇文章将带你去感受下它的优异之处。
本文将详细地讲解I2C协议,并基于I2C
来读写EEPROM模块以达到练习的目的
通过本篇博客您将学到:
I2C简介
IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信。
I2C特性
IIC一共有只有两个总线: 一条是双向的数据线SDA,一条是串行时钟线SCL
所有接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线的SCL上。I2C总线上的每一个设备都对应一个唯一的地址。
I2C起始信号与终止信号(代码段为软件I2C)
1、起始信号:SCL为高电平时,SDA由高->低
void I2C_Start()
{
SDA=1; //确保SDA为高电平
HAL_Delay(1);
SCL=1; //确保SCL为高电平
HAL_Delay(1);
SDA=0; //SCL为高时拉低SDA线
HAL_Delay(1);
SCL=0; //钳住I2C总线,准备数据的发送与接收
}
2、终止信号:SCL为高电平时,SDA由低->高
void I2C_Stop()
{
SCL=0;
SDA=0;
HAL_Delay(1);
SCL=1; //确保SCL为高电平
HAL_Delay(1);
SDA=1; //SCL为高时拉高SDA
}
I2C应答信号与非应答信号 (代码段为软件I2C)
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
1、应答信号‘0’:SCL高时SDA低
void I2C_Ack() //产生应答信号
{
SCL=0;
SDA=0;
HAL_Delay(1);
SCL=1; //确保SCL为高时SDA为低
HAL_Delay(1);
SCL=0;
}
2、非应答信号‘1’:SCL高时SDA高
void I2C_NAck() //不产生应答信号
{
SCL=0;
SDA=1;
HAL_Delay(1);
SCL=1; //SCL高时SDA高
HAL_Delay(1);
SCL=0;
}
I2C数据有效性
I2C信号在进行数据传输时, 当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。
SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输
接着我们介绍一下基于AT24C256的I2C通信
AT24C256芯片手册如下:
描述:
AT24C256是提供131072/262144位串行电可擦除可编程只读存储器(EEPROM),该设备最多允许4个设备共享一条公共的双线总线。
特征:
芯片的寻址:
AT24C设备地址为如下,前四位固定为1010,A2~A0为由管脚电平。AT24CXX EEPROM Board模块中默认为接地。所以A2~A0默认为000,最后一位表示读写操作。所以AT24Cxx的读地址为0xA1,写地址为0xA0。
也就是说如果是\写24C02的时候,从器件地址为10100000(0xA0);读24C02的时候,从器件地址为10100001(0xA1)。
注意:
- 在写数据的过程中,每成功写入一个字节,E2PROM存储空间的地址就会自动加1,当加到0xFF后,再写一个字节,地址就会溢出又变成0x00。
- 当我们在写多个字节时,写入一个字节之后,再写入下一个字节之前,必须延时5ms才可以
STM32CubeMX创建I2C例程(作者使用的是STM32f4ccu6)
1、设置RCC时钟
2、配置I2C
3、配置串口
这里配置串口是为了读EEPROM并打印出来。
4、时钟树配置
5、项目文件配置
6、 生成代码
7、配置下载工具
8、重写printf函数
重写printf函数能更方便与上位机通信 ,方法参考printf重写
I2C函数库(HAL)
在I2C.c文件中可以看到I2C初始化函数。在stm32f1xx_hal_i2c.h头文件中可以看到I2C的操作函数。分别对应轮询,中断和DMA三种控制方式
这里我们简单介绍一下等下用到的函数
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写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址

读写AT24C256
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define AT24C256_Write 0xA0
#define AT24C256_Read 0xA1
#define BufferSize 64
/* USER CODE END PD */
宏定义,增加了程序的可读性
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t w[BufferSize],r[BufferSize];
uint16_t i,j;
/* USER CODE END PV */
定义接收与发送数组
/* USER CODE BEGIN 2 */
for(i=0;i<64;i++)
{
w[i]=i;
}
printf("I2C Example Test\n");
//for( j=0;j<16;j++)
//{
if(HAL_I2C_Mem_Write(&hi2c1,AT24C256_Write,0,I2C_MEMADD_SIZE_16BIT,w,64,10000)==HAL_OK)
{
printf("EEPROM AT24C256 Write Test OK\n");
HAL_Delay(100);
}
else
{
printf("EEPROM AT24C256 Write Test False");
HAL_Delay(50);
}
//}
HAL_Delay(100);
HAL_I2C_Mem_Read(&hi2c1,AT24C256_Read,0,I2C_MEMADD_SIZE_16BIT,r,BufferSize,10000);
for(i=0;i<64;i++)
{
HAL_Delay(10);
printf("0x%02X ",r[i]);
}
/* USER CODE END 2 */
64字节一次性写入
或
/* USER CODE BEGIN 2 */
for(i=0;i<64;i++)
{
w[i]=i;
}
printf("I2C Example Test\n");
for( j=0;j<16;j++)
{
if(HAL_I2C_Mem_Write(&hi2c1,AT24C256_Write,0+16*j,I2C_MEMADD_SIZE_16BIT,w+16*j,16,10000)==HAL_OK)
{
printf("EEPROM AT24C256 Write Test OK\n");
HAL_Delay(100);
}
else
{
printf("EEPROM AT24C256 Write Test False");
HAL_Delay(50);
}
}
HAL_Delay(100);
HAL_I2C_Mem_Read(&hi2c1,AT24C256_Read,0,I2C_MEMADD_SIZE_16BIT,r,BufferSize,10000);
for(i=0;i<64;i++)
{
HAL_Delay(10);
printf("0x%02X ",r[i]);
}
/* USER CODE END 2 */
分批次写入
单片机烧录,运行
注意:确保写入时地址不会溢出,否则可能像当时的菜鸡博主一样搞半天(如下)