利用CubeMX配置硬件IIC主要注意以下几点:100/400K的速率、设备地址0xA0写地址,0xA1读地址。
我们打开CubeMX对IIC进行配置,配置之前我们查看AT24C02芯片对写入时序的要求如下:
IIC设备上可以挂多个设备,这跟IIC的从机地址有关,AT24C02这款芯片通过地址线的搭配可以连接8个从机。
上述原理图地址线全接地是0XA0。
主要用的HAL库函数用到两个函数:
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);使用之前将上述函数进行封装方便后期使用:
在建立的at24cxx.c中外部声明结构体句柄extern I2C_HandleTypeDef hi2c1;根据HAL库中对IIC的读写操作函数的格式,并对其做封装处理,目的是将该函数的底层配置在这个封装里配置好,在引用该函数时不必再重复书写。
/******************写操作******************/ int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len) { if(pbuf == NULL)//数据为空,返回-1 { return -1; } HAL_I2C_Mem_Write(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff); } /******************读操作******************/ int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len) { if(pbuf == NULL) { return -1; } HAL_I2C_Mem_Read(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff); }在At24cxx.h文件中对上述函数进行声明,并定义读写地址。
#define DEV_ADDR 0xA0 int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len); int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len);在main.c函数中直接操作就可以了
AT_Write(DEV_ADDR,0,(uint8_t*)"HELLO",5); HAL_Delay(100);//加延时为后续的读操作预留时间 AT_Read(DEV_ADDR,0,table,5);另外注意:在i2c.c中,由CobeMX生成的HAL_I2C_MspInit函数将IIC时钟的使能__HAL_RCC_I2C1_CLK_ENABLE()放在了后面,这会导致IIC不工作,所以应将其调整到IO口配置之前。
当设备的IIC引脚用于其他用途时,可以采用模拟IIC来驱动IIC接口的芯片,模拟IIC的编写主要根据时序图进行编写。
接着上述的CobeMX工程,将配置的IIC引脚改成GPIO_OUTPUT,并将其改名为IIC_SDA,IIC_CLK。
根据EEPROM手册
故要完成模拟IIC要具备几个条件
开始、等待数据传输、读操作、写操作、等待响应、结束传输。
我们首先建立两个文件myiic.c和myiic.h
GPIO写时序无非是设置其高低点评和读状态,首先针对这两个引脚进行定义
#define IIC_CLK_SET() HAL_GPIO_WritePin(GPIOB,IIC_SCL_Pin,GPIO_PIN_SET) #define IIC_CLK_RESET() HAL_GPIO_WritePin(GPIOB,IIC_SCL_Pin,GPIO_PIN_RESET) #define IIC_SDA_SET() HAL_GPIO_WritePin(GPIOB,IIC_SDA_Pin,GPIO_PIN_SET) #define IIC_SDA_RESET() HAL_GPIO_WritePin(GPIOB,IIC_SDA_Pin,GPIO_PIN_RESET) #define IIC_SDA_READ() HAL_GPIO_ReadPin(GPIOB,IIC_SDA_Pin)根据上述的时序图我们建立其时序逻辑来仿真上述时序:
SDA有两种状态,输出状态和读状态
//SDA方向设置 //n=1 输出 //n=0 读 static void IIC_SDA_Dir(uint8_t n) { GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = IIC_SDA_Pin; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; if(n) { GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; } else { GPIO_InitStruct.Mode = GPIO_MODE_INPUT; } HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); } //静态函数不能被其它文件所用;其它文件中可以定义相同名字的函数,不会发生冲突。 static void _IIC_Delay() { __nop();//运行一个机器周期 } void IIC_Start() { IIC_CLK_SET(); IIC_SDA_SET(); IIC_SDA_RESET(); _IIC_Delay(); IIC_CLK_RESET(); } void IIC_Stop() { IIC_CLK_RESET(); IIC_SDA_RESET(); IIC_CLK_SET(); _IIC_Delay(); IIC_SDA_SET(); } int IIC_WaitACK() { uint8_t timeout = 0; IIC_SDA_Dir(0);//读状态 IIC_CLK_RESET(); _IIC_Delay(); IIC_CLK_SET(); while(IIC_SDA_READ())//读此时SDA电平为1 { //如果为高电平 _IIC_Delay(); timeout++; if(timeout > 200) { IIC_SDA_Dir(1); //转换为输出态 IIC_Stop(); return -1; } } IIC_SDA_Dir(1); IIC_CLK_RESET(); return 0; } void IIC_WriteByte(uint8_t byte) { uint8_t i = 0; IIC_CLK_RESET(); for(i = 0; i < 8; i++) { IIC_CLK_SET(); if(byte & 0x80)//写第一帧数据 { IIC_SDA_SET(); } else { IIC_SDA_RESET(); } byte <<= 1; _IIC_Delay(); IIC_CLK_RESET(); } } uint8_t IIC_ReadByte() { uint8_t i = 0, tmp = 0; IIC_SDA_Dir(0); IIC_CLK_RESET(); _IIC_Delay(); for(i = 0; i < 8; i++ ) { IIC_CLK_SET(); tmp <<= 1; if(IIC_SDA_READ()) { tmp |= 0x1;//将当前状态存入tmp } _IIC_Delay(); IIC_CLK_RESET(); } return tmp; }上述的IIC功能时序问题我们已经根据时序图用代码写出来了。
接下来就是利用上述的IIC各个功能模块拼凑成EEPROM芯片能够读取和写入的指令。同样写入或者读取一组数据要按照芯片手册的要求来编写。根据上述时序图可以写成:
static void AT_WriteByte(uint8_t dev_addr, uint8_t mm_addr,uint8_t b) { IIC_Start(); IIC_WriteByte(dev_addr);//写设备地址 if(IIC_WaitACK() < 0) { return; } IIC_WriteByte(mm_addr);//写内存地址 if(IIC_WaitACK() < 0) { return; } IIC_WriteByte(b); if(IIC_WaitACK() < 0) { return; } IIC_Stop(); } static uint8_t AT_ReadByte(uint8_t dev_addr, uint8_t mm_addr) { int temp = 0; IIC_Start(); IIC_WriteByte(dev_addr); if(IIC_WaitACK() < 0) { return 0; } IIC_WriteByte(mm_addr); if(IIC_WaitACK() < 0) { return 0; } IIC_Start(); IIC_WriteByte(dev_addr | 0x1); if(IIC_WaitACK() < 0) { return 0; } temp = IIC_ReadByte(); IIC_Stop(); return temp; }最后为了兼并上述HAL库的硬件IIC程序,采用条件编译的方式兼顾两种程序
int AT_Write(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len) { #if _HW_IIC if(pbuf == NULL) { return -1; } HAL_I2C_Mem_Write(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff); #else uint8_t i=0; for(i = 0; i < 8; i++) { AT_WriteByte(dev_addr, mm_addr + i, pbuf[i]); HAL_Delay(10); } return 0; #endif } int AT_Read(uint8_t dev_addr, uint8_t mm_addr, uint8_t *pbuf, uint8_t len) { #if _HW_IIC if(pbuf == NULL) { return -1; } HAL_I2C_Mem_Read(&hi2c1, dev_addr, mm_addr, I2C_MEMADD_SIZE_8BIT, pbuf, len, 0xffff); #else uint8_t i=0; for(i = 0; i < 8; i++) { pbuf[i] = AT_ReadByte(dev_addr, mm_addr + i); HAL_Delay(10); } return 0; #endif }如果上述分析不易理解,我还将上述程序进行了图片化分析便于迅速理解:
本期IIC的介绍就到这里了,感谢大家的收看。
往期回顾:基于HAL库的SPI读FLASH