STM32HAL库学习笔记:I2C通信实战
HAL库快速部署I2C
本文主要介绍如何使用STM32CubeMX快速部署I2C通信,并与EEPROM进行数据收发。
文章目录
I2C简介
I2C是一种串行同步半双工通信方式。
I2C物理层是由一条双向数据总线SDA和一条双向时间总线SCL组成,I2C总线上可以挂载多个从机设备。
stm32f103的引脚说明,来自leung——STM32CubeMX学习笔记(9)PB8 PB9 为重映射。
I2C协议层定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。通信流程如下:
起始信号发出后,先发出设备地址决定与哪个从机通信,WRITE位为0表示写入为1表示读出。主机每发送完一个字节数据,都要等待从机的应答信号 (ACK),只有接收到应答信号后,主机才能继续发送或接收数据。然后开始写入/读出数据,对于EEPROM写入/读出的第一个数据是写入/读出内存的地址。若接收端希望结束数据传输,则向对方发送“非应答 (NACK)”信号,发送方接收到该信号后会产生一个停止信号,结束信号传输。
EEPROM简介
EEPROM 是一种掉电后数据不丢失的存储器,最常用的通讯方式就是 I2C 协议,STM32F103开发板中使用的 EEPROM 芯片型号是AT24C02。
AT24C02 EEPROM 的 7 位设备地址是:101 0000b ,即 0xA0。由于 I2C 通讯时常常是地址跟读写方向连在一起构成一个 8 位数,且当 R/W 位为 0 时,表示写方向,所以加上 7 位地址,其值为“0xA0”,常称该值为 I2C 设备的“写地址”;当 R/W 位为 1时,表示读方向,加上 7 位地址,其值为“0xA1”,常称该值为“读地址”。
AT24C02有2K的存储容量,2K=2*1024=2048位,所以最多可以往AT24C02存储器里面写2048bit,可以写256个8位字节,写或者读的地址是0X00–0X7FF。
值得一提的是根据AT24C02的规格说明书,写入ROM是比较耗时的,写周期最大5ms,没写入完就指令写入下一组数据会出现错误,这点在编程时需要注意。
EEPROM的页写入:虽然I2C通信是串行通信,一次传输一个字节,但对于EEPROM定义了一种页写入的时序。我们希望向连续地址写入多个数据的时候,只要告诉 EEPROM 第一个内存地址 address1,后面的数据按次序写入到address2、address3…这样可以节省通讯的内容,加快速度。AT24C02 型号的芯片页写入时序最多可以一次发送 8 个数据 (即 n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输 16 个数据。
24系列的eeprom提供缓存的技术,写入一页的数据,它先保存起来,当你停止对它操作时(stop信号后),eeprom再把缓存的数据写好。所以页操作的最大值时受限于IC的缓存区大小的。 ——来自:EEPROM的写入操作解析
当写入数据溢出页大小限制时,溢出数据会从页起始地址再写入,如果该地址原本有数据,会被覆盖,导致写入异常。
很好的分析AT24C02的博客:stm32专题十七:AT24C02
HAL库部署IIC通信实现多字节写入
AT24C02 型EEPROM一页最多写入8个数据,所以一般来说我们不能写入超过8个数据,但是通过合理的程序编程我们可以实现跨页写入,从而一次写入多个数据。
一、CubeMX配置
1.新建工程,常规配置调试模式、时钟树、项目环境;
2.选择PB0、PB1、PB5配置为GPIO_Out,初始为高电平,用于测试程序的指示;
3.配置I2C模式、传输速度,需要中断时开中断。
4.GENERATE CODE
二、代码编写
1.函数介绍
HAL库部署I2C通信主要使用HAL_I2C_Mem_Write函数和HAL_I2C_Mem_Read函数。
HAL_StatusTypeDef 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)
HAL_StatusTypeDef 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)
*hi2c是指向设备特定I2C配置信息的结构体的指针;
DevAddress是从机设备地址;
MemAddress是写入或者读出数据的地址;
MemAddSize是MemAddress的地址长度,注意hal库I2C函数中地址长度是以字节为单位的;
*pData是指向待写入的数据内存或者是存放读出数据的内存的指针;
Size是待写入的数据或者是待读出数据的数据大小;
Timeout是超时时间,超过该时间函数返回错误值。
2.单字节和页写入
这两种写入方式没有特别大差异,之间调用HAL_I2C_Mem_Write函数就可以实现。EEPROM 的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。
单字节写入一次写一个字节,把Size设为1就可以;页写入一次最多写入8个字节,在MemAddress模8为0时Size最大为8,参考代码如下:
#define EEPROM_ADDRESS 0xA0
#define I2C_MEMADD_SIZE_8BIT 0x00000001U
I2C_HandleTypeDef I2C_Handle;
uint32_t I2C_EE_ByteWrite(uint8_t* pBuffer, uint8_t WriteAddr)
{
HAL_StatusTypeDef status = HAL_OK;
status = HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS, (uint16_t)WriteAddr, I2C_MEMADD_SIZE_8BIT, pBuffer, 1, 100);
/* Check the communication status */
if(status != HAL_OK)
{
/* Execute user timeout callback */
//I2Cx_Error(Addr);
}
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
/* Check if the EEPROM is ready for a new operation */
while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
/* Wait for the end of the transfer */
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
return status;
}
uint32_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint8_t NumByteToWrite)
{
HAL_StatusTypeDef status = HAL_OK;
/* Write EEPROM_PAGESIZE */
status=HAL_I2C_Mem_Write(&I2C_Handle, EEPROM_ADDRESS,WriteAddr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)(pBuffer),NumByteToWrite, 100);
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
/* Check if the EEPROM is ready for a new operation */
while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS, EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);
/* Wait for the end of the transfer */
while (HAL_I2C_GetState(&I2C_Handle) != HAL_I2C_STATE_READY)
{
}
return status;
}
3.多字节写入
多字节写入主要针对多页写入设计,引入变量Addr(写入地址模8) count(第一页写入数据长度) NumOfPage(需要页数) NumOfSingle (去除整页剩余数据长度),先把写入第一页的起始地址后的部分写完再写入后面的数据。
void I2C_EE_BufferWrite(uint8_t* pBuffer, uint8_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0;
Addr = WriteAddr % EEPROM_PAGESIZE;
count = EEPROM_PAGESIZE - Addr;
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
/* 写入起始地址是8的整数倍 */
if(Addr == 0)
{
/* If NumByteToWrite < I2C_PageSize */
if(NumOfPage == 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else
{
while(NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if(NumOfSingle!=0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
/* 写入起始地址非8的整数倍 */
else
{
/* If NumByteToWrite < I2C_PageSize */
if(NumOfPage== 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
/* If NumByteToWrite > I2C_PageSize */
else
{
NumByteToWrite -= count; //去掉第一页要写入的字节数
NumOfPage = NumByteToWrite / EEPROM_PAGESIZE;
NumOfSingle = NumByteToWrite % EEPROM_PAGESIZE;
/*如果起始地址与页不对齐,需要先写满当页剩下的字节*/
if(count != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
}
while(NumOfPage--)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, EEPROM_PAGESIZE);
WriteAddr += EEPROM_PAGESIZE;
pBuffer += EEPROM_PAGESIZE;
}
if(NumOfSingle != 0)
{
I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}