STM32 HAL库常用外设教程(九)—— I2C通信与EEPROM读写指南

文章目录

  • 前言
  • 一、I2C定义
  • 1.1 I2C总线结构
  • 1.2 I2C总线通信协议
  • 1.3 STM32F407的I2C接口
  • 二、 I2C的HAL驱动方式及程序
  • 2.1 I2C接口的初始化
  • 2.2 阻塞方式数据传输
  • 2.3 中断方式数据传输
  • 2.4 DMA方式数据传输
  • 三、EEPROM芯片 24C02
  • 3.1 接口和通信协议
  • 3.1.1 写操作
  • 3.1.2 读操作
  • 3.2 写一个字节数据
  • 3.3 连续写多字节数据
  • 3.4 读1字节数据
  • 3.5 连续读多字节数据
  • 四、示例:通过I2C通信 读写24C02
  • 4.1 STM32CubeMx设置
  • 4.2 程序设计
  • 4.2示例结果
  • 五、总结

  • 前言

    一、I2C定义

      I2C(Inter-Integrated Circuit)接口,有时也写作IIC,是一种串行数字总线接口。I2C接口只有两根信号线,总线上可以连接多个设备,硬件实现简单,可拓展性强。I2C通信协议可以通过普通GPIO引脚进行软件模拟。I2C接口主要用于通信速率要求不高,以及多个器件之间通信的应用场景。

    1.1 I2C总线结构

      一个器件的I2C接口只有2根信号线,即双向串行数据线SDA和时钟信号线SCL。I2C是一种多设备总线,一根I2C总线上挂载多个设备。例如,STM32F407开发板上的I2C总线通信系统如图1-1所示,它连接了多个I2C器件,从图中可以看出其中一个是EEPROM器件24C02,另一个是加速度计和陀螺仪传感器芯片MPU6050。

    图1-1 MCU通过IIC总线的期间连接的示意图

    I2C总线有如下的特点。

  • I2C总线只有两个信号线,SDA是双向串行数据线,SCL是时钟信号线,用于数据收发的同步。
  • I2C总线上可以挂载多个设备,支持多设备连接,允许多主机存在,每个设备都有一个唯一的地址,一般只有一个主设备,多个从设备。MCU一般作为主设备,外围电路作为从设备。在I2C通信协议中,主动发起通信的器件就是主设备,被动进行响应的器件就是从设备。
  • I2C总线上每个设备有一个7位或10位的地址,主设备发起通信时,会首先发送目标设备地址,只有地址对应的设备才会做出响应。
  • I2C总线的两根信号线有上拉电阻。当I2C器件空闲时,其输出接口是高阻态。当所有设备都有空闲时,I2C总线上是高电平
  • I2C通信有标准模式和快速模式,标准模式传输速率是 100Kbit/s(12.5KB/s),快速模式传输速率为400Kbit/s(50KB/s)。
  • 1.2 I2C总线通信协议

      I2C通信总是由主机启动,每个通信过程由起始信号开始,由停止信号结束。一个数据包有8位,每个数据包有一个应答位(ACK)或非应答位(NACK),例如,主设备向从设备发送1字节数据的时序图,如图1-2所示。

    图1-2 IIC传输1字节的时序图

  • 起始位:当SCL是一个高电平时,SDA的下跳沿就是起始位,是启动一次I2C通信的起始信号。
  • 信号位:当SCL是高电平时,SDA的上跳沿就是停止位,是停止一次I2C通信的结束信号。
  • 数据位在SCL的一个时钟周期内传输一个数据位,在SCL的一个时钟周期内传输一个数据位,当SCL为低电平时,发送设备更新SDA的电平,当SCL为高电平时,接收设备读取SDA的电平就是一次有效的一位数据
  • 数据包I2C数据通信一个数据包总是8位。也就是1字节的数据。
  • 应答信号:在发送完8位数据包后,发送设备在第9个SCL时钟周期采集接收设备的应答设备。若在SCL的第九个周期采集的SDA为低电平,就是应答信号ACK,表示接收设备 正确接收到数据包,并发出应答信号。如果采集的SDA是高电平,就是非应答信号NACK,表示接收设备未正确接收到数据包,或者不希望继续通信,故发出非应答信号。
      在一次I2C通信过程中,可以传输多个字节的数据。主机启动I2C通信后,发送的第一个字节是目标设备地址,后面在发送或接收的数据由具体器件的指令定义决定I2C通信协议只是定义了基本的数据传输时序,图1-2的通信时序由MCU的硬件I2C接口实现,这就是软件模拟I2C通信时序2,这就是软件模拟I2C接口。
  • 1.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库驱动程序头文件是stm32xx_hal_i2c.hstm32xx_gal_i2c_ex.h。I2C的HAL驱动程序包括宏定义、结构体定义、宏定义和函数功能。。I2C的数据传输有阻塞式、中断方式和DMA方式

    2.1 I2C接口的初始化

      对I2C接口进行初始化配置的函数是HAL_IC_Init(),其函数原型定义如下:

    HAL_StatusTypeDef HAL_I2C_Init(I2C_HandleTypeDef *hi2c)
    

      其中,hic2是I2C接口的对象指针,是I2C_HandleTypeDef 结构体类型指针。在CubeMx自动生成的文件i2c.c中,会为启用的I2C接口定义外设对象变量,例如,为I2C接口定义的变量如下:

    I2C_HandleTypeDef  hi2c1;     //I2C1接口的外盒对象变量
    

      结构体I2C_HandleTypeDef 的成员变量主要是HAL程序内部用到的一些定义,只有成员变量Init是需要用户配置的I2C 通信参数,是I2C_HandleTypeDef 结构类型。

    2.2 阻塞方式数据传输

      I2C接口的阻塞式数据传输相关函数如表1-1所示。阻塞式数据传输使用方便,且I2C接口的传输速率不高,一般传输数据量也不大。

    表2-1 I2C接口的阻塞式数据传输相关函数

    函数名 功能描述
    HAL_I2C_IsDeviceReady() 检查某个从设备是否准备好了I2C通信
    HAL_I2C_Master_Transmit() 作为主设备向某个地址的从设备发送一定长度的数据
    HAL_I2C_Master_Receive() 作为主设备向某个地址的从设备接受一定长度的数据
    _HAL_I2C_Slave_Transmit() 开启某个中断事件源,允许事件产生硬件中断
    HAL_I2C_Mem_Write() 向某个从设备的指定存储地址开始写入一定长度的数据
    HAL_I2C_Mem_Read() 向某个从设备的指定存储地址开始读取一定长度的数据

    (1)函数HAL_I2C_IsDeviceReady()
      函数HAL_I2C_IsDeviceReady()用于检查I2C网络上一个设备是否做好了I2C通信准备,其函数原型如下:

     HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);
    

      其中,hi2c是I2C接口对象指针,DevAddress是从设备地址,Trials是尝试的次数,Timeout是超时等待时间(单位是嘀嗒信号节拍数),当SysTicks定时器频率为默认的1000Hz时,Timeout的单位就是ms。
    注:
      一个I2C从设备有两个地址,一个是写操作地址,另一个是读操作地址。例如,开发板上的EEPROM芯片24C02的写操作地址是0xA0,读操作地址是0xA1,也就是在写操作地址上加1。在I2C的HAL库驱动中,传递从设备地址参数时,只需要设置写操作地址,函数内部会根据读写操作类型,自动使用写操作地址或读操作地址。但是在软件模拟I2C接口通信时,必须明确使用相应的地址。

    (2)主设备发送和接收数据
      一个I2C总线上有一个主设备,可能有多个设备。主设备和从设备通信时,必须指定从设备地址。I2C主设备发送和接收数据的两个函数的原型定义如下:

    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从设备发送和接收数据的两个函数的原型定义如下:

    HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
    HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
    

      I2C从设备是应答式地响应主设备的传输要求,发送和接收数据的对象总是主设备,所以函数中无需设置目标设备地址。
    (4)I2C存储器数据传输
      对于I2C接口的存储器,例如EEPROM芯片24C02,有两个专门的函数用于存储器数据读写。向存储器写入数据的函数是HAL_I2C_Mem_Write(),其原型定义如下:

    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位地址。在一些I2C设备中,存储器内部地址可能只需要8位来表示,而在另一些设备中,存储器内部地址可能需要16位来表示,8位地址可以表示256个不同的存储器位置,范围为0x00到0xFF,16位地址可以表示65536个不同的存储器位置,范围为0x0000到0xFFFF。程序中有两个宏定义表示存储器内部地址的大小。

    #define I2C_MEMADD_SIZE_8BIT            0x00000001U
    #define I2C_MEMADD_SIZE_16BIT           0x00000010U
    

      参数pData是待写入数据的缓冲区指针,Size是待写入数据的字节数,Timeout是超时等待时间。使用这个函数可以很方便地向I2C接口存储器一次性写入多个字节。
      从存储器读取数据的函数是HAL_I2C_Mem_Read(),其原型定义如下:

    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通信协议依次传输地址和数据,而不需要用户自己分解通信过程。

    2.3 中断方式数据传输

      一个I2C接口有两个中断号,一个用于事件中断,另一个用于错误中断。HAL_I2C_EV_IRQHandler() 是事件中断ISR中调用处理函数,HAL_I2C_ER_IRQHandler() 是错误中断ISR中调用的通用处理函数。
    I2C接口的中断方式数据传输函数,以及各个传输函数关联的回调函数如表2-2所示。

    表2-2 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_Receive_IT() 作为从设备接收一定长度的数据 HAL_I2C_SlaveRxCpltCallback()
    HAL_I2C_Slave_Transmit_IT() 作为从设备发送一定长度的数据 HAL_I2C_SlaveTxCpltCallback()
    HAL_I2C_Mem_Write_IT() 向某个从设备的指定存储地址开始写入一定长度的数据 HAL_I2C_MemTxCpltCallback()
    HAL_I2C_Mem_Read_IT 从某个从设备的指定存储地址开始读一定长度的数据 HAL_I2C_MemRxCpltCallback()
    所有中断方式传输函数 中断方式传输过程出现错误 HAL_I2C_ErrorCallback()

      中断方式数据传输函数的参数定义与对应的阻塞式传输函数类似,只是没有超时等待时间Timeout。例如,以中断方式读写I2C接口存储的两个函数的原型定义如下:

    HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
    HAL_StatusTypeDef HAL_I2C_Master_Seq_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
    

      中断方式数据传输是非阻塞式的,函数返回HAL_OK只是表示函数操作成功,并不表示数据传输完成,只有相关的回调函数被调用时,才表示数据传输完成。

    2.4 DMA方式数据传输

      一个I2C接口有I2C_TX和I2C_RX两个DMA请求,可以为DMA请求配置DMA流,从而进行DMA方式数据传输。I2C接口的 DMA方式数据书传输函数,以及DMA流发生传输完成事件(DMA_IT_TC)中断时的回调函数如表2-3 所示。

    表2-3 I2C接口的DMA方式数据传输函数以及关联的回调函数

    函数名 函数功能描述 关联的回调函数
    HAL_I2C_Master_Transmit_DMA() 主设备向某个地址从设备发送一定长度的数据 HAL_I2C_MasterTxCpltCallback()
    HAL_I2C_Master_Receive_DMA() 主设备从某个地址的从设备接收一定长度的数据 HAL_I2C_MasterRxCpltCallback()
    HAL_I2C_Slave_Receive_DMA() 作为从设备接收一定长度的数据 HAL_I2C_SlaveRxCpltCallback()
    HAL_I2C_Slave_Transmit_DMA() 作为从设备发送一定长度的数据 HAL_I2C_SlaveTxCpltCallback()
    HAL_I2C_Mem_Write_DMA() 向某个从设备的指定存储地址开始写入一定长度的数据 HAL_I2C_MemTxCpltCallback()
    HAL_I2C_Mem_Read_DMA 从某个从设备的指定存储地址开始读一定长度的数据 HAL_I2C_MemRxCpltCallback()

      DMA传输函数的参数形式与中断方式传输函数的参数形式相同,例如,以DMA方式读写I2C接口存储器的两个函数原型定义如下:

    HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
    HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
    

      DMA传输是非阻塞式传输,函数返回HAL_OK时只表示函数操作完成,并不表示数据传输完成。DMA传输过程中由DMA 流产生中断事件,DMA流的中断函数指针指向I2C驱动程序中定义的一些回调函数。I2C的HAL驱动程序中并没有DMA传输半完成中断事件设计和关联回调函数。

    三、EEPROM芯片 24C02

    3.1 接口和通信协议

      开发板上有一个I2C接口的EEPROM芯片24C02,24C02存储容量为256字节,2K bit,它的电路连接如图3-1所示。其SDA和SCL引脚连接STM32F407芯片的I2C1接口,使用引脚PB9和PB8。WP是写保护引脚,WP接地时,对24C02芯片可读可写。

    图 3-1 24C02引脚图

    图 3-2 24C02开发板电路图

      上图中有一个 WP,这个是写保护引脚,接高电平只读,接地允许读和写,从开发板电路图中可以得出该引脚接地,可以进行读和写操作。
      每一个设备都有自己的设备地址, 24C02 也不例外,其设备地址格式如图 3-3所示,从图中可以看出,24C02 的设备地址是包括不可编程部分(1010)和可编程部分(A2 A1 A0),可编程部分是根据上图的硬件引脚 A0、 A1 和 A2 所决定 。通过图3-2的电路图可以得出, A0、 A1 和 A2 均接地处理,所以 24C02 设备的读操作地址为: 0xA1(二进制为1010 0001);写操作地址为: 0xA0(二进制为1010 0000)。
      设备地址最后一位(R/W)用于设置数据的传输方向,即读操作/写操作, 0 是写操作, 1 是读操作 设备地址最后一位(R/W)用于设置数据的传输方向,即读操作/写操作, 0 是写操作, 1 是读操作,具体格式如下图3-3所示:

    图 3-3 设备地址格式

    图3-3 读操作地址0xA1

      24C02的I2C设备地址组成如图3-2图所示,A2、A1、A0由芯片的引脚A2、A1、A0的电平决定,图3-1中这三个引脚都接地,所以都是0。最低位R/W是读写标志位时读写标志位,当主设备对24C02进行写操作时,R/W为0,进行读操作时,R/W为1。所以,24C02的写操作地址为0xA0,读操作地址为0xA1。HAL驱动程序函数中需要传递I2C从设备的地址时,都使用写操作地址。
      24C02的读写操作比较简单,存储空间可反复读写。以下是几种主要的读写操作定义。

    3.1.1 写操作

      下面介绍一下 IIC 的基本的读写通讯过程,包括主机写数据到从机 (写操作),主机到从机读取数据(读操作)。下面先看一下写操作通讯过程图。

    图3-4 写操作通讯过程图

      主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。
    注意: IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

    3.1.2 读操作

    图3-5 写操作通讯过程图

      主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+1(读操作)组成的 8bit 数据,从机接收到数据验证是否是自身的地址。 那么在验证是自己的设备地址后,从机就会发出应答信号,并向主机返回 8bit 数据,发送完之后从机就会等待主机的应答信号。假如主机一直返回应答信号,那么从机可以一直发送数据,也就是图中的(n byte + 应答信号)情况,直到主机发出非应答信号,从机才会停止发送数据

    3.2 写一个字节数据

      MCU向24C02写入1字节数据的SDA传输内容的顺序如图所示。操作的顺序如下。

  • 主机发送起始信号,然后主机在 IIC 总线发送第 1 个字节的数据为 24C02
    的设备地址 0xA0,用于寻找总线上找到 24C02。
  • 在获得24C02应答ACK应答信号后,主机再发送8位字地址,这是24C02内部存储单元的地址。8位地址的范围是0~255,也就是24C02内256字节存储单元的地址。
  • 再等到24C02应答信号ACK后,主机再发送需要写入的1字节的数据,这里的数据即是写入在第 2 字节内存地址的数据。
  • 从机接收完数据后,应答ACK,主机发停止信号结束传输。
  • 图3-6 写1个字节数据的SDA传输内容和顺序

    3.3 连续写多字节数据

      上面的写操作只能单字节写入到 24C02,效率比较低,所以 24C02 有页写入时序,大大提高了写入效率。24C02内部存储区域划分,每页8字节,所以256字节的存储单元为32页,页的起始地址是8*N,其中N = 0,1,…,31
      用户可以在一次I2C通信过程(一个起始信号与一个停止信号限定的通信过程)中向24C02连续写入多个字节的数据,SDA传输内容和顺序如图3-7所示。在单字节写时序时,每次写入数据时都需要先写入设备的内存地址才能实现,在页写时序中,只需要告诉 24C02 第一个内存地址 1,后面数据会按照顺序写入到内存地址 2,内存地址 3等,大大节省了通信时间,提高了时效性。图3-7中的n是数据存储的起始地址,存储的数据字节数为1+x,但是要注意,连续写入的数据的存储位置不超过页的边界,否则,将自动从这页的开始位置继续存储

    图3-7 24C02连续写入多个字节数据的SDA传输内容和顺序

      所以,在连续写数据时,如果数据起始地址在页的起始位置,则一次最多可写入8个字节的数据,当然,数据存储起始地址也可以不在页的起始位置,这时要注意,一次写入的数据不要超过页的边界

    3.4 读1字节数据

      用户可以从24C02的任何一个存储位置读取1字节的数据,读取1字节数据时,SDA传输的内容和顺序如图3-8 所示。
      24C02 读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送 24C02 设备地址 0xA0,获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送 24C02 设备地址 0xA1,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。

    图3-8 读1个字节数据的SDA传输内容和顺序

    3.5 连续读多字节数据

      用户可以从24C02一次性连续读取多字节的数据,且读取数据时不受页边界的影响,也就是读取数据的长度可以超过8个字节。连续读多个字节数据的SDA传输内容和顺存如图 所示。主设备先进行一次写操作,写入需要读取的存储单元的地址,然后再进行一次读操作,连续读取多字节,存储器内部将自动移动存储位置,且存储位置不受页边界的影响。
      在使用I2C的HAL库驱动函数进行24C02的数据读写时,图 的传输时序是由I2C硬件接口完成的,用户不需要这些时序。但如果是软件模拟I2C接口取读写24C02,则需要严格按照这些时序一个位一个位的操作

    四、示例:通过I2C通信 读写24C02

    4.1 STM32CubeMx设置

      在CubeMx里面设置I2C1的模式和参数,设置界面如图4-1所示。在模式设置里中,设置接口类型为I2C。还有SMBus的选项,SMbus一般用于智能电池管理。

    图4-1 I2C1的模式和参数设置

    I2C1的参数设置分为2组,各组参数的意义和设置内容描述如下。
    (1)Master Features组,主设备参数。
    I2C Speed Mode,速度模式。可选标准模式(Standard Mode)或快速模式(Fast Mode)。
    I2C Clock Speed(Hz),I2C时钟速度。标准模式下最大值为100kHz,快速模式下最大值为400kHz。
    Fast Mode Duty Cycle,快速模式占空比。选择快速模式后这个参数会出现,用于设置时钟信号的占空比,是一个周期内低电平与高电平的时间比,有2:1和16:9两种选项。本示例中,速度模式选择到标准模式,所以图4-1中没有这个参数。
    (2)Slave Features组,从设备参数。
    Clock No Stretch Mode,禁止时钟延长。设置为Disable表示允许时钟延长。
    Primary Address Length selection,设备主地址长度。可选7-bit或10-bit,这里选择7-bit。
    Dual Address Acknowledge,双地址确认。从设备可以有两个地址,如果设置为Enabled,还会出现一个Secondary slave address参数,用于设置从设备副地址。
    General Call address detection,广播呼叫检测。设置为Disable表示禁止广播呼叫,不对地址0x00应答;否则,就是允许广播呼叫,对地址0x00应答。
      设置I2C的模式为I2C时,CubeMx自动分配的引脚可能是PB6和PB7,而不是开发板上实际用的PB8和PB9,这是因为一个外设有多组复合引脚。在引脚视图上直接将PB8为I2C_SCL,将PB9设置为I2C_SDA,PB6和PB7的设置就会自动取消。I2C1的GPIO引脚设置如图4-2所示,自动设置为复用功能开漏,并且有上拉。
      在开发板上,STM32F407是I2C主设备,所以无须设置从设备地址。24C02是I2C从设备,其从设备地址是0xA0。
      I2C的中断事件主要是表示传输过程中和错误的一些事件,由于I2C通信时一种应答式通信,与其他外设的轮询式操作类似,本示例不开启I2C1的中断。I2C也具有DMA功能,当时24C02操作的数据量小,没有使用DMA的必要。

    4.2 程序设计

      在CubeMx里完成设置后生成代码,我们在Keil里面打开项目。
    (1)先通过CubeMx自动生成的部分函数进一步谅解CubeMx的配置.
      在i2c.c文件中,函数MX_I2C1_Init()用于I2C1接口的初始化,如图4-2所示,MX_I2C1_Init()对hi2c1的各个成员变量赋值,各赋值语句与CubeMx里的配置是对应的。完成hic1的赋值后,执行HAL_I2C_Init()对I2C1接口进行初始化。HAL_I2C_MspInit()函数的是I2C接口的MSP初始化函数,在函数HAL_I2C_MspInit()里面被调用。函数HAL_I2C_MspInit() 的主要功能是对I2C1接口的复用引脚PB8和PB9进行GPIO引脚配置,如图4-3。

    图4-2 I2C1的初始化设置①

    图4-3 I2C1的初始化设置②

    注:
      本示例是通过按键去触发相应的函数,再通过执行函数实现与EEPROM芯片24C02之间的读写操作,按键的实现过程已将在《HAL库STM32常用外设教程(二)—— GPIO输入\输出》中讲述(主要是keyled.c和keyled.h),如果又兴趣可以去了解一下,也可以直接下载其中的源码工程,在源码的基础上实现本章的项目。从另一个角度讲,如果不用按键的话同样是可以的,甚至在main函数的While循环里面直接通过延时去不断的循环这几个函数也是可以的,重点是明白其中读写EEPROM的过程是如何实现的 以及是如何通过I2C通信进行读写的。

    (2)在本章的源码里面提供了两个文件,24xx.c和24cxx.h,这两个文件是24C02的驱动程序,是根据24C02的指令定义,实现器件具体功能操作的一系列函数。将这两个文件添加到本次项目的工程中,后面需要自己编写的几个函数都是调用的其提供的函数。具体要求及实现的功能如下:
      程序调用函数EP24C_IsDeviceReady()函数检查24C02器件是否正常,然后显示了一个菜单,菜单内容如下:

    Divice id ready
    [1]KeyUp=Write a number
    [2]Key0=Read the number
    [3]Key1=Write a Sting
    [4]Key2=Read the string

    在While循环里面检测按键输入,根据按下的按键执行相应的响应代码。

  • 按下KEY_UP键时,调用函数 EP24C_WriteOneByte() 向一个地址写入1字节的数据。
  • 按下KEY0键时,调用函数 EP24C_ReadOneByte() 从同一个地址读出数据,以检验写入和读出的是否一致。
  • 按下KEY1时,调用函数 EP24C_WriteLongData() 从Page2起始地址开始写入一个长字符串,字符串又结束字符‘\0’,函数内部会将长数据分解为多个页后分批写入。
  • 按下KEY2时 P24C_ReadBytes() 从Page2起始地址读取50个字节。
  • 注:为了方便观察,本实例中加入了对灯的控制,即在按键按下时对应的灯的状态会发生变化。

    	if(EP24C_IsDeviceReady()	== HAL_OK){
    		printf("Divice id ready\n");
    	}
    	printf("[1]KeyUp=Write a number\n[2]Key0=Read the number\n[3]Key1=Write a Sting\n[4]Key2=Read the string");
    


    下面将整个while(1)循环复制下来以便参考:

      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    		uint16_t addr_any = 4;
    		uint8_t num1 =107,num2;
    		uint16_t addr_page = 2*8;
    		 KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
    
    	  switch(curKey)
    	  {
    	  case KEY0:
    		  LED0_Toggle();																		/* 切换LED0的状态 */
    			if(EP24C_ReadOneByte(addr_any,&num2) == HAL_OK){	/* 从EE2PROM读取一个字节到num2,如果读取成功,返回HAL_OK */
    				printf("Read %d at Arrress %d\n",num2,addr_any);
    			}
    		  break;
    
    	  case KEY2:
    		LED1_Toggle();																			/* 翻转LED1的状态 */
    		uint8_t strOut[50];																	/* 因为case是标签,C语言中标签的下一句不能是定义变量的表达式,所以放在翻转灯后面 */
    		if(EP24C_ReadBytes(addr_page,strOut,sizeof(strOut)) == HAL_OK){/* 从Page2起始地址读取50个字节 */
    				printf("Read string from Page2 = %s\n",strOut);
    			}
    		break;
    
    	  case KEY_UP:
    		  LED0_Toggle();																	/* 翻转LED0的状态 */
    		  LED1_Toggle();																	/* 翻转LED1的状态 */
    			if(EP24C_WriteOneByte(addr_any,num1) == HAL_OK){/* 从EE2PROM写入一个字节到num1,如果写入成功,返回HAL_OK */
    				printf("write %d at Arrress %d\n",num1,addr_any);
    			}
    		break;
    
    	  case KEY1:
    		{
    		uint8_t strIn[50] = "University of CHAIN";      
    		Buzzer_Toggle();																			/* 翻转蜂鸣器的状态 */
    		if(EP24C_WriteLongData(addr_page,strIn,sizeof(strIn)) == HAL_OK){/* 从Page2起始地址开始写入2一个长字符串 */
    				printf("Write string from Page2 %s\n",strIn);
    			}
    		}
    		default:
    			break;
    	  }
      }
      /* USER CODE END 3 */
    }  /* USER CODE BEGIN WHILE */
    

    (3)因为上述需要用到串口,添加代码时需要注意要实现串口的重定向。


    代码如下(示例):

    /* 串口重定向 */
    int fputc(int ch, FILE*f)
    	{
    		while (!(USART1->SR & (1 << 7)));
    		USART1->DR = ch;
    		return ch;
    	}
    

    4.2示例结果

      将上面的程序下载到下载板中,按下按键,在串口中进行显示,图4-4显示的是开发板的按键,图4-5是通过串口打印出来的信息,需要注意的是,由于开发板上的按钮没有进行消抖处理,所以每一次按下显示出来的次数可能有多次。
    请添加图片描述

    图4-4 开发板按键

    图4-5 开发板按键

    五、总结

      本文讲解了I2C的基础知识,其中涉及了I2C的通信原理、HAL库驱动程序、I2C的三种发送模式(轮询(阻塞)、中断、DMA)。然后又介绍了通过I2C通信方式的EEPROM芯片24C02,包括该芯片的 的读写等规则,然后通过一个示例 来讲解通过I2C方式读写24C02芯片得到内容。


    项目源码:HAL库STM32常用外设教程(九)—— I2C通信(读写EEPROM)
    参考书籍:
    《STM32Cube高效开发教程(基础篇)》王维波
    《STM32F4xx中文参考手册》
    《STM32F407 探索者开发指南》


      无人问津也好,技不如人也罢。你都要试着安静下来,去做自己该做的事,而不是让内心烦躁,焦虑,毁掉你本就不多的热情和定力。昨日之深渊,今日之浅谈。路随远,行则将至。事随难,做则可成。
                                      ——《人民日报》

    作者:printf_01

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库常用外设教程(九)—— I2C通信与EEPROM读写指南

    发表评论