基于HAL库的硬件IIC和基于时序的模拟IIC

    科技2025-01-13  12

    基于AT24C02的IIC硬件原理图

     

    硬件IIC配置过程

    利用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接口的芯片,模拟IIC的编写主要根据时序图进行编写。

    接着上述的CobeMX工程,将配置的IIC引脚改成GPIO_OUTPUT,并将其改名为IIC_SDA,IIC_CLK。

    根据EEPROM手册

    故要完成模拟IIC要具备几个条件

    开始、等待数据传输、读操作、写操作、等待响应、结束传输。

    我们首先建立两个文件myiic.cmyiic.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

    Processed: 0.017, SQL: 8