)
一、I2C 通信基础我们之前用的 UART 串口为异步全双工通信TTL 只是电平标准不要混淆通信前约定波特率收发双方依靠各自内部时钟采样对波特率误差敏感。而 I2C 由主机产生 SCL 时钟从机不需要高精度独立晶振即可通信但主机必须输出稳定 SCL 时钟。IIC 包含 1 条 SDA 数据线、1 条 SCL 时钟线、GND自带时钟线属于同步通信主从架构。SDA 单根线分时收发是半双工。I2C 总线空闲时SDA、SCL 通过上拉电阻保持高电平主机拉低 SDA 产生起始信号。通信时主机在 SCL 低电平阶段放置 SDA 数据SCL 高电平阶段从机读取 SDA 数据。通信流程简述主机发送带读写位的从机地址AHT20 左移后地址 0x70从机收到地址后拉低 SDA回复 ACK 应答。二、CubeIDE 工程配置先配置 UART 等会用于与电脑通信的外设先在 System Core 里的 SYS 选择 Serial Wire在 Connectivity 里打开 UART1/2模式选 Asynchronous异步。再打开 Connectivity 的 I2C1/2选择 I2C 模式。三、编写传感器驱动文件先勾选一下为每个外设生成一对 .c/.h 文件CtrlS 保存并生成代码。在 inc 文件夹新建头文件在 src 文件夹新建 c 文件。aht20.h 文件中需要引用一下 i2c.h 文件这也就是我们之前设置的目的然后是两个函数声明。#ifndef INC_AHT20_H_ #define INC_AHT20_H_ #include i2c.h void AHT20_Init(); void AHT20_Read(floatTemperature,floatHumidity); #endif /* INC_AHT20_H_ */传感器测量数据结构。aht20.c 文件附注释。#include aht20.h // AHT20 I2C写就是0x70,是读就是0x71,但是代码会自动操作我们只需要对函数输入一个0x70地址就好 #define AHT20_ADDRESS 0x70 /** brief AHT20传感器初始化函数上电后检查并执行校准 retval 无 */ void AHT20_Init() { uint8_t readbuffer; // 上电延时至少40ms等待AHT20内部电源稳定 HAL_Delay(40); // 读取AHT20状态字 HAL_I2C_Master_Receive(hi2c1,AHT20_ADDRESS,readbuffer,1, HAL_MAX_DELAY); // 判断状态字bit3(0x08)bit30代表未完成内部校准需要发送校准命令 if((readbuffer0x08)0x00) { // AHT20校准命令0xBE,0x08,0x00 uint8_t sendbuffer[3]{0xBE,0x08,0x00}; // I2C主机发送校准指令 HAL_I2C_Master_Transmit(hi2c1,AHT20_ADDRESS,sendbuffer,3, HAL_MAX_DELAY); } } /** brief 读取AHT20温湿度 param Temperature: float指针存储读取到的温度(℃) param Humidity: float指针存储读取到的湿度(%RH) retval 无 note 若传感器忙不会更新温湿度数值 / void AHT20_Read(floatTemperature,float*Humidity) { // AHT20触发测量命令 0xAC 0x33 0x00 uint8_t sendbuffer[3]{0xAC,0x33,0x00}; // 存放读取回来的6字节数据状态湿度20bit温度20bitCRC uint8_t readbuffer[6]; // I2C发送触发测量指令 HAL_I2C_Master_Transmit(hi2c1,AHT20_ADDRESS,sendbuffer,3, HAL_MAX_DELAY); // 等待75ms测量完成 HAL_Delay(75); // 连续读取6字节数据 HAL_I2C_Master_Receive(hi2c1,AHT20_ADDRESS,readbuffer,6, HAL_MAX_DELAY); // 判断状态字bit7(0x80)bit70 代表测量完成bit71为传感器忙 if((readbuffer[0]0x80)0x00) { uint32_t data0; // 拼接20位湿度原始数据 // readbuffer[1]湿度高8bitreadbuffer[2]湿度中8bitreadbuffer[3]高4bit为湿度低4bit data((uint32_t)readbuffer[1]12)((uint32_t)readbuffer[2]4)((uint32_t)readbuffer[3]4); // 湿度计算公式RH raw / 2^20 *100%RH Humiditydata100.0f/(120); // 拼接20位温度原始数据 // readbuffer[3]低4bit readbuffer[4]8bit readbuffer[5]8bit data(((uint32_t)readbuffer[3]amp;0x0F)lt;lt;16)((uint32_t)readbuffer[4]lt;lt;8)((uint32_t)readbuffer[5]); // 温度计算公式T raw / 2^20 *200 -50 (℃) *Temperaturedata*200.0f/(1lt;lt;20)-50; } }main.c 文件。int main(void) { //这里是需要修改和添加的部分不包括自动生成代码 AHT20_Init(); float Temperature 0.0f, Humidity 0.0f; char message[50]; while (1) { AHT20_Read(Temperature,Humidity); sprintf(message,温度: %.1f°C,湿度 %.1f %%\r\n,Temperature,Humidity); HAL_UART_Transmit(huart2, (uint8_t*)message, strlen(message), HAL_MAX_DELAY); HAL_Delay(1000); } }四、中断状态机实现温湿度测量先配置 UART 等会用于与电脑通信的外设先在 System Core 里的 SYS 选择 Serial Wire在 Connectivity 里打开 UART1/2模式选 Asynchronous异步。再打开 Connectivity 的 I2C1/2选择 I2C 模式并在下面勾选打开两个中断。我们前面的 AHT20_Read 函数中有一个发送一个接收我们把他们都加上 _IT 后缀由于中断模式是非阻塞的所以又要用到我们的 callback 函数在每次中断结束后再进行后面的程序。但是问题来了我们之前的 AHT20_Read 函数是一个整体而我们有两次中断要调用两次回调函数所以我们必须把这个大函数拆分为几个小函数把两个中断流程切割开。首先 AHT20_Measure 中的 sendbuffer 要改为静态变量因为 HAL_I2C_Master_Transmit_IT(hi2c1,AHT20_ADDRESS,sendbuffer,3); 开启发送之后就结束了函数这时 sendbuffer 在栈内会被马上清空也就是还没开始发送我们的发送数据就已经被清除。因为后面两个函数都要用 readbuffer所以改为全局变量顺便初始化一下uint8_t readbuffer[6]{0};其它流程不变只是拆开于是我们的 aht20.c 文件就变成了测量、接收、温湿度测算三个函数然后 aht20.h 的声明也跟着改一下就好了。#include aht20.h // AHT20 I2C地址7bit地址0x38HAL库需要左移1位得到0x70 #define AHT20_ADDRESS 0x70 // 全局缓冲区存放AHT20读取回来的6字节原始数据 uint8_t readbuffer[6]{0}; /** brief AHT20传感器初始化 retval 无 */ void AHT20_Init() { uint8_t status; // 原代码局部变量readbuffer与全局数组重名已改名 HAL_Delay(40); // 上电等待AHT20稳定 // 读取状态字 HAL_I2C_Master_Receive(hi2c1,AHT20_ADDRESS,status,1, HAL_MAX_DELAY); // 判断校准位未校准则发送校准命令 if((status 0x08)0x00) { uint8_t sendbuffer[3]{0xBE,0x08,0x00}; HAL_I2C_Master_Transmit(hi2c1,AHT20_ADDRESS,sendbuffer,3, HAL_MAX_DELAY); } } /** brief 发送测量指令使用I2C中断非阻塞发送 retval 无 */ void AHT20_Measure(){ // AHT20触发测量命令 static uint8_t sendbuffer[3]{0xAC,0x33,0x00}; HAL_I2C_Master_Transmit_IT(hi2c1,AHT20_ADDRESS,sendbuffer,3); } /** brief 等待测量完成读取6字节原始数据I2C中断接收 retval 无 */ void AHT20_GET(){ HAL_Delay(75); // AHT20测量转换需要至少75ms HAL_I2C_Master_Receive_IT(hi2c1,AHT20_ADDRESS,readbuffer,6); } /** brief 解析AHT20原始数据计算温湿度 param Temperature: 浮点指针输出温度值 param Humidity: 浮点指针输出湿度值 retval 无 / void AHT20_Analysis(floatTemperature,float*Humidity){ // bit7为0表示测量数据有效 if((readbuffer[0] 0x80)0x00) { uint32_t data0; // 提取湿度20bit原始数据 data((uint32_t)readbuffer[1]12)((uint32_t)readbuffer[2]4)((uint32_t)readbuffer[3]4); Humiditydata100.0f/(120); // 换算成%RH湿度 // 提取温度20bit原始数据 data(((uint32_t)readbuffer[3]amp;0x0F)lt;lt;16)((uint32_t)readbuffer[4]lt;lt;8)((uint32_t)readbuffer[5]); *Temperaturedata*200.0f/(1lt;lt;20)-50; // 换算成摄氏度 } }然后在主函数中写一个状态机在主函数 while 循环置 0、1、3 状态写状态机代码。while (1) { // 状态机依靠I2C传输完成回调修改aht20State分步完成AHT20采集 if(aht20State0){ // 状态0空闲发起AHT20测量命令I2C中断发送 AHT20_Measure(); aht20State1; } else if(aht20State2){ // 状态2I2C发送测量命令完成开始读取传感器原始数据 AHT20_GET(); aht20State3; } else if(aht20State4){ // 状态4I2C接收6字节原始数据完成解析温湿度 AHT20_Analysis(Temperature,Humidity); // 格式化字符串拼接温湿度信息 sprintf(message,温度: %.1f°C,湿度 %.1f %%\r\n,Temperature,Humidity); // 通过串口2发送采集结果 HAL_UART_Transmit(huart2, (uint8_t*)message, strlen(message), HAL_MAX_DELAY); HAL_Delay(1000); // 采集间隔1s aht20State0; // 重置状态进入下一轮采集循环 } }在这里找到回调函数的函数头。在 i2c.c 文件中抄写回调函数头在回调函数中置 2、4 状态。void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c){ if (hi2c hi2c1){ aht20State2; } } void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c){ if (hi2c hi2c1){ aht20State4; } }注意由于我们的 uint8_t aht20State0; 状态标志要在 i2c.c 文件中使用所以要在 main.h 文件中通过 extern uint8_t aht20State; 声明为全局变量而自动生成的 i2c.c 文件会自动 include main.h无需我们再次 include至此就实现了通过 IIC 的中断测量温湿度。