IIC(Inter-Integrated Circuit),是一种双向二线制同步半双工串行总线,采用串行数据线(SDA)和串行时钟线(SCL)实现设备间通信。
芯片配置
以I2C1为例:
- 在左侧选择Connectivity中选择I2C1,Mode选项中I2C下拉中选择I2C即可启用I2C;
- 在右侧的芯片引脚处,可以看到PB6和PB7已经分别被设置为I2C1_SCL和I2C1_SDA;
- 左侧I2C的设置一般保持默认即可。
代码配置
目的:使AHT20温湿度传感器每秒钟返回温湿度信息到上位机。
根据AHT20规格书,我们在新的文件aht20.h和aht20.c中写入aht20的驱动程序:
头文件
在aht20.h中写入包含的头文件以及对函数的声明:
#ifndef __AHT20_H__#define __AHT20_H__
#include "i2c.h"
void AHT20_Init(void);void AHT20_Measure(float *temperature,float *humidity);void AHT20_Print(void);
#endif初始化

根据手册中的初始化信息,我们将其转化为函数AHT20_Init:
#define AHT20_ADDRESS 0x70void AHT20_Init(void){ HAL_Delay(40); uint8_t readBuffer; HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &readBuffer, 1, HAL_MAX_DELAY); if((readBuffer & 0x08) == 0x00){//用 按位与(&) 的操作检测readBuffer第3位是否为1,如果是0,则返回0x00,如果是1,则返回0x08 static uint8_t sendBuffer[3]={0xBE,0x08,0x00}; HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY); }}其中HAL_I2C_Master_Receive和HAL_I2C_Master_Transmit两个函数是I2C通信的重点:
HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, &readBuffer, 1, HAL_MAX_DELAY);函数,用于主机从从机中读取信息:
&hi2c1:使用的i2c句柄,与之前的&huart2类似;AHT20_ADDRESS:从机的地址;&readBuffer:存储变量readBuffer的指针;1:读取的数据的字节数量;HAL_MAX_DELAY:最大等待时间。
同样地,HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY);函数用于主机向从机发送信息:
sendBuffer:要发送的数据的指针;3:要发送的字节数量。
测量并读取数据

我们向从机依次发送 0xAC,0x33,0x00,以开始测量。
在等待75ms后,从从机接收信息,判断第七位是否为1,然后进入数据处理环节。
void AHT20_Measure(float *temperature,float *humidity){ static uint8_t sendBuffer[3]={0xAC,0x33,0x00}; HAL_I2C_Master_Transmit(&hi2c1, AHT20_ADDRESS, sendBuffer, 3, HAL_MAX_DELAY); char *message_measuring="Measuring..."; HAL_UART_Transmit_IT(&huart2,(uint8_t*)message_measuring, strlen(message_measuring)); HAL_Delay(75);
uint8_t readBuffer[6]={0}; HAL_I2C_Master_Receive(&hi2c1, AHT20_ADDRESS, readBuffer, 6, HAL_MAX_DELAY); if((readBuffer[0] & 0x80)==0x00){ ... } return;}数据处理


根据图中信息,可以得知温湿度的信息被拆成的多个字节被返回主机,因此我们先定义data用于拼接返回的数据。
然后用信号转换中的公式为入参*humidity和*temperature赋值。
uint32_t data=0;data=((uint32_t)readBuffer[3]>>4)+((uint32_t)readBuffer[2]<<4)+((uint32_t)readBuffer[1]<<12);*humidity=data*100.0f/(1<<20);
data=((uint32_t)readBuffer[5])+((uint32_t)readBuffer[4]<<8)+(((uint32_t)readBuffer[3] & 0x0F)<<16);*temperature=data*200.0f/(1<<20)-50.0;打印数据
使用函数AHT20_Print通过串口返回测定的数据。
由于STM32 默认关闭sprintf浮点支持,我们需要将浮点数temperature和humidity拆成整数部分和小数部分:
float temperature=-1.0,humidity=-1.0;char message[50];struct { int integer; int floating;}temperature_temp,humidity_temp;然后使用函数AHT20_Measure得到temperature和humidity的值,将其拆分后放入sprintf函数组合成字符串message,最后通过串口返回到上位机。
void AHT20_Print(void){ float temperature=-1.0,humidity=-1.0; char message[50]; struct { int integer; int floating; }temperature_temp,humidity_temp; AHT20_Measure(&temperature,&humidity); temperature_temp.integer=(int)temperature; temperature_temp.floating=(int)((temperature-temperature_temp.integer)*10); humidity_temp.integer=(int)humidity; humidity_temp.floating=(int)((humidity-humidity_temp.integer)*10); sprintf(message, "Temperature:%d.%d,Humidity:%d.%d%%\n\r",temperature_temp.integer,temperature_temp.floating,humidity_temp.integer,humidity_temp.floating); HAL_UART_Transmit(&huart2, (uint8_t*)message,strlen(message), HAL_MAX_DELAY); return;}调用函数
在main.c中,/* USER CODE BEGIN Init */和/* USER CODE END Init */之间,加上我们对AHT20的初始化函数AHT20_Init:
/* USER CODE BEGIN Init */AHT20_Init();/* USER CODE END Init */然后在while(1)死循环中加入AHT20_Print函数,并为其设置延迟:
AHT20_Print();HAL_Delay(1000);全部设置完后,打开串口助手,便可以看到AHT20返回的温湿度信息:

