IIC协议采集温湿度

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管脚定义:

img

  • AHT20 - 参考电路:

3. I2C时序特性

  • I2C时序特性:(支持标准100KHz,高速400KHz)

img

img

4. 应用场景

它广泛应用于暖通空调 、除湿器、测试及检测设备、消费品、汽车 、自动控制、数据记录器、气象站、家电、湿度调节、医疗及其他相关湿度检测控制等领域。

二、IIC 介绍

1.IIC简介

I2C(IIC)属于两线式串行总线,由飞利浦公司开发用于微控制器(MCU)和外围设备(从设备)进行通信的一种总线,属于一主多从(一个主设备(Master),多个从设备(Slave))的总线结构总线上的每个设备都有一个特定的设备地址,以区分同一I2C总线上的其他设备

物理I2C接口有两根双向线,**串行时钟线(SCL)串行数据线(SDA)**组成,可用于发送和接收数据,但是通信都是由主设备发起,从设备被动响应,实现数据的传输。

img

2. IIC 一般通信过程

一. 主设备给从设备发送/写入数据:

  1. 主设备发送起始(START)信号
  2. 主设备发送设备地址到从设备
  3. 等待从设备响应(ACK)
  4. 主设备发送数据到从设备,一般发送的每个字节数据后会跟着等待接收来自从设备的响应(ACK)
  5. 数据发送完毕,主设备发送停止(STOP)信号终止传输

img

二. 主设备从从设备接收/读取数据

  1. 设备发送起始(START)信号

  2. 主设备发送设备地址到从设备

  3. 等待从设备响应(ACK)

  4. 主设备接收来自从设备的数据,一般接收的每个字节数据后会跟着向从设备发送一个响应(ACK)

  5. 一般接收到最后一个数据后会发送一个无效响应(NACK),然后主设备发送停止(STOP)信号终止传输

img

注:具体通信过程需视具体时序图而定

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由高电平跳变到低电平。

img

四. 停止(STOP)信号

I2C通信的停止信号由主设备终止,SCL保持高电平,SDA由低电平跳变到高电平。

img

五. 数据有效性

I2C总线进行数据传送时,在SCL的每个时钟脉冲期间传输一个数据位,时钟信号SCL为高电平期间,数据线SDA上的数据必须保持稳定,只有在时钟线SCL上的信号为低电平期间,数据线SDA上的高电平或低电平状态才允许变化,因为当SCL是高电平时,数据线SDA的变化被规定为控制命令STARTSTOP,也就是前面的起始信号停止信号)。

img

六. 应答信号(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模块通常包括以下关键功能:

  1. SCL和SDA线的硬件控制: 专门的硬件电路用于生成和控制时钟线(SCL)和数据线(SDA)。这消除了在软件I2C中手动控制这些线的需求,提高了通信的精确性和效率。

  2. 时序控制: 硬件I2C模块内部具有时序生成和检测电路,确保通信协议的时序满足I2C标准。这有助于保持通信的稳定性和可靠性。

  3. 寄存器设置: 通过配置相关寄存器,可以轻松设置硬件I2C的工作参数,如通信速率、地址等。

  4. 中断支持: 很多硬件I2C模块支持中断,使得在数据传输完成或发生错误时能够及时响应,提高系统的响应性。

  5. 多主机支持: 一些硬件I2C模块支持多主机模式,允许多个主机设备在同一总线上进行通信。

由于硬件I2C直接在硬件层面实现了协议,相比软件I2C,它通常具有更高的通信速度和更稳定的性能。因此,在大多数情况下,当系统集成了硬件I2C模块时,优先选择使用硬件I2C以获得更好的性能和可靠性。

四、工程建立

CubeMX配置

基本的配置在我之前的文章中有,这里我们直接跳到对应端口的配置

  1. 配置对应的端口在这里插入图片描述

  2. 配置端口模式

    在这里插入图片描述

函数撰写

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温湿度传感器的数据采集。以下是实验的一些关键总结点:

  1. 软件I2C和硬件I2C的选择: 在嵌入式系统开发中,我们面临了软件I2C和硬件I2C的选择。软件I2C适用于一些特殊场景,例如资源受限或者需要与不同电平设备通信。而硬件I2C通过硬件模块直接支持,更加稳定和高效。

  2. AHT20数据手册阅读: 数据手册是了解和使用传感器的关键工具。通过详细阅读AHT20数据手册,我们获得了关于寄存器配置、数据采集频率等方面的必要信息,为正确实现数据采集提供了基础。

  3. 数据采集与串口通信: 我们成功地实现了每隔2秒钟采集一次AHT20的温湿度数据,并通过串口将数据发送到上位机(Windows 10)。串口通信的配置和数据传输是嵌入式系统中常见而重要的任务,通过本实验,我们掌握了相关配置和数据传输的基本原理。

  4. 实时监测与应用: 通过上位机(Windows 11)的串口监测工具,我们能够实时地监测到环境温湿度的变化。这对于许多嵌入式系统应用,如气象站、温室控制等,提供了关键的实时数据反馈。

  5. 扩展与深入学习: 本次实验是嵌入式系统开发的一个入门级实例,为进一步深入学习提供了坚实的基础。学员可以在此基础上探索更多传感器的应用、优化数据采集算法等方面的工作,拓展实际应用领域。
    ~
    综上所述,通过本次实验,我们不仅掌握了I2C通信协议在STM32F103上的应用,还成功地完成了与AHT20传感器的数据采集,并实现了串口通信。这为今后的嵌入式系统开发奠定了坚实的基础,为更广泛的应用提供了宝贵的经验。

最后感谢大佬友情链接:

  • https://zhuanlan.zhihu.com/p/312206749

  • https://zhuanlan.zhihu.com/p/503219395

  • https://blog.csdn.net/Mark_md/article/details/108481450

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LX很爱吃葱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值