文章目录
- 一. 前言
- 二. 关于IIC通信
- 三. IIC通信过程
- 四. STM32实现硬件IIC通信
- 五. 关于硬件IIC的Bug
一. 前言
最近正在DIY一款智能电池,需要使用STM32F030F4P6
和TI的电池管理芯片BQ40Z50
进行SMBUS通信。SMBUS本质上就是IIC通信,项目用到STM32CubeMX+HAL库
,使用硬件IIC完成MCU对芯片的寄存器数据的读取和改写。
整篇博客主要是梳理一下IIC的通信流程,并记录一下项目实现的过程,如有错的地方,欢迎各位读者批评指正。
二. 关于IIC通信
总线又两条信号线构成一条是数据线SDA
,用于数据传输。另一条则是时钟线SCL
,用于传输CLK信号,始终由主设备控制,主机(Master)
通过控制时钟信号可以实现对从机(Slave)
的控制与读写操作。各设备通过SDA、SCL接入总线,每个连接到IIC总线上的器件都有一个唯一的地址,如上图所示。
SDA和SCL需通过上拉电阻
接至VCC
。由于连接到总线上的器件输出级必须是漏极开路或者集电极开路的,因此只要有一个器件任意时刻输出低电平,都将总线上的信号变低;当IIC总线空闲时,SDA和SCL两条线均为高电平,即各器件的SDA和SCL都是线与
的关系。
三. IIC通信过程
IIC通信过程由一系列的操作组成:
-
起始信号 :通信开始时,主设备发送一个低电平的SCL时钟脉冲,然后再发送一个低电平的SDA数据线脉冲。这个SDA的下降沿表示I2C总线上的一个开始信号。
-
从设备地址 :主设备发送从设备地址到I2C总线。I2C地址由7Bit或10Bit组成,取决于使用的设备。
-
读/写位传输 :读写位占据 1bit 数据,指定了数据传输的方向:
a. 主设备发送数据,从设备接受数据,为 0 b. 主设备接收数据,从设备发送数据,为 1
-
仲裁机制和应答 :应答位由1个Bit的数据组成。主设备通过检测应答位,以确定从设备是否存在。若主设备收到的是NACK,这会让主设备发生重启或停止流程:
a. 如果从设备存在并正确收到地址,从设备将SDA下拉,产生应答信号 0 (ACK)。b. 如果从设备不存在或未正确收到地址,导致SDA空闲,产生非应答信号1(NACK)。
-
数据传输 :在确认通信目标后,主设备将发送或接收数据。数据传输是在主机产生的每个时钟周期的上升沿或下降沿进行的。
a. 主设备发送数据: 上图所示,
主设备
将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。从设备在每个时钟周期下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。
b. 主设备接收数据: 上图所示,当读/写传输位为1时,到了数据传输时,主机从发送变成接收,从机从接收变成发送。
从设备
将要发送的数据位(8位或更多)依次发送到SDA线上,并在每个时钟周期上升沿时更新数据。主设备在每个CLK下降沿时接收数据,并在接收之后发送应答位来确认是否接收正确。 -
停止信号(Stop Signal) :通信完成后,主设备发送一个停止信号,由一个高电平的SCL时钟脉冲和一个高电平的SDA数据线脉冲组成。这个SDA的上升沿表示I2C总线上的一个停止信号。
四. STM32实现硬件IIC通信
本项目用的是STM32F030F4P6
通过Smbus通信
和TI的电池管理芯片BQ40Z50
通信,读取电池内部的电压、电流及电量信息,并且通过改写电池内部寄存器控制输入输出的Mos管。Smbus本质上就是IIC通信
,下面通过访问BQ40Z50内部的电压寄存器的例子来说明模拟IIC的实现过程:
STM32CubeMX配置如下:
1. 主设备: STM32F030F4P6
2. 从设备: BQ40Z50
3. 通信频率: 10-100KHz
4. 从设备地址: 0x16
5. 电压寄存器地址: 0x09
读取寄存器数据:
- 主机(STM32)首先产生Start信号。
- 然后紧跟着发送从机设备地址(0x16),此时读写位为0,表明是向从机写命令;
- 这时候主机等待从机(BQ40Z50)的应答信号(ACK)。
- 当主机收到应答信号时,发送要访问的寄存器地址(0x09),继续等待从机的应答信号;
- 当主机收到应答信号后,主机要改变通信模式,主机将由发送变为接收,从机将由接收变为发送。接着主机重新发送一个开始Start信号,然后紧跟着发送从机地址(0x16),注意此时读写位为1,表明将主机设置成接收模式开始读取数据。
- 这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收从机发送来的寄存器数据。
- 主机产生停止信号,结束传送过程。
虽然通信过程比较复杂,但是实现非常简单。因为大部分步骤HAL库已经封装好。直接调用现成的接口即可:
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)/**
* @brief Read an amount of data in blocking mode from a specific memory address
* @param hi2c Pointer to a I2C_HandleTypeDef structure that contains
* the configuration information for the specified I2C.
* @param DevAddress Target device address: The device 7 bits address value
* in datasheet must be shifted to the left before calling the interface
* @param MemAddress Internal memory address
* @param MemAddSize Size of internal memory address
* @param pData Pointer to data buffer
* @param Size Amount of data to be sent
* @param Timeout Timeout duration
* @retval HAL status
*/
比如,读取BQ40Z50的电压:
Uint8_t Rxbuffer[2];//用于接受数据的数组HAL_I2C_Mem_Read(hi2cx,0x16,0x09,I2C_MEMADD_SIZE_8BIT,Rxbuffer,2,OxFF);//IIC接受从机数据voltage = Rxbuffer[1]<<8|Rxbuffer[0];//对接受数据进行解析
同样的,写入寄存器数据:
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)
/*** @brief Write an amount of data in blocking mode to a specific memory address* @param hi2c Pointer to a I2C_HandleTypeDef structure that contains* the configuration information for the specified I2C.* @param DevAddress Target device address: The device 7 bits address value* in datasheet must be shifted to the left before calling the interface* @param MemAddress Internal memory address* @param MemAddSize Size of internal memory address* @param pData Pointer to data buffer* @param Size Amount of data to be sent* @param Timeout Timeout duration* @retval HAL status*/
比如:向0x00写入0x270C和0x430D可以关闭BQ40Z50的Mos管:
uint8_t Mos_off [2];
Mos_off [0] = 0x0C;
Mos_off [1] = 0x27;
if(HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF)==HAL_OK)
{Mos_off [0] = 0x3D;Mos_off [1] = 0x04;HAL_I2C_Mem_Write(hi2cx,0x16,0x00,I2C_MEMADD_SIZE_8BIT,Mos_off ,2,0xFF);
}
五. 关于硬件IIC的Bug
在开发过程中,IIC经常会遇到很多莫名其妙的Bug,比如反复超时,陷入I2C_WaitOnFlagUntilTimeout()
死循环中,或者是一直处于HAL_I2C_STATE_BUSY_RX
直接卡死,看了网络上很多的帖子,但是都没有找到好的解决办法。
这里的解决方式非常简单粗暴,就在卡死的地方直接对硬件IIC进行重置,重新初始化,亲测有效。
各位有什么更好的方法,欢迎在评论区留言,一起探讨。
void User_I2C_ErrorInit(I2C_HandleTypeDef *hi2c)
{HAL_I2C_DeInit(hi2c);hi2c->Instance = I2C1;hi2c->Init.Timing = 0x2000090E;hi2c->Init.OwnAddress1 = 0;hi2c->Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;hi2c->Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;hi2c->Init.OwnAddress2 = 0;hi2c->Init.OwnAddress2Masks = I2C_OA2_NOMASK;hi2c->Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;hi2c->Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;hi2c->Instance->CR1 |= I2C_CR1_SWRST;hi2c->Instance->CR1 &= ~I2C_CR1_SWRST;if (HAL_I2C_Init(hi2c) != HAL_OK){User_Error_Handler();}/** Configure Analogue filter*/if (HAL_I2CEx_ConfigAnalogFilter(hi2c, I2C_ANALOGFILTER_ENABLE) != HAL_OK){User_Error_Handler();}/** Configure Digital filter*/if (HAL_I2CEx_ConfigDigitalFilter(hi2c, 0) != HAL_OK){User_Error_Handler();}
}static void User_Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}
各位有什么更好的方法,欢迎在评论区留言,一起探讨。