中断与DMA模式
与串口通信类似,I2C通信也有中断模式和DMA模式,他们也都有自己的回调函数,在此不再赘述,可参考先前串口的中断模式和DMA模式。
深入IIC通信协议
IIC通信通过两根数据线进行通信,不过,与TTL串口不同,IIC使用的两条线分别是SDA(Serial Data)和SCL(Serial Clock)。因为只有一根数据线,IIC通信同一时刻只能一端发送,不能同时进行发送和接收两个任务,因此称为半双工通信。
由于同时只能进行一个方向的通信,IIC通信采用了主从模式,即一台设备为主机,其他设备为从机,只能由主机发送信号后,从机再进行响应。
IIC协议支持多设备通信,因此被称为总线协议。在其总线上,每个从机都有其唯一的地址,当需要操作从机时,需要在发送数据的开始发送从机的地址,从机才会做出反应,无关从机则会忽略接下来的信息。
与TTL串口通信的异步通信模式不同,IIC通信协议选择使用同步通信,也就是主机通过SCL发送固定频率的时钟脉冲信号,来作为IIC总线上所有设备统一的时钟源。
开始通信
IIC的数据线一般都有上拉电阻,因此常态下,SDA和SCL都处于高电平。主机要发送IIC通信启动信号时,会先下拉SDA,此时所有从机准备接受指令。

发送数据
传输信息时,主机会在SCL上产生恒定的时钟脉冲信号,主机和从机通过SCL上的脉冲信号实现同步对SDA的读写:
- 当SCL处于低电平时:主机设置SDA的电平;
- 当SCL处于高电平时:从机读取SDA的电平,即刚刚主机设置的SDA的电平。

每发送完一个字节后,从机都需要回复ACK信号以确定自己已经接受到信息:
- 在SCL处于低电平时,从机设置SDA为低电平。
返回数据
返回数据时,与发送数据时类似,不过这时SDA的电平设置由从机完成:
- 当SCL处于低电平时:从机设置SDA的电平;
- 当SCL处于高电平时:主机读取SDA的电平,即刚刚从机设置的SDA的电平。

每发送完一个字节后,同样地需要一个ACK信号,不过,这一次由主机回复ACK信号:
- 当SCL处于低电平时,主机设置SDA为低电平。
结束通信
当SCL处于高电平时,主机将会拉高SDA,以结束通信。

关于从机地址
一般地,IIC通信使用7位从机地址码,当主机发送地址时,从机地址码会左移一位,并在尾部添加0或者1,IIC协议规定:
- 如果主机发起通信是为了写入从机,则末尾置0,例如
HAL_I2C_Master_Transmit; - 如果主机发起通信是为了读取从机,则末尾置1,例如
HAL_I2C_Master_Receive。
例如,AHT20的从机地址是0x38(0111000),因此主机读取信息时发送地址,会发送0x71(01110001)
置0和置1的过程,HAL库已经帮我们自动执行了,因此,使用HAL库填入从机地址时,只用填入置0情况下从机地址即可。
总结
相关函数
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);//主机接收从机信息HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);//主机向从机发送信息
HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);//主机接收从机信息(中断模式)HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);//主机向从机发送信息(中断模式)
HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);//主机接收从机信息(DMA模式)HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);//主机向从机发送信息(DMA模式)
HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c);//发送结束回调函数HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c);//接收结束回调函数I2C通信协议
- 使用SDA和SCL两条数据线进行通信。
- 半双工通信:可以进行发送和接收,但是不能同时进行;
- 主从模式:由主机和从机组成;
- 总线协议:一个主机可以连接多个从机;
- 同步通讯:主机在SCL上产生规律的脉冲时钟信号,实现各设备共用一个时钟信号。
部分信息可能已经过时





