目录
[一、 I2C总线结构](#一、 I2C总线结构)
[3、 STM32F407的I2C接口](#3、 STM32F407的I2C接口)
[二、 I2C的HAL驱动程序](#二、 I2C的HAL驱动程序)
[1、 I2C接口的初始化](#1、 I2C接口的初始化)
[4、 DMA方式数据传输](#4、 DMA方式数据传输)
I2C(Inter-Integrated Circuit)接口,有时也写作IIC或I²C接口,是一种串行数字总线接口。I2C接口只有2根信号线,总线上可以连接多个设备,硬件实现简单,可扩展性强。I2C接口主要用于通信速率要求不高,以及多个器件之通信的应用场景。
本文使用的开发板:旺宝红龙STM32F407ZGT6 KIT V1.0。
一、 I2C总线结构
一个器件的I2C接口只有2根信号线,即双向串行数据线SDA和时钟信号线SCL。I2C是种多设备总线,一根I2C总线上可以挂载多个设备。
1、I2C总线的特点
- I2C总线只有两根信号线,SDA是双向串行数据线,SCL是时钟信号线,用于数据收发的同步。
- I2C总线上可以挂载多个设备,一般有一个主设备、多个从设备。MCU一般作为主设备,外围器件作为从设备。在I2C通信协议中,主动发起通信的器件就是主设备,被动进行响应的器件就是从设备。
- I2C总线上每个器件有一个7位或10位的地址,主设备发起通信时,会首先发送目标设备地址,只有地址对应的从设备才会做出响应。
- I2C总线的两根信号线有上拉电阻。当I2C器件空闲时,其输出接口是高阻态。当所有设备都空闲时,I2C总线上是高电平。
- I2C通信有标准模式和快速模式,标准模式传输速率为100kbit/s,快速模式传输速率为400kbit/s。
2、I2C总线通信协议
I2C通信总是由主机启动,每个通信过程由起始信号开始,由停止信号结束。一个数据包有8位,每个数据包后有一个应答位(ACK)或非应答位(NACK)。例如,主设备向从设备发送1字节数据的时序图:
- 起始位:当SCL是高电平时,SDA的下跳沿就是起始位,是启动一次I2C通信的起始信号。
- 停止位:当SCL为高电平时,SDA的上跳沿就是停止位,是停止一次I2C通信的结束信号。
- 数据位:在SCL的一个时钟周期内传输一个数据位,当SCL为低电平时,发送设备更新SDA的电平,当SCL为高电平时,接收设备读取SDA的电平就是有效的一位数据。
- 数据包:I2C数据通信一个数据包总是8位,也就是1字节的数据。
- 应答信号:在发送完8位数据包后,发送设备在第9个SCL时钟周期采集接收设备的应答信号。若在SCL的第9个周期采集的SDA为低电平,就是应答信号ACK,如果采集的SDA是高电平,就是非应答信号NACK。
在一次I2C通信过程中,可以传输多字节的数据。主机启动I2C通信后,发送的第一个字节是目标设备地址,后面再发送或接收的数据由具体器件的指令定义决定。I2C通信协议只是定义了基本的数据传输时序,并且通信时序由MCU的硬件I2C接口实现。
3、 STM32F407的I2C接口
STM32F407芯片上有3个硬件I2C接口,记作I2C1、I2C2和I2C3,均支持I2C标准模式和I2C快速模式,还与系统管理总线(System Management Bus,SMBus)2.0兼容。STM32F407上的I2C接口具有如下特性。
- 同一个I2C接口既可以工作于主模式,又可以工作于从模式。
- 工作于从模式时,可以设置两个从设备地址,从而对两个从地址应答。
- 使用7位或10位设备地址,还可以进行广播呼叫。
- 支持不同的通信速度:标准模式传输速率为100kbit/s,快速模式传输速率为400kbit/s。
- 带DMA功能的1字节缓存。
二、 I2C的HAL驱动程序
I2C的HAL驱动程序头文件是stm32f4xx_hal_i2c.h和stm32f4xx_hal_i2c_ex.h。I2C的HAL驱动程序包括宏定义、结构体定义、宏函数和功能函数。I2C的数据传输有阻塞式、中断方式和DMA方式。
1、 I2C接口的初始化
对I2C接口进行初始化配置的函数是HAL_I2C_Init(),其函数原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
其中,hi2c是I2C接口的对象指针,是I2C_HandleTypeDef结构体类型指针。在STM32CubeIDE自动生成的文件i2c.c中,会为启用的I2C接口定义外设对象变量,例如,为I2C1接口定义的变量如下:
cpp
I2C_HandleTypeDef hi2c1; //I2C1接口的外设对象变量
结构体I2C_HandleTypeDef的成员变量主要是HAL程序内部用到的一些定义,只有成员变量Init是需要用户配置的I2C通信参数,是I2C_InitTypeDef结构体类型。
2、阻塞式数据传输
阻塞式数据传输使用方便,且I2C接口的传输速率不高,一般传输数据量也不大,阻塞式传输是常用的数据传输方式。
|---------------------------|--------------------------|
| 函数名 | 功能描述 |
| HAL_I2C_IsDeviceReady() | 检查某个从设备是否准备好了I2C通信 |
| HAL_I2C_Master_Transmit() | 作为主设备向某个地址的从设备发送一定长度的数据 |
| HAL_I2C_Master_Receive() | 作为主设备从某个地址的从设备接收一定长度的数据 |
| HAL_I2C_Slave_Transmit() | 作为从设备发送一定长度的数据 |
| HAL_I2C_Slave_Receive() | 作为从设备接收一定长度的数据 |
| HAL_I2C_Mem_Write() | 向某个从设备的指定存储地址开始写入一定长度的数据 |
| HAL_I2C_Mem_Read() | 从某个从设备的指定存储地址开始读取一定长度的数据 |
(1)函数HAL_I2C_IsDeviceReady()
函数HAL_I2C_IsDeviceReady()用于检查I2C网络上一个从设备是否做好了I2C通信准备,其函数原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint32_t Trials,uint32_t Timeout);
其中,hi2c是I2C接口对象指针,DevAddress是从设备地址,Trials是尝试的次数,Timeout是超时等待时间(单位是嘀嗒信号节拍数),当SysTick定时器频率为默认的1000Hz时,Timeout的单位就是ms。
(2)主设备发送和接收数据
一个I2C总线上有一个主设备,可能有多个从设备。主设备与从设备通信时,必须指定设备地址。I2C主设备发送和接收数据的两个函数的原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint8_t *pData,uint16_t Size,uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint8_t *pData,uint16_t Size,uint32_t Timeout);
其中,pData是发送或接收数据的缓冲区,Size是缓冲区大小。DevAddress是从设备地址无论是发送还是接收,这个地址都要设置为I2C设备的写操作地址。Timeout为超时等待时间单位是嘀嗒信号节拍数。
阻塞式操作函数在数据发送或接收完成后才返回,返回值为HAL_OK时表示传输成功,否则可能是出现错误或超时。
(3)从设备发送和接收数据
I2C从设备发送和接收数据的两个函数的原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_slave_Transmit(I2C_HandleTypeDef *hi2c,uint8_t *pData,uint16_t Size,uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_slave_Receive(I2C_HandleTypeDef *hi2c,uint8_t *pData,uint16_t size,uint32_t Timeout)
I2C从设备是应答式地响应主设备的传输要求,发送和接收数据的对象总是主设备,所以函数中无须设置目标设备地址。
(4)I2C存储器数据传输
对于I2C接口的存储器,例如EEPROM芯片24C02,有两个专门的函数用于存储器数据读写。向存储器写入数据的函数是HAL_I2C_Mem_Write(),其原型定义如下:
cpp
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);
其中,DevAddress是I2C从设备地址,MemAddress是存储器内部写入数据的起始地址,MemAddSize是存储器内部地址大小,即8位地址或16位地址,有两个宏定义表示存储器内部地址大小。
cpp
#define I2C_MEMADD_SIZE_8BIT 0x00000001U //8位存储器地址
#define I2C_MEMADD_SIZE_16BIT 0x000000100 //16位存储器地址
参数pData是待写入数据的缓冲区指针,Size是待写入数据的字节数,Timeout是超时等待时间。使用这个函数可以很方便地向I2C接口存储器一次性写入多字节的数据。从存储器读取数据的函数是HAL_I2C_Mem_Read),其原型定义如下:
cpp
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);
使用I2C存储器数据传输函数的好处是,可以一次性传递地址和数据,函数会根据存储器的I2C通信协议依次传输地址和数据,而不需要用户自己分解通信过程。
3、中断方式数据传输
一个I2C接口有两个中断号,一个用于事件中断,另一个用于错误中断。HAL_I2C_EV_IRQHandler()是事件中断ISR中调用的通用处理函数,HAL_I2C_ER_IRQHandler()是错误中断ISR中调用的通用处理函数。
I2C接口的中断方式数据传输函数,以及各个传输函数关联的回调函数如表:
|------------------------------|---------------------------|--------------------------------|
| 函数名 | 函数功能描述 | 关联的回调函数 |
| HAL_I2C_Master_Transmit_IT() | 主设备向某个地址的从设备发送 一定长度的数据 | HAL_I2C_MasterTxCpltCallback() |
| HAL_I2C_Master_Receive_IT() | 主设备从某个地址的从设备接收 一定长度的数据 | HAL_I2C_MasterRxCpltCallback() |
| HAL_I2C_Master_Abort_IT() | 主设备主动中止中断传输过程 | HAL_I2C_AbortCpltCallback() |
| HAL_I2C_Slave_Transmit_IT() | 作为从设备发送一定长度的数据 | HAL_I2C_SlaveTxCpltCallback() |
| HAL_I2C_Slave_Receive_IT() | 作为从设备接收一定长度的数据 | HAL_I2C_SlaveRxCpltCallback() |
| HAL_I2C_Mem_Write_IT() | 向某个从设备的指定存储地址开 始写入一定长度的数据 | HAL_I2C_MemTxCpltCallback() |
| HAL_I2C_Mem_Read_IT() | 从某个从设备的指定存储地址开 始读取一定长度的数据 | HAL_I2C_MemRxCpltCallback() |
| 所有中断方式传输函数 | 中断方式传输过程出现错误 | HAL_I2C_ErrorCallback() |
中断方式数据传输函数的参数定义与对应的阻塞式传输函数类似,只是没有超时等待参数Timeout。例如,以中断方式读写I2C接口存储器的两个函数的原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint16_t MemAddress,uint16_t MemAddSize,uint8_t *pData,uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,int16_t MemAddress,uint16_t MemAddSize,uint8_t *pData,uint16_t Size);
中断方式数据传输是非阻塞式的,函数返回HAL_OK只是表示函数操作成功,并不表示数据传输完成,只有相关联的回调函数被调用时,才表示数据传输完成。
4、 DMA方式数据传输
一个I2C接口有I2C_TX和I2C_RX两个DMA请求,可以为DMA请求配置DMA流,从而进行DMA方式数据传输。I2C接口的DMA方式数据传输函数,以及DMA流发生传输完成事件(DMA_IT_TC)中断时的回调函数如表:
|-------------------------------|---------------------------|--------------------------------|
| 函数名 | 函数功能描述 | 关联的回调函数 |
| HAL_I2C_Master_Transmit_DMA() | 向某个地址的从设备发送一 定长度的数据 | HAL_I2C_MasterTxCpltCallback() |
| HAL_I2C_Master_Receive_DMA() | 从某个地址的从设备接收一 定长度的数据 | HAL_I2C_MasterRxCpltCallback() |
| HAL_I2C_Slave_Transmit_DMA() | 作为从设备发送一定长度的 数据 | HAL_I2C_SlaveTxCpltCallback() |
| HAL_I2C_Slave_Receive_DMA() | 作为从设备接收一定长度的 数据 | HAL_I2C_SlaveRxCpltCallback() |
| HAL_I2C_Mem_Write_DMA() | 向某个从设备的指定存储地 址开始写入一定长度的数据 | HAL_I2C_MemTxCpltCallback() |
| HAL_I2C_Mem_Read_DMA() | 从某个从设备的指定存储地 址开始读取一定长度的数据 | HAL_I2C_MemRxCpltCallback() |
DMA传输函数的参数形式与中断方式传输函数的参数形式相同,例如,以DMA方式读写I2C接口存储器的两个函数的原型定义如下:
cpp
HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint16_t MemAddress,uint16_t MemAddSize,uint8_t *pData,uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c,uint16_t DevAddress,uint16_t MemAddress,uint16_t MemAddSize,uint8_t *pData,uint16_t Size);
DMA传输是非阻塞式传输,函数返回HAL_OK时只表示函数操作完成,并不表示数据传输完成。DMA传输过程由DMA流产生中断事件,DMA流的中断函数指针指向I2C驱动程序中定义的一些回调函数。I2C的HAL驱动程序中并没有为DMA传输半完成中断事件设计和关联回调函数。
5、其它
一个I2C从设备有两个地址,一个是写操作地址,另一个是读操作地址。例如开发板上的EEPROM芯片24C02的写操作地址是0xA0,读操作地址是0xA1,也就是在写操作地址上加1。在I2C的HAL驱动程序中,传递从设备地址参数时,只需设置写操作地址,函数内部会根据读写操作类型,自动使用写操作地址或读操作地址。但是在软件模拟I2C接口通信时,必须明确使用相应的地址。
IIC总线判断高低电平;SPI总线判断边沿。