使用STM32 HAL库实现AT24C02的IIC通信
一、IIC通信协议
IIC是一种通用串行同步通信协议,将IIC通信分为物理层和协议层两方面来学习。
1.物理层
物理层上有如下特点:
(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连
接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线 (SDA) ,一条串行时钟线 (SCL)。数
据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访 问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。 (6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达
3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制。
上拉电阻和高阻态:根据IIC物理层,在使用IIC通信协议的SDA和SCL两个线时,需要设置内部上拉电阻或者外加电阻至高电平。当stm32作为硬件主机时,连接到总线上所有设备有唯一的IIC地址,主机可以拉低SDA电平以开始通信。(使用CubeMX配置HAL库一般配置开漏输出,且硬件上拉)
SDA线和SCL线:IIC通信主要通过这两根线实现通信,SDA为IIC数据线,用于传输数据,SCL为时钟线,用于同步主机与从机的读写数据时钟,所以IIC通信为同步通信协议
2.协议层
起始信号S:由主机拉低SDA电平,来表示开始通信,S起始信号发出后,主机拉低SCL电平,准备开始发送数据
从机地址SLAVE ADDRESS:在总线上的从机的唯一地址,有7位或者10位,一般用7位地址,紧跟在S信号后,当SCL由低电平变为高电平时,地址数据由低位到高位按位发送
读写位:R/W为读写位,0表示写W,1表示读R。一般与从机地址7位地址组成8位写地址或者读地址
应答位A:由从机拉低SDA线表示应答,若从机没有拉低SDA线,则为非应答。主机会在这里拉高SCL线,并等待一段时间的应答。主机接收到应答后会继续发送数据,若未接收到即接收到非应答位,停止发送数据
终止信号P:当数据发送完毕,SCL由低电平变为高电平时,SDA变为高电平表示终止信号
IIC协议的写数据与读数据(来自数据手册)
S 表示由主机的IIC接又产生的传输起始信号,这时连接到IIC总线上的所有从都会接收到这个信号。
起始信号产生后,所有从机就开始等待主机紧接下来广播的从机地址信号(SLAVE_ ADDRESS)。在PC总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地 址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据IIC协议, 这个从机地址可以是7位或10 位。
在地址位之后,是传输方向的选择位,该位为0时,表示后面的数据传输方向是由主机 传输至从机,即主机向从机写数据。该位为1时,则相反,即主机由从机读取数据。
从机接收到匹配的地址后,主机或从机会返回 一个应答或非应答信号,只有接收到应答 信号后, 主机 才能继续发送或接 收数据。
若配置的方向传输位为“写数据” 方向,即图22-2的情况,广播完地址,接收到应答 信号后,主机开始正式向从机传输数据(DATA),数据包的大小为8 位。主机每发送完 一个 字节数据,都要等待从机的应答信号,重复这个过程,可以向从机传输N 个数据,这个N 没有大小限制。当数据传输结束时,主机向从机发送一个停止传输信号(P),表示不再传输 数据。
若配置的方向传输位为 “读数据” 方向,即图22-3的情况,广播完地址,接收到应答信 号后,从机开始向主机返回数据,数据包大小也 8 位。从机每发送完 一个数据,都会等待 主机的应答信号,重复这个过程,可以返回N 个数据,这个N 也没有大小限制。当主机希 望停止接收数据时,就向从机返回一个非应答信号,则从机自动停止数据传输。
除了基本的读写,FC通信更常用的是复合格式,即图22- 4的情况,该传输过程有两次 起始信号。一般在第1次传输中,主机通过SLAVE_ADDRESS 寻找到从设备后,发送一段 “ 数据”,这段数据通常用于表示从设备内部的寄存器或存储器地址(注意区分它与SLAVE_ ADDRESS的区别);在第2 次的传输中,对该地址的内容进行读或写。也就是说,第1次通信是告诉从机读写地址,第2 次则是读写的实际内容。
二、IIC通信的HAL库
1.HAL库函数和关键词分析
HAL_I2C_Master_Transmit(I2C_HandleTypeDef* hi2c, uint16_t DevAddress,uint8_t *pData, uint16_t Size, uint32_t Timeout);:HAL库的IIC写数据函数,适用于不需要写寄存器的时候调用该函数,比如用于与IIC从机握手的时候。*hi2c为IIC端口,DevAddress为从机设备地址,*pData为要写入的数据的地址,Size为要发送的字节数(EEPROM为按字节写的存储器),Timeout为最大传输时间
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);:HAL库的IIC读数据函数,*hi2c为IIC端口,DevAddress为从机设备地址,*pData为要读区的数据的地址,Size为要接收的字节数(EEPROM为按字节写的存储器),Timeout为最大读取时间
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库的写数据函数,用于数据需要写到从机寄存器时调用该函数,比如向EEPROM写存储数据。*hi2c为IIC端口,DevAddress为从机设备地址,MemAddress为从机寄存器地址,MenAddSize为从机寄存器地址长度,*pData为发送数据地址,Size为传输数据字节数,Timeout为最大传输时间
注:使用HAL_I2C_Mem_Write相当于先用HAL_I2C_Master_Transmit先传输寄存器地址,再用HAL_I2C_Master_Transmit传输寄存器的数据。
2.CubeMX配置IIC
在IIC中一般用默认配置即可,在IIC使用的引脚被占用时,可以通过端口映射到其他引脚
如果使用CubeMX配置IIC,不需要再去设置IIC的引脚,默认开漏即可,一般会有外部硬件上拉电阻(有的需要自己手动添加外部硬件上拉!)
三、IIC通信实现(AT24C02读写)
以IIC读写EEPROM为例的IIC通信实验。EEPROM是一种掉电后数据不会丢失的存储器,以AT24C02型号的EEPROM存储器为例,AT24C02可以存储2kb的数据,STM32F407可以通过IIC读写EEPROM。
AT24C02的地址为七位地址,地址取决于芯片上的引脚A0、A1、A2的电平。以带有该芯片的STM32F407ZGT6开发版为例,其地址为0xA0,SDA连接在PB6,SCL连接在PB7,IIC为I2C1。(注意!!!实验室的f103c8t6开发版所带的AT24C02芯片与f103之间IIC没有外部上拉,必须外接10k电阻上拉!!!TMD就因为这个折腾我好久!!!)
这里我将AT24C02的IIC操作封装成库文件,便于代码的快速移植,以提高main.c文件的可读性(注意:AT24C02支持连续写入,每次写入需要等待5ms左右,而且连续写入最大为8字节,即一次行写整个2kb内存需要32次!!!)
at24c02.h文件
#ifndef __AT24C02_H
#define __AT24C02_H
#include "stm32f1xx_hal.h"
#include "stm32f1xx_hal_i2c.h"
#define AT24_Write 0xA0
#define AT24_Read 0xA1
uint8_t AT24_Init(I2C_HandleTypeDef);
uint8_t AT24_Write_Byte(uint8_t reg , uint8_t data);
uint8_t AT24_Read_Byte(uint8_t reg);
uint8_t AT24_Write_Len(uint8_t reg,uint8_t len,uint8_t *buf); //iic连续读写
uint8_t AT24_Read_Len(uint8_t reg,uint8_t len,uint8_t *buf);
#endif
at24c02.c文件
#include "at24c02.h"
I2C_HandleTypeDef AT24_IIC;
uint8_t AT24_Init(I2C_HandleTypeDef hi2c){
AT24_IIC = hi2c;
uint8_t R_Data;
return (HAL_I2C_Mem_Read(&AT24_IIC, AT24_Read, 0x00, I2C_MEMADD_SIZE_8BIT, &R_Data, 1, 0xff) != HAL_OK);
}
uint8_t AT24_Read_Byte(uint8_t reg){
unsigned char R_Data=0;
HAL_I2C_Mem_Read(&AT24_IIC, AT24_Read, reg, I2C_MEMADD_SIZE_8BIT, &R_Data, 1, 0xfff);
HAL_Delay(20);
return R_Data;
}
uint8_t AT24_Write_Byte(uint8_t reg,uint8_t data)
{
unsigned char W_Data=0;
W_Data = data;
HAL_I2C_Mem_Write(&AT24_IIC, AT24_Write, reg, I2C_MEMADD_SIZE_8BIT, &W_Data, 1, 0xfff);
HAL_Delay(20);
return 0;
}
//连续写入函数需要根据AT24C02芯片的特性更改
uint8_t AT24_Write_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
if(len >8){
uint8_t count = len/8;
uint8_t noun = len%8;
for(int p=0; p<count ; p++){
HAL_I2C_Mem_Write(&AT24_IIC, AT24_Write, reg+8*p,I2C_MEMADD_SIZE_8BIT,buf+8*p,8,0xff);
HAL_Delay(5);
}
if(noun){
HAL_I2C_Mem_Write(&AT24_IIC, AT24_Write, reg+8*count, I2C_MEMADD_SIZE_8BIT, buf+8*count, noun, 0xff);
HAL_Delay(5);
}
}
else{
HAL_I2C_Mem_Write(&AT24_IIC, AT24_Write, reg ,I2C_MEMADD_SIZE_8BIT, buf,len,0xff);
HAL_Delay(5);
}
return 0;
}
uint8_t AT24_Read_Len(uint8_t reg,uint8_t len,uint8_t *buf)
{
HAL_I2C_Mem_Read(&AT24_IIC, AT24_Read, reg, I2C_MEMADD_SIZE_8BIT, buf, len, 0xfff);
HAL_Delay(20);
return 0;
}
main.c主文件(在main()中,while(1)前)
/* USER CODE BEGIN 2 */
//调用printf格式化输出需要重写fputc和fgetc函数,并在target中设置“Use MircoLIB”
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1);
if(AT24_Init(hi2c1)){
printf("AT24C02 connected ERROR!\r\n");
while(1);
}
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0);
printf("AT24C02 connected SUCCESS!\r\n");
uint8_t recv_buff[256];
for (int i = 0; i<256; i++){
AT24_Write_Byte(i,i);
}
printf("AT24C02 transmission SUCCESS!\r\n");
for (int i = 0; i<256; i++){
AT24_Read_Len(0x00, 255 , recv_buff);
}
for (int i = 0; i<256; i++){
printf("0x%02X ",recv_buff[i]);
}
printf("\r\n");
printf("AT24C02 receive SUCCESS!\r\n");
/* USER CODE END 2 */
演示如下
MD,都看到底了,不点个赞?!!
作者:hrilug