I2C通信应用——温湿度传感器AHT20读取室内温湿度

 

一,实验准备阶段

 

1,实验目标

对I2C进行基本的学习认识,并学会通过查阅数据手册来配置AHT20温湿度传感器来测量室内的温湿度,并结合蓝牙通信,将读取到的温湿度发送给移动端。

 

2,I2C的基本认识

I2C 是一种串行、多主从设备的通信协议,广泛用于单片机与传感器、EEPROM、显示屏等外设的短距离通信。以下是其核心机制与实战要点:

一、物理层特性

  1. 双线制总线

    • SCL(时钟线):主机生成时钟,控制数据传输节奏。
    • SDA(数据线):双向传输数据,高电平表示 1,低电平表示 0。
    • 上拉电阻:SCL/SDA 需外接上拉(4.7kΩ~10kΩ),确保总线空闲时为高电平(如 STM32 的 PB6/PB7 默认开漏输出,需外部上拉)。
  2. 设备寻址

    • 7 位地址(主流):共 128 个地址(0x00~0x7F),其中 0x00 为广播地址,0x78~0x7F 保留。
      ▶ 代码中需左移 1 位(如设备地址 0x48 → 发送 0x90(写)/0x91(读))。
    • 10 位地址:扩展寻址范围,较少用。

二、协议层核心时序

1. 起始 / 停止条件

  • Start:SCL 高电平时,SDA 由高→低(主机发起通信)。
  • Stop:SCL 高电平时,SDA 由低→高(主机结束通信)。

    plaintext

    SCL:  ______     ______     ______
          |    |     |    |     |    |
    SDA:  __|____|____|____|____|____|__  Start→Data→Stop
    

2. 数据传输

  • 字节格式:8 位数据 + 1 位 ACK(从机响应)。
    ▶ 高位在前,每个字节后需从机返回 ACK(低电平),否则视为失败(如 STM32 的HAL_I2C_Mem_Read返回HAL_TIMEOUT)。
  • 读 / 写操作
    • 写流程:Start → 设备地址(W) → 寄存器地址 → 数据 → Stop。
    • 读流程:Start → 设备地址(W) → 寄存器地址 → Start → 设备地址(R) → 数据 → NACK → Stop。

 

 

二,初始化配置

 

1,新建工程

点击File,选择STM32 Project,芯片依旧选择经典的STM32F103C8T6型号,工程名为I2C_AHT20。

 

2,引脚选择配置

本期项目需要用到I2C通信和串口通信,所以只需要打开对应的引脚即可。

STM32F103C8T6中一共有两对I2C和三对串口,在这里我选择的是I2C1和USART2,对应的引脚分别是PB6(SCL)PB7(SDA)和PA2(TX)PA3(RX),当然SWD调试端口还是一样要打开。配置好后就是这个样子

 

3,I2C1及USART2参数配置

 

单击左边的connectivity,双击我们选择的I2C1,并将模式选择为I2C

 

之后为I2C1开启中断和DMA请求。

中断有I2C事件中断和I2C错误中断两种,我们全部开启即可,参数保持默认。

 

然后为I2C的发送和接收都添加上DMA请求,参数保持默认。

 

开启中断和DMA的目的是利用DMA来传输数据,减轻CPU的负担,防止系统堵塞。

 

最后就是配置USART2的模式及参数,这里依旧是选择异步传输模式,下面的参数中,只需要将波特率更改为9600Bits/s,其余参数皆保持不变。

 

4,保存配置

 全部配置好后,点击左上方的Save保存配置,然后等待CubeMX为我们生成好初始化代码。

 

 

三,代码实现部分

 

1,项目基本思路

在我们配置AHT20传感器时,会分为很多步骤,为了使项目整体逻辑分明,环环相扣,所以我们可以引入状态机思想,人话就是,可以定义一个变量,当变量为不同的值时,需要做出不同的行为,这样也便于我们对应数据手册的读取流程书写代码。

代码的思路是非常简单的,基本就是分为三步

  1. 根据数据手册读取传感器
  2. 将读取传感器的步骤与状态机一一对应
  3. 将读取到的数据通过蓝牙发送到移动端

 

2,传感器的读取流程 

1,模块化编程思想

由于本次的传感器配置部分代码较多,所以建议单独为这部分另外创建一个.c文件和对应的.h文件,这么做的好处是将代码模块化,使main主函数更加简洁清晰。

所以在写代码前可以如图右键点击Inc文件夹,并选择新建一个.h文件,命名为AHT20.h,.c文件同理创建在Src文件夹下方,并将用于读取传感器的代码写在这里,将用到的函数声明在.h文件中。

 

2,数据手册的获取及认识

