文章目录
前言
一、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。
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所示。
- 起始位:当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.h和stm32xx_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接口的传输速率不高,一般传输数据量也不大。
函数名 | 功能描述 |
---|---|
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所示。
函数名 | 函数功能描述 | 关联的回调函数 |
---|---|---|
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 所示。
函数名 | 函数功能描述 | 关联的回调函数 |
---|---|---|
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芯片可读可写。
上图中有一个 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所示:
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 的基本的读写通讯过程,包括主机写数据到从机 (写操作),主机到从机读取数据(读操作)。下面先看一下写操作通讯过程图。
主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地址+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。
注意: IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。
3.1.2 读操作
主机向从机读取数据的操作,一开始的操作与写操作有点相似,观察两个图也可以发现,都是由主机发出起始信号,接着发送从机地址+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.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,但是要注意,连续写入的数据的存储位置不超过页的边界,否则,将自动从这页的开始位置继续存储。
所以,在连续写数据时,如果数据起始地址在页的起始位置,则一次最多可写入8个字节的数据,当然,数据存储起始地址也可以不在页的起始位置,这时要注意,一次写入的数据不要超过页的边界。
3.4 读1字节数据
用户可以从24C02的任何一个存储位置读取1字节的数据,读取1字节数据时,SDA传输的内容和顺序如图3-8 所示。
24C02 读取数据的过程是一个复合的时序,其中包含写时序和读时序。先看第一个通信过程,这里是写时序,起始信号产生后,主机发送 24C02 设备地址 0xA0,获取从机应答信号后,接着发送需要读取的内存地址;在读时序中,起始信号产生后,主机发送 24C02 设备地址 0xA1,获取从机应答信号后,接着从机返回刚刚在写时序中内存地址的数据,以字节为单位传输在总线上,假如主机获取数据后返回的是应答信号,那么从机会一直传输数据,当主机发出的是非应答信号并以停止信号发出为结束,从机就会结束传输。
3.5 连续读多字节数据
用户可以从24C02一次性连续读取多字节的数据,且读取数据时不受页边界的影响,也就是读取数据的长度可以超过8个字节。连续读多个字节数据的SDA传输内容和顺存如图 所示。主设备先进行一次写操作,写入需要读取的存储单元的地址,然后再进行一次读操作,连续读取多字节,存储器内部将自动移动存储位置,且存储位置不受页边界的影响。
在使用I2C的HAL库驱动函数进行24C02的数据读写时,图 的传输时序是由I2C硬件接口完成的,用户不需要这些时序。但如果是软件模拟I2C接口取读写24C02,则需要严格按照这些时序一个位一个位的操作。
四、示例:通过I2C通信 读写24C02
4.1 STM32CubeMx设置
在CubeMx里面设置I2C1的模式和参数,设置界面如图4-1所示。在模式设置里中,设置接口类型为I2C。还有SMBus的选项,SMbus一般用于智能电池管理。
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。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/c80b43b3c05e71bef5d49fbd9cc711c1.png#pic_center)
注:
本示例是通过按键去触发相应的函数,再通过执行函数实现与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是通过串口打印出来的信息,需要注意的是,由于开发板上的按钮没有进行消抖处理,所以每一次按下显示出来的次数可能有多次。
五、总结
本文讲解了I2C的基础知识,其中涉及了I2C的通信原理、HAL库驱动程序、I2C的三种发送模式(轮询(阻塞)、中断、DMA)。然后又介绍了通过I2C通信方式的EEPROM芯片24C02,包括该芯片的 的读写等规则,然后通过一个示例 来讲解通过I2C方式读写24C02芯片得到内容。
项目源码:HAL库STM32常用外设教程(九)—— I2C通信(读写EEPROM)
参考书籍:
《STM32Cube高效开发教程(基础篇)》王维波
《STM32F4xx中文参考手册》
《STM32F407 探索者开发指南》
无人问津也好,技不如人也罢。你都要试着安静下来,去做自己该做的事,而不是让内心烦躁,焦虑,毁掉你本就不多的热情和定力。昨日之深渊,今日之浅谈。路随远,行则将至。事随难,做则可成。
——《人民日报》