ESP32驱动AHT20温湿度传感器源代码

一、总体结构

        使用ESP-IDF编译软件,ESP32S3,AHT20探测温湿度,结果输出在屏幕monitor上。

        主机ESP32S3使用硬件I2C器件,任意两只引脚做SCL和SDA,3.3V供电给AHT20即可。PDD买的这个模块自带上拉电阻,不用操心硬件布局,直接连单片机引脚即可。

        之前我做过DHT11的,但DHT11精度和分辨率都跟AHT20没得比。DHT11的半双工传输需要软件模拟,占用cpu。

二、AHT20芯片手册解读

        I2C协议我就不讲了,讲讲这个芯片如何操作。这个芯片手册很简单,只有3种命令,分别是初始化、触发测量和软重启命令,读数不是命令,读数里面包含一个状态字节用来记录芯片的状态供主机查看。

        

        红框表示写命令,是主机向从机的写操作,控制AHT20执行一些操作,发送的从机地址后尾要跟一个0表示写命令操作。比如让从机执行初始化命令,主机发送的信息就是0x70(从机地址,写操作)+0xBE+0x08+0x00共4个字节,然后SDA线从低拉高,通讯结束。主机全程写操作。(0x38<<1)&0x00=0x70。

        蓝框表示读温湿度数据。当我们要想读温湿度数据时,要分进行两套操作,先写“触发测量”的指令,再读温湿度数据。第一步:主机启动总线、主机写0x70(从机地址+写操作位)、向从机写0xAC、0x33、0x00三个字节的命令,触发测量,拉高SDA,通讯结束,全程写操作。过80ms后,第二步:主机启动总线、主机写0x71(从机地址+读操作位),主机read 6个bytes的信息,读完后拉高SDA,通讯结束。无论主机想读还是写,都要先向从机发送地址0x38加一位读/写方向位。0x38向左移一位后加上1,表示读温湿度和状态的操作,这8个bits=0x71,因此芯片手册出现的0x71是这么来的。

        温度和湿度数据都是20bit,这个跟芯片分辨率以及测量范围关联。芯片温度分辨率是0.01°C,测量温度范围是-40度至85度,精度是正负0.3度。湿度分辨率是0.024%RH,精度正负2%。这都说明了AHT20比DHT11强。

三、代码

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "driver/i2c.h"

#define slave_addr 0x38
#define scl_gpio 15
#define sda_gpio 16
#define commu_speed 100000   //100khz通讯

static const char* TAG = "aht20";
int i2c_master_port=I2C_NUM_0;
TaskHandle_t taskA,taskB,taskC;
QueueHandle_t queue;

void I2C_Init()
{
    i2c_config_t i2c_cnf={
    .mode=I2C_MODE_MASTER,
    .master.clk_speed=commu_speed,
    .scl_io_num=scl_gpio,
    .sda_io_num=sda_gpio,
    };
    i2c_param_config(i2c_master_port,&i2c_cnf);
    i2c_driver_install(i2c_master_port,I2C_MODE_MASTER,0,0,0);
}