之后我们还需要去寻找对应的传感器的数据手册,寻找数据手册的渠道有很多,这里简单分享几个,第一个是我比较常用的,就是嘉立创的立创商城,还有就是半岛小芯网站,搜索对应的模块名称即可将数据手册下载到本地,当然还可以在我们购买器件时找商家获取也是可行的。

而对于一般的数据手册基本都分为这么几部分, 

  1. 基础信息区(快速入门)

  2. 电气与物理层(硬件设计核心)

  3. 功能与协议层(软件 / 固件开发核心)

  4. 应用与可靠性(量产关键)

  5. 附录与索引(高效查阅)

我们在软件编程中需要用到的就是第三点功能与协议部分,这里一般是对寄存器的介绍,功能模块描述,时序图等。

而对于我们这款的AHT20温湿度传感器的数据手册,我们在编程中需要运用到的部分便是传感器读取流程这一点,根据数据手册的步骤一步一步的配置AHT20便可正确使用。

 

3, 代码实现部分

 

1,初始化AHT20

在写代码之前 ,可以将经常要用的数据宏定义一下,这样方便后续的操作,也便于理解。例如我这里就将AHT20传感器的地址宏定义一下,这样之后的aht_address就代表0x70了,代码阅读起来也更直观。

#define aht20_address 0x70

 

按照数据手册流程的第一步,先读取0x71地址上的校准位,然后发送初始化命令

void aht20_Init(){           //初始化状态
	uint8_t mingling[3]={0xBE,0x00,0x08};  //定义初始化命令
	uint8_t Readbyte;

	HAL_Delay(40);
	HAL_I2C_Master_Receive_IT(&hi2c1, aht20_address, &Readbyte, 1);//直接读取地址,然后返回校验位储存在Readbyte中
	if((Readbyte & 0x08) ==0x00){
		HAL_I2C_Master_Transmit_IT(&hi2c1, aht20_address, mingling, 3);//发送初始化命令
	}
}

 

void aht20_transmit(){      //发送测量指令
	static uint8_t receive[3]={0xAC,0x33,0x00};
	HAL_Delay(10);
	HAL_I2C_Master_Transmit_DMA(&hi2c1, aht20_address, receive, 3);
}

 

uint8_t receiveData[7]={0};
void aht20_get(){          //等待数据测量完毕

	HAL_I2C_Master_Receive_DMA(&hi2c1, aht20_address, receiveData, sizeof(receiveData));
}

 

当我们读取到六个字节后,就需要对温湿度进行处理了,查看数据手册中的表可以看到,从receiveData[1]到receiveData[3]的后四位,这些是湿度数据,而 receiveData[3]的前四位到receiveData[5]这些数据是温度数据,所以我们需要将温湿度数据分别合并储存起来。 

 

	uint32_t a,b;
	if((receiveData[0] & 0x80) == 0x00){
	a=((uint32_t)receiveData[3]>>4)+((uint32_t)receiveData[2]<<4)+((uint32_t)receiveData[1]<<12);
	b=((uint32_t)receiveData[5])+((uint32_t)receiveData[4]<<8)+(((uint32_t)receiveData[3] & 0x0F)<<16);

 

之后将前面合并储存的数据通过给出的公式转换即可

void aht20_Read(float *SRH,float *ST){    //读取数据并对温湿度进行转换
	*SRH=(a/1048576.0)*100.0f;
	*ST=(b/1048576.0)*200.0f-50.0;
}

 

然后将所有在aht20.c文件中出现的函数在aht20.h中进行声明

#ifndef INC_AHT20_H_
#define INC_AHT20_H_
#include "i2c.h"


void aht20_Init();
void aht20_transmit();
void aht20_get();
void aht20_Read(float *SRH,float *ST);

#endif /* INC_AHT20_H_ */

 

2, 设置状态机

定义一个变量state来表示状态机的状态,并设置五个不同的状态

extern uint8_t state =0; //状态机状态判断: 0:发送测量命令 1:命令发送中 2 :测量完成  3:读取中 4:读取完成并串口发送数据

再定义两个32位的变量来表示温度和湿度,然后定义一个8位的数组来存储之后要通过串口发送的数据,数组的长度尽量大一些。

  float RH,T;
  char Data[50];

 在main函数里调用初始化AHT20的函数

  aht20_Init();                  //初始化aht20

在while里配置状态机各个状态的函数,并一一对应环环相扣。

  while (1)
  {
	  switch(state){
	  case 0:
		  aht20_transmit();      //发送测量指令
		  state=1;
		  HAL_Delay(10);
		  break;
	  case 2:
		  HAL_Delay(80);
		  aht20_get();           //等待数据测量完毕
		  state=3;
		  break;
	  case 4:
		  aht20_Read(&RH,&T);   //读取数据中并对温湿度进行转换
		  sprintf(Data,"温度:%.1f℃,湿度:%.1f%%\r\n",T,RH);  //将温湿度数据传入Data数组中,并通过串口以一秒间隔发送
		  HAL_UART_Transmit(&huart2, (uint8_t*)Data, strlen(Data), HAL_MAX_DELAY);
		  HAL_Delay(1000);
		  state=0;
		  break;
	  }
  }

 

这里可以注意到没有从1状态进入到2状态,也没有从 3状态进入到4状态的步骤,这是因为我们在写读取AHT20的时候,在aht20_transmit()函数和aht20_get()函数中使用到了

	HAL_I2C_Master_Transmit_DMA(&hi2c1, aht20_address, receive, 3);
	HAL_I2C_Master_Receive_DMA(&hi2c1, aht20_address, receiveData, sizeof(receiveData));

这两个函数分别是发送测试指令和接收测试数据的作用,所以我们必须等待发送或者接受完毕才能进入下一环节的操作,不然可能会出现接收数据不完整导致结果出现不符合预期的现象,因此我们这里要调用两个对应的中断回调函数,来确保只有完全执行这两个函数完毕后才进入下一状态。

回调函数的具体操作如下,注意进入回调函数第一步先判断触发中断源是否匹配!

void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c){  //回调函数,当执行完	HAL_I2C_Master_Transmit_DMA()函数后触发中断跳转过来将state状态机状态变为状态2
	if(hi2c==&hi2c1){
		state=2;
	}
}

