前言
(1)个人邮箱:zhangyixu02@gmail.com
(2)最近移植了AHT20的驱动代码其中查了很多资料,也踩了一些坑,再次做一下记录。
(3)学习本文之前,需要对I2C时序有一个简单了解。
移植过程
手册解析
(1)AHT20有两个手册,一个是立创商城的,一个是广州奥松电子的官方网址文档。两者内容略有不同,但都是官方文档。我的感觉是各有优缺点,但是对于第一次接触这款芯片的人来说,看到两个不同的文档,但是都是广州奥松电子的官方文档,铁定是懵逼的。我对两个手册进行了对比,发现内容上大体是一致的,所以无需担心,我会和你彻底讲明白。
上电检测
(1)在立创商城的官方文档里面会有这样一个步骤,要求你上电等待40ms,判断
status
的bit[3]
是否为1。但是在广州奥松电子的官方网址文档里面没有这一条规则,当时广州奥松电子的FAE得知我进行了这一步操作,他也懵了一下。这一步操作为什么在官网手册里面没有,我也不清楚,可能官网手册和立创商城那个手册写的人不是同一个吧,然后写官网手册的人可能比较粗心就漏写了。
(2)从手册上也可以知道status
的bit[3]
就是判断AHT20校验位是否校验完成。操作流程也很简单,如下:
//起始信号 主机 从机 从机 主机 停止信号
0x71 ACK 0x18 NACK
(3)对于上述指令,肯定有些同学是蒙圈的。我们一步一步解析:
<1>起始信号和停止信号我就不多说了,I2C基础。
<2>首先,我们需要知道,I2C从机设备有10bit和7bit之分,而AHT20是7bit的设备,地址为0x38
。而当主机和从机通讯的时候,发送地址信号紧接着就是读/写信号。读信号是1,因此,(0x38<<1) | 1 == 0x71
。
<3>从机收到主机信号之后,就会发送ACK回应信号。
<4>因为我们告诉从机,主机要读取信号,因此此时是从机发送信号。这个时候肯定有同学要问了,为什么从机发送的信号是0x18
,这个其实是测试结果(笑)。那么有同学就要说了,我的不是0x18
,我的是0x19
行不行。这个我们不需要过多关注其他位,我们现在只关心bit[3]
。只要bit[3]
为1即可。
(4)但是,如果有同学发现,bit[3]
不是1,而是0怎么办呢?按照如下方法发送指令:
<1>有些同学可能又要说了,手册里面明明说的是发送0xBE
命令,接着发0x08
和0x00
。你在0xBE
前面加0x70干嘛?
<2>注意,还是I2C基础,主机向从机发送信号,需要起始信号的地址信息。因为AHT20地址为0x38
,写数据为0,因此(0x38<<1 | 0) == 0x70
。
//起始信号 主机 从机 主机 从机 主机 从机 主机 从机 停止信号
0x70 ACK 0xBE ACK 0x08 ACK 0x00 ACK
触发信号
(1)从这一部分开始,广州奥松电子的官方网址文档明显就比立创商城的那个文档好很多了,时序非常清晰。你可以尝试对比两个时序,发现是一模一样的。
<1>广州奥松电子的官方网址文档
<2>立创商城文档
测量数据
(1)这部分依旧写的非常清晰。唯一需要注意的点在于,官网文档比立创商城文档里面多读取了一位CRC校验,这个需要你根据需求来。
<1>广州奥松电子的官方网址文档
<2>立创商城文档
(2)CRC校验方法如下:
//CRC校验类型: CRC8
//多项式: X8+X5+X4+1
//Poly:0011 0001 0x31
unsigned char Calc_CRC8(unsigned char *message,unsigned char Num)
{
unsigned char i;
unsigned char byte;
unsigned char 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;
}
(3)获取到的温湿度数据转换公式
代码实操
(1)驱动代码如下
/*
* @file i2c_bus
* @brief i2c总线模型
* @author zhangyixu
* @version 1.0.0
* @date 2023-09-23
*
* @Copyright (c) 2023, PLKJ Development Team, All rights reserved.
*
* @par Change Logs:
* Date Author Notes
* 2023-09-23 zhangyixu 初始版本
*
*/
/*============================ INCLUDES ======================================*/
#include <stdio.h>
#include "unity.h"
#include "driver/i2c.h"
#include "esp_system.h"
#include "esp_log.h"
#include "aht20.h"
/*============================ MACROS ========================================*/
#define AHT20_STATUS_REG 0x00 //状态字 寄存器地址
#define ATH20_SLAVE_ADDRESS 0x38
#define ATH20_SOFT_RESET 0xBA
#define ATH20_CMD_INIT 0xBE
#define ATH20_TRIGGER_MEASURE 0xAC
#define ATH20_CAL_ENABLE(x) (x & 0x08)
#define ATH20_BUSY_ENABLE(x) (x & 0x80)
#define delay_ms(ms) vTaskDelay(pdMS_TO_TICKS(ms))
#define liblog_info(format,...) ESP_LOGI("aht20",format, ##__VA_ARGS__)
/*============================ TYPES =========================================*/
typedef struct
{
i2c_port_t bus;
uint8_t dev_addr;
uint8_t cmd_init[2];
uint8_t trigger_buff[2];
uint8_t readbuff[6];
}aht20_dev_t;
/*============================ EXTERNAL IMPLEMENTATION =======================*/
bool libi2c_master_write(const uint8_t device, const uint8_t reg, const void* buf, size_t len)
{
// USER TODO:
// write `len` bytes from `buf` to i2c device with slave address `device` starting at `reg`
i2c_cmd_handle_t cmd;
esp_err_t ret;
cmd = i2c_cmd_link_create();
ret = i2c_master_start(cmd);
assert(ESP_OK == ret);
ret = i2c_master_write_byte(cmd, device | I2C_MASTER_WRITE, true);
assert(ESP_OK == ret);
ret = i2c_master_write_byte(cmd, reg, true);
assert(ESP_OK == ret);
ret = i2c_master_write(cmd, (const uint8_t*)buf, len, true);
assert(ESP_OK == ret);
ret = i2c_master_stop(cmd);
assert(ESP_OK == ret);
ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(100));
assert(ESP_OK == ret);
i2c_cmd_link_delete(cmd);
return true;
}
bool libi2c_master_read(const uint8_t device, const uint8_t reg, void* buf, size_t len)
{
// USER TODO:
// read `len` bytes to `buf` from i2c device with slave address `device` starting at `reg`
i2c_cmd_handle_t cmd;
esp_err_t ret;
cmd = i2c_cmd_link_create();
ret = i2c_master_start(cmd);
assert(ESP_OK == ret);
ret = i2c_master_write_byte(cmd, device | I2C_MASTER_WRITE, true);
assert(ESP_OK == ret);
ret = i2c_master_write_byte(cmd, reg, true);
assert(ESP_OK == ret);
ret = i2c_master_start(cmd);
assert(ESP_OK == ret);
ret = i2c_master_write_byte(cmd, device | I2C_MASTER_READ, true);
assert(ESP_OK == ret);
ret = i2c_master_read(cmd, (uint8_t*)buf, len, I2C_MASTER_LAST_NACK);
assert(ESP_OK == ret);
ret = i2c_master_stop(cmd);
assert(ESP_OK == ret);
ret = i2c_master_cmd_begin(I2C_NUM_0, cmd, pdMS_TO_TICKS(100));
assert(ESP_OK == ret);
i2c_cmd_link_delete(cmd);
return true;
}
/*===================================================*/
static void aht20_soft_reset(aht20_dev_t *sens)
{
libi2c_master_write(sens->dev_addr,ATH20_SOFT_RESET,0,0);
delay_ms(20);
}
static void aht20_read_status(aht20_handle_t sensor)
{
aht20_dev_t *sens = (aht20_dev_t *) sensor;
libi2c_master_read(sens->dev_addr,AHT20_STATUS_REG,&sens->readbuff[0],1);
}
static void aht20_cmd_init(aht20_handle_t sensor)
{
aht20_dev_t *sens = (aht20_dev_t *) sensor;
libi2c_master_read(sens->dev_addr,ATH20_CMD_INIT,sens->cmd_init,2);
}
aht20_handle_t aht20_create(i2c_port_t port)
{
aht20_dev_t *sens = (aht20_dev_t *) calloc(1, sizeof(aht20_dev_t));
uint8_t soft_reset = 2,cmt_init = 2;
/*=================== AHT20命令初始化 ==========================*/
sens->bus = port;
sens->dev_addr = ATH20_SLAVE_ADDRESS << 1;
sens->cmd_init[0] = 0x08;
sens->cmd_init[1] = 0x00;
sens->trigger_buff[0] = 0x33;
sens->trigger_buff[1] = 0x00;
/*=================== AHT20上电检测 ===================*/
while(soft_reset--)
{
cmt_init = 2;
delay_ms(40);
aht20_read_status(sens);
/*=================== 检测两次校验使能位 ===================*/
while(!ATH20_CAL_ENABLE(sens->readbuff[0]) && (cmt_init--))
{
aht20_cmd_init(sens);
delay_ms(40);
}
/*=================== 两次软复位机会 ===================*/
if(soft_reset)
{
break;
}
else
{
aht20_soft_reset(sens);
delay_ms(20);
}
}
if(soft_reset)
{
liblog_info("sens->readbuff[0] is 0x%x",sens->readbuff[0]);
return (aht20_handle_t) sens;
}
// 如果失败进入这里
free(sens);
sens = NULL;
return (aht20_handle_t) sens;
}
esp_err_t aht20_read_data(aht20_handle_t sensor, float* humidity, float* temperature)
{
aht20_dev_t *sens = (aht20_dev_t *) sensor;
uint32_t sensor_data[2] = {0};
/*=================== 触发AHT20测量数据 ==========================*/
libi2c_master_write(sens->dev_addr,ATH20_TRIGGER_MEASURE,sens->trigger_buff,2);
/*=================== 读取温湿度数据 ==========================*/
delay_ms(80);
liblog_info("start read data");
delay_ms(2000);
sens->readbuff[0] = 0;
libi2c_master_read(sens->dev_addr,AHT20_STATUS_REG,sens->readbuff,6);
/*=================== 进行数据分析 ==========================*/
if(ATH20_BUSY_ENABLE(sens->readbuff[0]))
{
liblog_info("ATH20_BUSY_ENABLE");
return ESP_FAIL;
}
sensor_data[0] |= (sens->readbuff[1]) << 16;
sensor_data[0] |= (sens->readbuff[2]) << 8;
sensor_data[0] |= (sens->readbuff[3]);
sensor_data[0] = sensor_data[0] >> 4;
sensor_data[0] &= sensor_data[0] & 0xfffff;
sensor_data[1] |= (sens->readbuff[3]) << 16;
sensor_data[1] |= (sens->readbuff[4]) << 8;
sensor_data[1] |= (sens->readbuff[5]);
sensor_data[1] &= sensor_data[1] & 0xfffff;
liblog_info("sensor_data[0] is %lu , sensor_data[1] is %lu",sensor_data[0],sensor_data[1]);
*humidity = (double)sensor_data[0] * 100 / 0x100000 ;
*temperature = (double)sensor_data[1] * 200 / 0x100000 - 50;
return ESP_OK;
}
测试代码
(1)测试代码如下
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "sdkconfig.h"
#include "unity.h"
#include "driver/i2c.h"
#include "aht20.h"
static const char *TAG = "AHT20 test";
static aht20_handle_t aht20 = NULL;
void i2c_bus_0_master_init(int i2c_sda1, int i2c_scl1)
{
i2c_config_t conf;
conf.mode = I2C_MODE_MASTER;
conf.sda_io_num = (gpio_num_t)i2c_sda1;
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
conf.scl_io_num = (gpio_num_t)i2c_scl1;
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
conf.master.clk_speed = 100000;
conf.clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL;
esp_err_t ret = i2c_param_config(I2C_NUM_0, &conf);
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C0 config returned error");
ret = i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0);
TEST_ASSERT_EQUAL_MESSAGE(ESP_OK, ret, "I2C0 install returned error");
ESP_LOGI(TAG, "i2c_0_master_init OK");
}
TEST_CASE("aht20 test")
{
esp_err_t ret;
float humidity, temperature;
i2c_bus_0_master_init(10,11);
aht20 = aht20_create(I2C_NUM_0);
while (1) {
ret = aht20_read_data(aht20, &humidity, &temperature);
TEST_ASSERT_EQUAL(ESP_OK, ret);
ESP_LOGI(TAG,"humidity is %f , temperature is %f C",humidity,temperature);
vTaskDelay(pdMS_TO_TICKS(1000));
}
ret = i2c_driver_delete(I2C_NUM_0);
TEST_ASSERT_EQUAL(ESP_OK, ret);
}
湿度一直为0的bug
(1)经过测试,发现有时候湿度值一直为0。跟广州奥松电子的FAE沟通之后,发现可能是焊接时候,将AHT20内部电容搞坏了,导致的湿度一直测试为0。
参考
(1)立创商城:AHT20 技术手册;奥松电子AHT20 官方手册;
(2)ESP32IDF — 硬件I2C使用教程;
(3)CSDN博客:AHT20温湿度传感器STM32-I2C驱动,替代DHT11/DHT12/AM2320/SHT20/SHT30,IIC代码兼容AHT10/15-MEMS温湿度传感器;
(4)百度文库:阻容法湿度测量原理;