/*AHT20刚上电时需要校准*/
void AHT20_Init()
{
    uint8_t status=0;
    uint8_t buffer[3]={0xBE,0x08,0x00};
    I2C_Init();
    vTaskDelay(100/portTICK_PERIOD_MS);
    i2c_cmd_handle_t cmd;
    cmd=i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, ((slave_addr << 1) | I2C_MASTER_WRITE), true);
    i2c_master_write(cmd,buffer,sizeof(buffer),true);
    i2c_master_stop(cmd);
    i2c_master_cmd_begin(i2c_master_port, cmd, pdMS_TO_TICKS(50));
    i2c_cmd_link_delete(cmd);

    i2c_master_read_from_device(i2c_master_port,slave_addr,&status,1,pdMS_TO_TICKS(50));
    if ((status & 0x08) == 0x08)
        ESP_LOGI(TAG,"AHT20 is calibrated.");
    else
        ESP_LOGI(TAG,"AHT20 calibrated failed.");
}
void timer()
{
    while(1)
    {
        vTaskDelay(2000/portTICK_PERIOD_MS);
        xTaskNotifyGive(taskB);
    }
}
void AHT20_Read()
{
    uint8_t busy_status=0xFF;
    uint8_t AC_CMD[3]={0xAC,0x33,0x00};
    uint8_t read_buf[6]={0};
    uint32_t rh_raw,temp_raw;
    float temp,rh;
    while(1)
    {
        ulTaskNotifyTake(1,portMAX_DELAY);

        /*先发送AC命令触发测量*/
        i2c_master_write_to_device(i2c_master_port,slave_addr,AC_CMD,sizeof(AC_CMD),pdMS_TO_TICKS(50));

        /*等80ms测量完成*/
        vTaskDelay(80/portTICK_PERIOD_MS);

        uint8_t cnt=10;
        do/*检测AHT20是否处于忙状态*/
        {
            i2c_master_read_from_device(i2c_master_port,slave_addr,&busy_status,1,pdMS_TO_TICKS(5));
            vTaskDelay(2/portTICK_PERIOD_MS);
            cnt--;
        }while(((busy_status & 0x80) ==0x80 ) && (cnt>0));
        if(0==cnt)
        {
            ESP_LOGE(TAG,"AHT20 is busy, read timeout.");
            return;
        }
        /*读5个字节湿温度数据*/
        i2c_master_read_from_device(i2c_master_port,slave_addr,read_buf,sizeof(read_buf),pdMS_TO_TICKS(50));

        /*数据转换*/
        rh_raw=((uint32_t)read_buf[1]<<16) | ((uint32_t)read_buf[2]<<8) | ((uint32_t)read_buf[3]);
        rh_raw=rh_raw >> 4;
        temp_raw=((uint32_t)(read_buf[3]&0x0F)<<16) | ((uint32_t)read_buf[4]<<8) | ((uint32_t)read_buf[5]);

        /*数据计算*/
        rh=((uint64_t)rh_raw*10000)>>20;
        temp=(((uint64_t)temp_raw*20000)>>20)-5000;
        float buffer[2]={rh,temp};
        xQueueSend(queue,buffer,10/portTICK_PERIOD_MS);
    }
}

void Data_Print()
{
    float buffer[2];
    while (1)
    {
        xQueueReceive(queue,buffer,portMAX_DELAY);
        ESP_LOGI(TAG, "rh:%.2f%%", buffer[0] / 100);
        ESP_LOGI(TAG, "temp:%.2f°C\n", buffer[1] / 100);
    }
}

void app_main(void)
{
    AHT20_Init();
    queue=xQueueCreate(1,sizeof(float[2]));
    xTaskCreate(timer,"taskA",3000,NULL,4,&taskA);
    xTaskCreate(AHT20_Read,"taskB",3000,NULL,5,&taskB);
    xTaskCreate(Data_Print,"taskC",3000,NULL,4,&taskC);
}

        I2C_Init()是要配置ESP32-S3的硬件外设I2C,ESP-IDF凡是配置外设的都会有config_t结构体配置、driver_install()函数,跟软件模拟的I2C区别是,软件模拟得靠MCU根据时序控制gpio高低电平以及输入输出方向。硬件的话,MCU只需前期配置好硬件I2C外设,将要操作的步骤一次过发给外设,通讯过程I2C自己搞定,MCU几乎不参与。ESP-IDF的I2C硬件有个特色,先用cmd_handle创造一条link,先把start、write、ack、stop等操作按顺序一并发给cmd_link,cpu就可以去干别的事,i2c通讯过程就靠i2c外设自己搞定。执行cmd_begin()函数让I2C外设硬件执行上述指令链开始通讯。有个地方要注意,编辑这条Link过程要是插入别的指令,比如延时,是不会生效的。

        AHT20_Init(),芯片刚上电时马上进行校准,无论校准使能位是否就绪,都直接进行校准。

        timer()是一个定时任务,每隔2s以任务通知形式触发AHT20_Read()任务,而AHT20_Read()完成后又通过队列触发Data_Print()任务,三个任务依次进行。

        值得注意的是AHT20_Read()里面这两句计算公式:

rh=((uint64_t)rh_raw*10000)>>20;
temp=(((uint64_t)temp_raw*20000)>>20)-5000;

        

        湿度rh跟芯片手册的公式比大了100倍,原因是,按照芯片手册的公式除以2^{20}的话,得出的是一个整数,两位小数被截去了,因此这儿先乘以100倍,后面Print时再缩小100倍,就不损失精度了。>>20的操作相当于除以2^{20}

四、输出结果显示

最近有点热

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值