STM32 IIC协议采集AHT20温湿度
引言:
在现代嵌入式系统中,I2C(Inter-Integrated Circuit)通信协议是一种常见的串行通信协议,用于连接微控制器与各种外设设备,例如传感器、存储器等。在STM32F103微控制器上,我们可以通过硬件I2C接口或者软件I2C来实现与外设的通信。软件I2C和硬件I2C在实现方式上略有不同,而其中的选择取决于具体的应用场景和需求。
~
软件I2C是通过在通用I/O引脚上模拟I2C协议的方式实现的,它适用于一些特殊场景,例如在硬件资源受限的情况下或者在需要与不同电平设备通信时。相比之下,硬件I2C是由硬件模块直接支持的,更加稳定和高效,适用于一般的I2C通信需求。
~
在本任务中,我们将学习如何使用STM32F103微控制器,结合I2C协议,通过硬件I2C接口进行AHT20温湿度传感器的数据采集。通过阅读AHT20数据手册,我们将实现每隔2秒钟采集一次温湿度数据,并通过串口将数据发送到上位机(Windows 10),以便实时监测和分析环境温湿度变化。这个实例将涉及到软件I2C和硬件I2C的选择,以及串口通信的配置和数据传输,为进一步深入嵌入式系统的开发提供了有益的经验。
~
希望你在本次学习过后,能够有一定的收获!!!绵延万万里的脊梁,撑起家国傲骨。——苏烈
冲啊!!!! ╭ ( `∀´ )╯ ╰ ( ’ ’ )╮
文章目录
一、AHT20介绍
AHT20是国内奥松生成的I2C接口的MEMS温湿度传感器,ADC位数为20Bit,具有体积小、精度高、成本低等优点。相较于AHT10,最显著的变化是体积由 541.6mm,缩小到 331.0mm。相对湿度精度 RH=±2%,温度精度 T=±0.3°C。相对湿度测量范围 RH=0~100%,温度测量范围 T=-40~85°C。从数据手册上看,AHT10/15/20只是供电电压不同,其他参数没有什么不同,其中AHT15具有聚四氟乙烯防水防尘膜,允许传感器在恶劣环境条件下使用(如喷淋水和高接触灰尘)。
由于AHT10/15/20 具有国产化、体积小、精度高、成本低等特点,可以替代 DHT11/DHT12/AM2320/SHT20/SHT30,单芯片价格在¥2~3,体积小巧很轻松嵌入到产品上。
1. 基本参数
2. 管脚定义、参考电路
- AHT20 - IC管脚定义:
- AHT20 - 参考电路:
3. I2C时序特性
- I2C时序特性:(支持标准100KHz,高速400KHz)
4. 应用场景
它广泛应用于暖通空调 、除湿器、测试及检测设备、消费品、汽车 、自动控制、数据记录器、气象站、家电、湿度调节、医疗及其他相关湿度检测控制等领域。
二、IIC 介绍
1.IIC简介
I2C(IIC)属于两线式串行总线,由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线,属于一主多从(一个主设备(Master),多个从设备(Slave))的总线结构,总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备。
物理I2C接口有两根双向线,**串行时钟线(SCL)和串行数据线(SDA)**组成,可用于发送和接收数据,但是通信都是由主设备发起,从设备被动响应,实现数据的传输。
2. IIC 一般通信过程
一. 主设备给从设备发送/写入数据:
- 主设备发送起始(START)信号
- 主设备发送设备地址到从设备
- 等待从设备响应(ACK)
- 主设备发送数据到从设备,一般发送的每个字节数据后会跟着等待接收来自从设备的响应(ACK)
- 数据发送完毕,主设备发送停止(STOP)信号终止传输
二. 主设备从从设备接收/读取数据
设备发送起始(START)信号
主设备发送设备地址到从设备
等待从设备响应(ACK)
主设备接收来自从设备的数据,一般接收的每个字节数据后会跟着向从设备发送一个响应(ACK)
一般接收到最后一个数据后会发送一个无效响应(NACK),然后主设备发送停止(STOP)信号终止传输
注:具体通信过程需视具体时序图而定
3.I2C通信的实现
一. 使用I2C控制器实现
就是使用芯片上的I2C外设,也就是硬件I2C,它有相应的I2C驱动电路,有专用的IIC引脚,效率更高,写代码会相对简单,只要调用I2C的控制函数即可,不需要用代码去控制SCL、SDA的各种高低电平变化来实现I2C协议,只需要将I2C协议中的可变部分(如:从设备地址、传输数据等等)通过函数传参给控制器,控制器自动按照I2C协议实现传输,但是如果出现问题,就只能通过示波器看波形找问题。
二. 使用GPIO通过软件模拟实现
软件模拟I2C比较重要,因为软件模拟的整个流程比较清晰,哪里出来bug,很快能找到问题,模拟一遍会对I2C通信协议更加熟悉。
如果芯片上没有IIC控制器,或者控制接口不够用了,通过使用任意IO口去模拟实现IIC通信协议,手动写代码去控制IO口的电平变化,模拟IIC协议的时序,实现IIC的信号和数据传输,下面会讲到根据通信协议如何用软件去模拟。
4.I2C通信协议
IIC总线协议无非就是几样东西:起始信号、停止信号、应答信号、以及数据有效性。
一. 空闲状态
时钟线(SCL)和数据线(SDA)接上拉电阻,默认高电平,表示总线是空闲状态。
二. 从设备地址
从设备地址用来区分总线上不同的从设备,一般发送从设备地址的时候会在最低位加上读/写信号,比如设备地址为0x50,0表示读,1表示写,则读数据就会发送0x50,写数据就会发送0x51。
三. 起始(START)信号
I2C通信的起始信号由主设备发起,SCL保持高电平,SDA由高电平跳变到低电平。
四. 停止(STOP)信号
I2C通信的停止信号由主设备终止,SCL保持高电平,SDA由低电平跳变到高电平。
五. 数据有效性
I2C总线进行数据传送时,在SCL的每个时钟脉冲期间传输一个数据位,时钟信号SCL为高电平期间,数据线SDA上的数据必须保持稳定,只有在时钟线SCL上的信号为低电平期间,数据线SDA上的高电平或低电平状态才允许变化,因为当SCL是高电平时,数据线SDA的变化被规定为控制命令(START或STOP,也就是前面的起始信号和停止信号)。
六. 应答信号(ACK:有效应答,NACK:无效应答)
接收端收到有效数据后向对方响应的信号,发送端每发送一个字节(8位)数据,在第9个时钟周期释放数据线去接收对方的应答。
当SDA是低电平为有效应答(ACK),表示对方接收成功;
当SDA是高电平为无效应答(NACK),表示对方没有接收成功。
5. 软件IIC
软件I2C(Software I2C)是一种通过软件实现的I2C通信协议,通常用于在没有硬件I2C支持或者需要额外的通信控制时。在一些嵌入式系统中,特别是一些小型或资源受限的系统,可能没有专门的硬件模块来支持I2C通信,因此开发者需要通过软件来模拟I2C协议的功能。
软件I2C的实现通常涉及两个通用I/O(General Purpose Input/Output,GPIO)引脚,一个用于模拟SCL(时钟线),另一个用于模拟SDA(数据线)。通过对这两个引脚进行适当的控制,可以模拟I2C的起始、停止、读取和写入等操作。
尽管软件I2C在一些特定的场景中是一种有效的解决方案,但与硬件I2C相比,它通常具有较低的速度和较大的时序不确定性。因此,对于对通信速度和时序精度有更高要求的应用,硬件I2C通常是更好的选择。
总体而言,软件I2C是一种适用于资源受限环境或者需要在没有硬件支持的情况下进行I2C通信的解决方案。
6. 硬件IIC
硬件I2C(Hardware I2C)是一种利用专门的硬件模块实现的I2C通信协议。在微控制器或其他嵌入式系统中,通常会集成硬件I2C模块,这些模块具有专门设计的电路和寄存器,可直接支持I2C通信的各个方面。
硬件I2C模块通常包括以下关键功能:
-
SCL和SDA线的硬件控制: 专门的硬件电路用于生成和控制时钟线(SCL)和数据线(SDA)。这消除了在软件I2C中手动控制这些线的需求,提高了通信的精确性和效率。
-
时序控制: 硬件I2C模块内部具有时序生成和检测电路,确保通信协议的时序满足I2C标准。这有助于保持通信的稳定性和可靠性。
-
寄存器设置: 通过配置相关寄存器,可以轻松设置硬件I2C的工作参数,如通信速率、地址等。
-
中断支持: 很多硬件I2C模块支持中断,使得在数据传输完成或发生错误时能够及时响应,提高系统的响应性。
-
多主机支持: 一些硬件I2C模块支持多主机模式,允许多个主机设备在同一总线上进行通信。
由于硬件I2C直接在硬件层面实现了协议,相比软件I2C,它通常具有更高的通信速度和更稳定的性能。因此,在大多数情况下,当系统集成了硬件I2C模块时,优先选择使用硬件I2C以获得更好的性能和可靠性。
四、工程建立
CubeMX配置
基本的配置在我之前的文章中有,这里我们直接跳到对应端口的配置
-
配置对应的端口
-
配置端口模式
函数撰写
AHT20.c文件
/*******************************************/
/*@版权所有:广州奥松电子有限公司 */
/*@作者:温湿度传感器事业部 */
/*@版本:V1.2 */
/*******************************************/
// #include "main.h"
#include "AHT20-21_DEMO_V1_3.h"
#include "gpio.h"
// PB13 SCl PB14 SDA
void Delay_N10us(uint32_t t) // 延时函数
{
uint32_t k;
while (t--)
{
for (k = 0; k < 2; k++)
; // 110
}
}
void SensorDelay_us(uint32_t t) // 延时函数
{
for (t = t - 2; t > 0; t--)
{
Delay_N10us(1);
}
}
void Delay_4us(void) // 延时函数
{
Delay_N10us(1);
Delay_N10us(1);
Delay_N10us(1);
Delay_N10us(1);
}
void Delay_5us(void) // 延时函数
{
Delay_N10us(1);
Delay_N10us(1);
Delay_N10us(1);
Delay_N10us(1);
Delay_N10us(1);
}
void Delay_1ms(uint32_t t) // 延时函数
{
while (t--)
{
SensorDelay_us(1000); // 延时1ms
}
}
void AHT20_Clock_Init(void) // 延时函数
{
// RCC_APB2PeriphClockCmd(CC_APB2Periph_GPIOB, ENABLE);
}
void SDA_Pin_Output_High(void) // 将PB14配置为输出 , 并设置为高电平, PB14作为I2C的SDA
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_SET);
}
void SDA_Pin_Output_Low(void) // 将PB14配置为输出 并设置为低电平
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_14, GPIO_PIN_RESET);
}
void SDA_Pin_IN_FLOATING(void) // SDA配置为浮空输入
{
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 浮空
GPIO_InitStruct.Pin = GPIO_PIN_14;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
void SCL_Pin_Output_High(void) // SCL输出高电平,PB13作为I2C的SCL
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);
}
void SCL_Pin_Output_Low(void) // SCL输出低电平
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
}
void Init_I2C_Sensor_Port(void) // 初始化I2C接口,输出为高电平
{
// GPIO_InitTypeDef GPIO_InitStruct;
// GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
// GPIO_InitStruct.Pin = GPIO_PIN_14;
// GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
// HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
// GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
// GPIO_InitStruct.Pin = GPIO_PIN_13;
// GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
// HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);
}
void I2C_Start(void) // I2C主机发送START信号
{
SDA_Pin_Output_High();
SensorDelay_us(8);
SCL_Pin_Output_High();
SensorDelay_us(8);
SDA_Pin_Output_Low();
SensorDelay_us(8);
SCL_Pin_Output_Low();
SensorDelay_us(8);
}
void AHT20_WR_Byte(uint8_t Byte) // 往AHT20写一个字节
{
uint8_t Data, N, i;
Data = Byte;
i = 0x80;
for (N = 0; N < 8; N++)
{
SCL_Pin_Output_Low();
Delay_4us();
if (i & Data)
{
SDA_Pin_Output_High();
}
else
{
SDA_Pin_Output_Low();
}
SCL_Pin_Output_High();
Delay_4us();
Data <<= 1;
}
SCL_Pin_Output_Low();
SensorDelay_us(8);
SDA_Pin_IN_FLOATING();
SensorDelay_us(8);
}
uint8_t AHT20_RD_Byte(void) // 从AHT20读取一个字节
{
uint8_t Byte, i, a;
Byte = 0;
SCL_Pin_Output_Low();
SDA_Pin_IN_FLOATING();
SensorDelay_us(8);
for (i = 0; i < 8; i++)
{
SCL_Pin_Output_High();
Delay_5us();
a = 0;
// if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_15)) a=1;
if (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14))
a = 1;
Byte = (Byte << 1) | a;
// SCL_Pin_Output_Low();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_RESET);
Delay_5us();
}
SDA_Pin_IN_FLOATING();
SensorDelay_us(8);
return Byte;
}
uint8_t Receive_ACK(void) // 看AHT20是否有回复ACK
{
uint16_t CNT;
CNT = 0;
SCL_Pin_Output_Low();
SDA_Pin_IN_FLOATING();
SensorDelay_us(8);
SCL_Pin_Output_High();
SensorDelay_us(8);
while ((HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_14)) && CNT < 100)
CNT++;
if (CNT == 100)
{
return 0;
}
SCL_Pin_Output_Low();
SensorDelay_us(8);
return 1;
}
void Send_ACK(void) // 主机回复ACK信号
{
SCL_Pin_Output_Low();
SensorDelay_us(8);
SDA_Pin_Output_Low();
SensorDelay_us(8);
SCL_Pin_Output_High();
SensorDelay_us(8);
SCL_Pin_Output_Low();
SensorDelay_us(8);
SDA_Pin_IN_FLOATING();
SensorDelay_us(8);
}
void Send_NOT_ACK(void) // 主机不回复ACK
{
SCL_Pin_Output_Low();
SensorDelay_us(8);
SDA_Pin_Output_High();
SensorDelay_us(8);
SCL_Pin_Output_High();
SensorDelay_us(8);
SCL_Pin_Output_Low();
SensorDelay_us(8);
SDA_Pin_Output_Low();
SensorDelay_us(8);
}
void Stop_I2C(void) // 一条协议结束
{
SDA_Pin_Output_Low();
SensorDelay_us(8);
SCL_Pin_Output_High();
SensorDelay_us(8);
SDA_Pin_Output_High();
SensorDelay_us(8);
}
uint8_t AHT20_Read_Status(void) // 读取AHT20的状态寄存器
{
uint8_t Byte_first;
I2C_Start();
AHT20_WR_Byte(0x71);
Receive_ACK();
Byte_first = AHT20_RD_Byte();
Send_NOT_ACK();
Stop_I2C();
return Byte_first;
}
uint8_t AHT20_Read_Cal_Enable(void) // 查询cal enable位有没有使能
{
uint8_t val = 0; // ret = 0,
val = AHT20_Read_Status();
if ((val & 0x68) == 0x08)
return 1;
else
return 0;
}
void AHT20_SendAC(void) // 向AHT20发送AC命令
{
I2C_Start();
AHT20_WR_Byte(0x70);
Receive_ACK();
AHT20_WR_Byte(0xac); // 0xAC采集命令
Receive_ACK();
AHT20_WR_Byte(0x33);
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
Stop_I2C();
}
// CRC校验类型:CRC8/MAXIM
// 多项式:X8+X5+X4+1
// Poly:0011 0001 0x31
// 高位放到后面就变成 1000 1100 0x8c
// C现实代码:
uint8_t Calc_CRC8(uint8_t *message, uint8_t Num)
{
uint8_t i;
uint8_t byte;
uint8_t crc = 0xFF;
for (byte = 0; byte < Num; byte++)
{
crc ^= (message[byte]);
for (i = 8; i > 0; --i)
{
if (crc & 0x80)
crc = (crc << 1) ^ 0x31;
else
crc = (crc << 1);
}
}
return crc;
}
void AHT20_Read_CTdata(uint32_t *ct) // 没有CRC校验,直接读取AHT20的温度和湿度数据
{
volatile uint8_t Byte_1th = 0;
volatile uint8_t Byte_2th = 0;
volatile uint8_t Byte_3th = 0;
volatile uint8_t Byte_4th = 0;
volatile uint8_t Byte_5th = 0;
volatile uint8_t Byte_6th = 0;
uint32_t RetuData = 0;
uint16_t cnt = 0;
AHT20_SendAC(); // 向AHT10发送AC命令
Delay_1ms(80); // 延时80ms左右
cnt = 0;
while (((AHT20_Read_Status() & 0x80) == 0x80)) // 直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
{
SensorDelay_us(1508);
if (cnt++ >= 100)
{
break;
}
}
I2C_Start();
AHT20_WR_Byte(0x71);
Receive_ACK();
Byte_1th = AHT20_RD_Byte(); // 状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
Send_ACK();
Byte_2th = AHT20_RD_Byte(); // 湿度
Send_ACK();
Byte_3th = AHT20_RD_Byte(); // 湿度
Send_ACK();
Byte_4th = AHT20_RD_Byte(); // 湿度/温度
Send_ACK();
Byte_5th = AHT20_RD_Byte(); // 温度
Send_ACK();
Byte_6th = AHT20_RD_Byte(); // 温度
Send_NOT_ACK();
Stop_I2C();
RetuData = (RetuData | Byte_2th) << 8;
RetuData = (RetuData | Byte_3th) << 8;
RetuData = (RetuData | Byte_4th);
RetuData = RetuData >> 4;
ct[0] = RetuData; // 湿度
RetuData = 0;
RetuData = (RetuData | Byte_4th) << 8;
RetuData = (RetuData | Byte_5th) << 8;
RetuData = (RetuData | Byte_6th);
RetuData = RetuData & 0xfffff;
ct[1] = RetuData; // 温度
}
void AHT20_Read_CTdata_crc(uint32_t *ct) // CRC校验后,读取AHT20的温度和湿度数据
{
volatile uint8_t Byte_1th = 0;
volatile uint8_t Byte_2th = 0;
volatile uint8_t Byte_3th = 0;
volatile uint8_t Byte_4th = 0;
volatile uint8_t Byte_5th = 0;
volatile uint8_t Byte_6th = 0;
volatile uint8_t Byte_7th = 0;
uint32_t RetuData = 0;
uint16_t cnt = 0;
// uint8_t CRCDATA=0;
uint8_t CTDATA[6] = {0}; // 用于CRC传递数组
AHT20_SendAC(); // 向AHT10发送AC命令
Delay_1ms(80); // 延时80ms左右
cnt = 0;
while (((AHT20_Read_Status() & 0x80) == 0x80)) // 直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
{
SensorDelay_us(1508);
if (cnt++ >= 100)
{
break;
}
}
I2C_Start();
AHT20_WR_Byte(0x71);
Receive_ACK();
CTDATA[0] = Byte_1th = AHT20_RD_Byte(); // 状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
Send_ACK();
CTDATA[1] = Byte_2th = AHT20_RD_Byte(); // 湿度
Send_ACK();
CTDATA[2] = Byte_3th = AHT20_RD_Byte(); // 湿度
Send_ACK();
CTDATA[3] = Byte_4th = AHT20_RD_Byte(); // 湿度/温度
Send_ACK();
CTDATA[4] = Byte_5th = AHT20_RD_Byte(); // 温度
Send_ACK();
CTDATA[5] = Byte_6th = AHT20_RD_Byte(); // 温度
Send_ACK();
Byte_7th = AHT20_RD_Byte(); // CRC数据
Send_NOT_ACK(); // 注意: 最后是发送NAK
Stop_I2C();
if (Calc_CRC8(CTDATA, 6) == Byte_7th)
{
RetuData = (RetuData | Byte_2th) << 8;
RetuData = (RetuData | Byte_3th) << 8;
RetuData = (RetuData | Byte_4th);
RetuData = RetuData >> 4;
ct[0] = RetuData; // 湿度
RetuData = 0;
RetuData = (RetuData | Byte_4th) << 8;
RetuData = (RetuData | Byte_5th) << 8;
RetuData = (RetuData | Byte_6th);
RetuData = RetuData & 0xfffff;
ct[1] = RetuData; // 温度
}
else
{
ct[0] = 0x00;
ct[1] = 0x00; // 校验错误返回值,客户可以根据自己需要更改
} // CRC数据
}
void AHT20_Init(void) // 初始化AHT20
{
Init_I2C_Sensor_Port();
I2C_Start();
AHT20_WR_Byte(0x70);
Receive_ACK();
AHT20_WR_Byte(0xa8); // 0xA8进入NOR工作模式
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
Stop_I2C();
Delay_1ms(10); // 延时10ms左右
I2C_Start();
AHT20_WR_Byte(0x70);
Receive_ACK();
AHT20_WR_Byte(0xbe); // 0xBE初始化命令,AHT20的初始化命令是0xBE, AHT10的初始化命令是0xE1
Receive_ACK();
AHT20_WR_Byte(0x08); // 相关寄存器bit[3]置1,为校准输出
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
Stop_I2C();
Delay_1ms(10); // 延时10ms左右
}
void JH_Reset_REG(uint8_t addr)
{
uint8_t Byte_first, Byte_second, Byte_third;
I2C_Start();
AHT20_WR_Byte(0x70); // 原来是0x70
Receive_ACK();
AHT20_WR_Byte(addr);
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
AHT20_WR_Byte(0x00);
Receive_ACK();
Stop_I2C();
Delay_1ms(5); // 延时5ms左右
I2C_Start();
AHT20_WR_Byte(0x71); //
Receive_ACK();
Byte_first = AHT20_RD_Byte();
Send_ACK();
Byte_second = AHT20_RD_Byte();
Send_ACK();
Byte_third = AHT20_RD_Byte();
Send_NOT_ACK();
Stop_I2C();
Delay_1ms(10); // 延时10ms左右
I2C_Start();
AHT20_WR_Byte(0x70); ///
Receive_ACK();
AHT20_WR_Byte(0xB0 | addr); // 寄存器命令
Receive_ACK();
AHT20_WR_Byte(Byte_second);
Receive_ACK();
AHT20_WR_Byte(Byte_third);
Receive_ACK();
Stop_I2C();
Byte_second = 0x00;
Byte_third = 0x00;
}
void AHT20_Start_Init(void)
{
JH_Reset_REG(0x1b);
JH_Reset_REG(0x1c);
JH_Reset_REG(0x1e);
}
AHT20.h文件
#ifndef _AHT20_DEMO_
#define _AHT20_DEMO_
#include "main.h"
void Delay_N10us(uint32_t t); // 延时函数
void SensorDelay_us(uint32_t t); // 延时函数
void Delay_4us(void); // 延时函数
void Delay_5us(void); // 延时函数
void Delay_1ms(uint32_t t);
void AHT20_Clock_Init(void); // 延时函数
void SDA_Pin_Output_High(void); // 将PB15配置为输出 , 并设置为高电平, PB15作为I2C的SDA
void SDA_Pin_Output_Low(void); // 将P15配置为输出 并设置为低电平
void SDA_Pin_IN_FLOATING(void); // SDA配置为浮空输入
void SCL_Pin_Output_High(void); // SCL输出高电平,P14作为I2C的SCL
void SCL_Pin_Output_Low(void); // SCL输出低电平
void Init_I2C_Sensor_Port(void); // 初始化I2C接口,输出为高电平
void I2C_Start(void); // I2C主机发送START信号
void AHT20_WR_Byte(uint8_t Byte); // 往AHT20写一个字节
uint8_t AHT20_RD_Byte(void); // 从AHT20读取一个字节
uint8_t Receive_ACK(void); // 看AHT20是否有回复ACK
void Send_ACK(void); // 主机回复ACK信号
void Send_NOT_ACK(void); // 主机不回复ACK
void Stop_I2C(void); // 一条协议结束
uint8_t AHT20_Read_Status(void); // 读取AHT20的状态寄存器
uint8_t AHT20_Read_Cal_Enable(void); // 查询cal enable位有没有使能
void AHT20_SendAC(void); // 向AHT20发送AC命令
uint8_t Calc_CRC8(uint8_t *message, uint8_t Num);
void AHT20_Read_CTdata(uint32_t *ct); // 没有CRC校验,直接读取AHT20的温度和湿度数据
void AHT20_Read_CTdata_crc(uint32_t *ct); // CRC校验后,读取AHT20的温度和湿度数据
void AHT20_Init(void); // 初始化AHT20
void JH_Reset_REG(uint8_t addr); /// 重置寄存器
void AHT20_Start_Init(void); /// 上电初始化进入正常测量状态
#endif
main.c文件
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "i2c.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "AHT20-21_DEMO_V1_3.h"
#include "oled.h"
#include "tb6612.h"
/* USER CODE END Includes */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
float tem; // 温度
float dam; // 湿度
uint32_t CT_data[2] = {0, 0};
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
volatile int c1, t1;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
MX_I2C1_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
AHT20_Init();
OLED_Init(); // 初始化OLED
OLED_Clear(0);
TB6612_PWM_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
// AHT20_Read_CTdata(CT_data); //不经过CRC校验,直接读取AHT20的温度和湿度数据 推荐每隔大于1S读一次
AHT20_Read_CTdata_crc(CT_data); // crc校验后,读取AHT20的温度和湿度数据
c1 = CT_data[0] * 1000 / 1024 / 1024; // 计算得到湿度值c1(放大了10倍)
t1 = CT_data[1] * 2000 / 1024 / 1024 - 500; // 计算得到温度值t1(放大了10倍)
dam = c1 / 10 + (float)(c1 % 10) * 0.1;
tem = t1 / 10 + (float)(t1 % 10) * 0.1;
printf("\r\n");
printf("温度: %.2f 湿度: %.2f \r\n", tem, dam);
HAL_Delay(500);
// if ((tem) > 28)
// {
// Set_Speed(0, 90);
// }
// else
// Set_Speed(0, 0);
}
/* USER CODE END 3 */
}
五、效果展示
实验效果
VID
六、总结
实验总结:
通过本次实验,我们成功地学习了I2C通信协议在STM32F103微控制器上的应用,并使用硬件I2C接口完成了与AHT20温湿度传感器的数据采集。以下是实验的一些关键总结点:
软件I2C和硬件I2C的选择: 在嵌入式系统开发中,我们面临了软件I2C和硬件I2C的选择。软件I2C适用于一些特殊场景,例如资源受限或者需要与不同电平设备通信。而硬件I2C通过硬件模块直接支持,更加稳定和高效。
AHT20数据手册阅读: 数据手册是了解和使用传感器的关键工具。通过详细阅读AHT20数据手册,我们获得了关于寄存器配置、数据采集频率等方面的必要信息,为正确实现数据采集提供了基础。
数据采集与串口通信: 我们成功地实现了每隔2秒钟采集一次AHT20的温湿度数据,并通过串口将数据发送到上位机(Windows 10)。串口通信的配置和数据传输是嵌入式系统中常见而重要的任务,通过本实验,我们掌握了相关配置和数据传输的基本原理。
实时监测与应用: 通过上位机(Windows 11)的串口监测工具,我们能够实时地监测到环境温湿度的变化。这对于许多嵌入式系统应用,如气象站、温室控制等,提供了关键的实时数据反馈。
扩展与深入学习: 本次实验是嵌入式系统开发的一个入门级实例,为进一步深入学习提供了坚实的基础。学员可以在此基础上探索更多传感器的应用、优化数据采集算法等方面的工作,拓展实际应用领域。
~
综上所述,通过本次实验,我们不仅掌握了I2C通信协议在STM32F103上的应用,还成功地完成了与AHT20传感器的数据采集,并实现了串口通信。这为今后的嵌入式系统开发奠定了坚实的基础,为更广泛的应用提供了宝贵的经验。
最后感谢大佬友情链接:
-
https://zhuanlan.zhihu.com/p/312206749
-
https://zhuanlan.zhihu.com/p/503219395
-
https://blog.youkuaiyun.com/Mark_md/article/details/108481450