void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c){ //回调函数,当执行完HAL_I2C_Master_Receive_DMA()函数后触达中断跳转过来将state状态机状态变为状态3
	if(hi2c==&hi2c1){
		state=4;
	}
}

 

至此,状态机所有的状态都匹配完毕且能够一步步向下运行。

 

3,数据发送移动端

我们将数据通过指针传递到RH和T变量来

float RH,T;
aht20_Read(&RH,&T); 

 然后使用sprintf函数将两个数据写入字符串中,然后使用串口发送函数,将温湿度数据以一秒间隔发送,这里需要的是sprintf的用法,以及下面Data字符串的类型。

sprintf(Data,"温度:%.1f℃,湿度:%.1f%%\r\n",T,RH);  //将温湿度数据传入Data数组中,并通过串口以一秒间隔发送
HAL_UART_Transmit(&huart2, (uint8_t*)Data, strlen(Data), HAL_MAX_DELAY);
HAL_Delay(1000);

sprintf 是 C 语言中将格式化数据写入字符串的核心函数,在嵌入式开发(如 STM32 日志打印、协议数据组包)中高频使用。) 

 由于这里使用了sprintf和strlen两个函数,所以需要在头部声明对应的库

#include <stdio.h>
#include <string.h>
#include "aht20.h"

 

4,运行编译代码 

点击上方小锤子图标运行代码,之后点击Run图标将程序烧入单片机中

 

 

四,硬件连接

 

硬件连接部分比较简单,这里AHT20的四个引脚可以直接插在PB6和PB7位置上,然后GND和VIN通过面包板孔位直接连接即可,也可以使用杜邦线进行连接

 

蓝牙模块的连接直接使用杜邦线,注意TX和RX要与单片机的PA2和PA3反接,然后GND共地即可

 

 

五,实验现象 

打开蓝牙调试app,连接蓝牙模块,注意要将接数据的类型调成文本模式而不是HEX模式,如果此时代码和接线都没问题的话,我们应该就可以接收到以每间隔一秒发出一次的消息。

如何判断温湿度检测的数据是否正确呢,不妨做个简单的小实验,可以将手指轻轻按在AHT20模块上,观察数据的变化,这里我们可以发现,温度在逐渐升高,湿度也一直在上升直至100%(不一定湿度会变成100%,只是博主手汗比较多!!!),实验现象如预料一样,实验圆满完成!

 

 

六,实验小结 

        本次实验运用到了I2C通信和蓝牙通信,其中的知识点也相比前几期的要更丰富些,简单总结下来大概有,I2C通信的基本原理,AHT20温湿度传感器的配置,数据手册的获取及阅读,sprintf函数的使用,状态机概念及应用,可以说是我有史以来写的内容最丰富的一篇,因此可能不能再像前几期一样面面俱到了,有些地方一笔带过实属无奈,否则篇幅过长显得啰嗦,这次的小实验实用性还是很强的,大多模块还有实验都离不开I2C通信和串口通信,所以这一部分我也花了很多心思学习研究,而像AHT20这样的有意思的模块还有很多,小伙伴们不妨在学会了I2C通信后可以考虑玩玩其他类似的模块呢?

        知而不行,是为不知,行而不知,可以致知。希望小伙伴们不光看了,最好还能多动手自己操作操作,这样才能更好地体会到嵌入式有趣的地方!!!

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