AHT20是国内奥松生成的I2C接口的MEMS温湿度传感器,ADC位数为20Bit,具有体积小、精度高、成本低等优点。相较于AHT10,最显著的变化是体积由 5*4*1.6mm,缩小到 3*3*1.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. 基本参数
AHT10 | AHT15 | AHT20 | |
---|---|---|---|
供电电压 | 1.8-3.6V | 1.8-3.6V | 2.0-5.5V |
工作电流(休眠) | 0.25uA | 0.25uA | 0.25uA |
工作电流(测量) | 23uA | 23uA | 23uA |
测量范围(湿度) | 0~100%RH | 0~100%RH | 0~100%RH |
测量范围(温度) | -40~+85℃ | -40~+85℃ | -40~+85℃ |
湿度精度 | ±2%RH(25℃) | ±2%RH(25℃) | ±2%RH(25℃) |
温度精度 | ±0.3℃ | ±0.3℃ | ±0.3℃ |
分辨率 | 温度: 0.01℃ 湿度: 0.024%RH | 温度: 0.01℃ 湿度: 0.024%RH | 温度: 0.01℃ 湿度: 0.024%RH |
信号输出 | I²C信号 | I²C信号 | I²C信号 |
防护 | 无 | 聚四氟乙烯防水防尘膜 | 无 |
封装大小 | 5x4x1.6mm(DFN) | 6x5x17.7mm(直插模块) | 3x3x1.0mm(DFN) |
2. 管脚定义、参考电路
- AHT20 - IC管脚定义:
- AHT20 - 参考电路:
3. I2C时序特性
- I2C时序特性:(支持标准100KHz,高速400KHz)
4. 寄存器映射
AHT10/15/20数据手册中提供的寄存器映射不完整,且手册对I2C操作器件的描述有误导倾向,建议各位直接看下面代码。
(AHT10/15/20的I2C时序与MPU6050等标准8Bit寄存器地址的器件相同,I2C读写时序可以照搬)
5. 实验器件、环境
MCU使用stm32F103,使用软件模拟I2C。
AHT20使用网上的模块,引脚与DHT12兼容,正好可以放在我自己的物联网测试板上。
6. stm32F103驱动代码
- AHT20.c:
#include "AHT20.h"
/**
* @brief 读AHT20 设备状态字
* @param void
* @retval uint8_t 设备状态字
*/
static uint8_t AHT20_ReadStatusCmd(void)
{
uint8_t tmp[1];
Soft_I2C_Read(ATH20_SLAVE_ADDRESS, AHT20_STATUS_REG, 1, tmp);
return tmp[0];
}
/**
* @brief 读AHT20 设备状态字 中的Bit3: 校准使能位
* @param void
* @retval uint8_t 校准使能位:1 - 已校准; 0 - 未校准
*/
static uint8_t AHT20_ReadCalEnableCmd(void)
{
uint8_t tmp;
tmp = AHT20_ReadStatusCmd();
return (tmp>>3)&0x01;
}
/**
* @brief 读AHT20 设备状态字 中的Bit7: 忙标志
* @param void
* @retval uint8_t 忙标志:1 - 设备忙; 0 - 设备空闲
*/
static uint8_t AHT20_ReadBusyCmd(void)
{
uint8_t tmp;
tmp = AHT20_ReadStatusCmd();
return (tmp>>7)&0x01;
}
/**
* @brief AHT20 芯片初始化命令
* @param void
* @retval void
*/
static void AHT20_IcInitCmd(void)
{
uint8_t tmp[2];
tmp[0] = 0x08;
tmp[1] = 0x00;
Soft_I2C_Write(ATH20_SLAVE_ADDRESS, AHT20_INIT_REG, 2, tmp);
}
/**
* @brief AHT20 触发测量命令
* @param void
* @retval void
*/
static void AHT20_TrigMeasureCmd(void)
{
uint8_t tmp[2];
tmp[0] = 0x33;
tmp[1] = 0x00;
Soft_I2C_Write(ATH20_SLAVE_ADDRESS, AHT20_TrigMeasure_REG, 2, tmp);
}
/**
* @brief AHT20 软复位命令
* @param void
* @retval void
*/
static void AHT20_SoftResetCmd(void)
{
uint8_t tmp[1];
Soft_I2C_Write(ATH20_SLAVE_ADDRESS, AHT20_SoftReset, 0, tmp);
}
/**
* @brief AHT20 设备初始化
* @param void
* @retval uint8_t:0 - 初始化AHT20设备成功; 1 - 初始化AHT20失败,可能设备不存在或器件已损坏
*/
uint8_t AHT20_Init(void)
{
uint8_t rcnt = 2+1;//软复位命令 重试次数,2次
uint8_t icnt = 2+1;//初始化命令 重试次数,2次
while(--rcnt)
{
icnt = 2+1;
delay_ms(40);//上电后要等待40ms
// 读取温湿度之前,首先检查[校准使能位]是否为1
while((!AHT20_ReadCalEnableCmd()) && (--icnt))// 2次重试机会
{
delay_ms(10);
// 如果不为1,要发送初始化命令
AHT20_IcInitCmd();
delay_ms(200);//这个时间不确定,手册没讲
}
if(icnt)//[校准使能位]为1,校准正常
{
break;//退出rcnt循环
}
else//[校准使能位]为0,校准错误
{
AHT20_SoftResetCmd();//软复位AHT20器件,重试
delay_ms(200);//这个时间不确定,手册没讲
}
}
if(rcnt)
{
delay_ms(200);//这个时间不确定,手册没讲
return 0;// AHT20设备初始化正常
}
else
{
return 1;// AHT20设备初始化失败
}
}
/**
* @brief AHT20 设备读取 相对湿度和温度(原始数据20Bit)
* @param uint32_t *HT:存储20Bit原始数据的uint32数组
* @retval uint8_t:0-读取数据正常; 1-读取设备失败,设备一直处于忙状态,不能获取数据
*/
uint8_t AHT20_ReadHT(uint32_t *HT)
{
uint8_t cnt=3+1;//忙标志 重试次数,3次
uint8_t tmp[6];
uint32_t RetuData = 0;
// 发送触发测量命令
AHT20_TrigMeasureCmd();
do{
delay_ms(75);//等待75ms待测量完成,忙标志Bit7为0
}while(AHT20_ReadBusyCmd() && (--cnt));//重试3次
if(cnt)//设备闲,可以读温湿度数据
{
delay_ms(5);
// 读温湿度数据
Soft_I2C_Read(ATH20_SLAVE_ADDRESS, AHT20_STATUS_REG, 6, tmp);
// 计算相对湿度RH。原始值,未计算为标准单位%。
RetuData = 0;
RetuData = (RetuData|tmp[1]) << 8;
RetuData = (RetuData|tmp[2]) << 8;
RetuData = (RetuData|tmp[3]);
RetuData = RetuData >> 4;
HT[0] = RetuData;
// 计算温度T。原始值,未计算为标准单位°C。
RetuData = 0;
RetuData = (RetuData|tmp[3]) << 8;
RetuData = (RetuData|tmp[4]) << 8;
RetuData = (RetuData|tmp[5]);
RetuData = RetuData&0xfffff;
HT[1] = RetuData;
return 0;
}
else//设备忙,返回读取失败
{
return 1;
}
}
/**
* @brief AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
* @param struct m_AHT20* aht:存储AHT20传感器信息的结构体
* @retval uint8_t:0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
*/
uint8_t StandardUnitCon(struct m_AHT20* aht)
{
aht->RH = (double)aht->HT[0] *100 / 1048576;//2^20=1048576 //原式:(double)aht->HT[0] / 1048576 *100,为了浮点精度改为现在的
aht->Temp = (double)aht->HT[1] *200 / 1048576 -50;
//限幅,RH=0~100%; Temp=-40~85°C
if((aht->RH >=0)&&(aht->RH <=100) && (aht->Temp >=-40)&&(aht->Temp <=85))
{
aht->flag = 0;
return 0;//测量数据正常
}
else
{
aht->flag = 1;
return 1;//测量数据超出范围,错误
}
}
- AHT20.h:
#ifndef __AHT20_H
#define __AHT20_H
#include "sys.h"
#include "myiic.h"
#include "delay.h"
#define ATH20_SLAVE_ADDRESS 0x38 /* I2C从机地址 */
//****************************************
// 定义 AHT20 内部地址
//****************************************
#define AHT20_STATUS_REG 0x00 //状态字 寄存器地址
#define AHT20_INIT_REG 0xBE //初始化 寄存器地址
#define AHT20_SoftReset 0xBA //软复位 单指令
#define AHT20_TrigMeasure_REG 0xAC //触发测量 寄存器地址
// 存储AHT20传感器信息的结构体
struct m_AHT20
{
uint8_t alive; // 0-器件不存在; 1-器件存在
uint8_t flag; // 读取/计算错误标志位。0-读取/计算数据正常; 1-读取/计算设备失败
uint32_t HT[2]; // 湿度、温度 原始传感器的值,20Bit
float RH; // 湿度,转换单位后的实际值,标准单位%
float Temp; // 温度,转换单位后的实际值,标准单位°C
};
uint8_t AHT20_Init(void);
uint8_t AHT20_ReadHT(uint32_t *HT);
uint8_t StandardUnitCon(struct m_AHT20* aht);
#endif
- myiic.c:(使用的软件模拟iic,使用stm32F103的PB6.7)
#include "myiic.h"
#include "delay.h"
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
IIC_SDA=(txd&0x80)>>7;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
/* 对器件读写的封装,从机器件地址为1字节 */
/**
* @brief 向I2C设备连续写数据(适用于符合IIC通信协议的寄存器地址为uint8类型的器件)
* @param addr:I2C从机器件地址
* @param reg: I2C从机寄存器地址
* @param len: 写入长度
* @param buf: uint8数据数组
* @retval 0,正常; 其他,错误代码;
*/
uint8_t Soft_I2C_Write(uint8_t dev_addr, uint8_t reg_addr, uint8_t len, unsigned char *data_buf)
{
uint8_t i;
IIC_Start();
IIC_Send_Byte(dev_addr << 1 | I2C_Direction_Transmitter);//发送器件地址+写命令
if(IIC_Wait_Ack())//等待应答
{
IIC_Stop();
return 1;
}
IIC_Send_Byte(reg_addr);//写寄存器地址
IIC_Wait_Ack();//等待应答
for(i=0;i<len;i++)
{
IIC_Send_Byte(data_buf[i]);//发送数据
if(IIC_Wait_Ack())//等待ACK
{
IIC_Stop();
return 1;
}
}
IIC_Stop();
return 0;
}
/**
* @brief 从I2C设备连续读数据(适用于符合IIC通信协议的寄存器地址为uint8类型的器件)
* @param addr:I2C从机器件地址
* @param reg: I2C从机寄存器地址
* @param len: 读出长度
* @param buf: uint8数据数组
* @retval 0,正常; 其他,错误代码;
*/
uint8_t Soft_I2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t len, unsigned char *data_buf)
{
uint8_t result;
IIC_Start();
IIC_Send_Byte(dev_addr << 1 | I2C_Direction_Transmitter);//发送器件地址+写命令
if(IIC_Wait_Ack())//等待应答
{
IIC_Stop();
return 1;
}
IIC_Send_Byte(reg_addr);//写寄存器地址
IIC_Wait_Ack();//等待应答
IIC_Start();
IIC_Send_Byte(dev_addr << 1 | I2C_Direction_Receiver);//发送器件地址+读命令
IIC_Wait_Ack();//等待应答
while(len)
{
if(len==1)*data_buf=IIC_Read_Byte(0);//读数据,发送nACK
else *data_buf=IIC_Read_Byte(1);//读数据,发送ACK
len--;
data_buf++;
}
IIC_Stop();//产生一个停止条件
return 0;
}
- myiic.h:
#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//IO方向设置
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
//IO操作函数
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //输入SDA
//IIC所有操作函数
void IIC_Init(void); //初始化IIC的IO口
void IIC_Start(void); //发送IIC开始信号
void IIC_Stop(void); //发送IIC停止信号
void IIC_Send_Byte(u8 txd); //IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); //IIC等待ACK信号
void IIC_Ack(void); //IIC发送ACK信号
void IIC_NAck(void); //IIC不发送ACK信号
uint8_t Soft_I2C_Write(uint8_t dev_addr, uint8_t reg_addr, uint8_t len, unsigned char *data_buf);
uint8_t Soft_I2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t len, unsigned char *data_buf);
#endif
- main.c:
struct m_AHT20 AHT20;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
IIC_Init(); //IIC管脚初始化
AHT20.alive=!AHT20_Init(); //AHT20温湿度传感器初始化
while(1)
{
if(AHT20.alive)// 如果AHT20传感器存在,则读取温湿度数据
{
//读取AHT20的 20Bit原始数据
AHT20.flag = AHT20_ReadHT(AHT20.HT);
//实际标准单位转换
StandardUnitCon(&AHT20);
}
delay_ms(2000);
}
}
I2C波形、读出数据
(使用的软件模拟IIC,所以时序不是特别标准)
- AHT20-ReadStatus:
- AHT20-ReadData:
Jlink 调试器读数:
- 室内温度、湿度:
- 冲着AHT20哈哈气,后的温湿度读数,跟上面有鲜明对比