这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » STM32 » 【STM32H747I-DISCO试用】3驱动AHT20温湿度传感器

共1条 1/1 1 跳转至

【STM32H747I-DISCO试用】3驱动AHT20温湿度传感器

高工
2025-11-15 15:57:08     打赏

【前言】

根据试用计划,我需要使用STM32H747_CM4内核来驱动一个温湿度传感器,获取其温度值,然后通过OpemAMP传给STM32H747_CM7核,再用touchGFX展示出来。这一篇,我将分享如STM32H747_CM4中驱动AHT20。

【接口选择】

AHT20是IIC接口的传感器,通过查看原理图,开发板开放的硬件IIC为I2C4,但是I2C4已经分配给STM32H747_CM7的LCD屏的触摸屏的接口使用。因此不方便再做为STM32H747_CM4的核使用,因此决定采用摸拟IIC来驱动。

我使用开发板上的Arduino接口中的D6与D7,D7为PJ0做为SCL,D6为PJ7做为SDA使用。

【代码实现】

首先往stm32cubeIDE的src中添加两个驱动文件:ATH20.h与ATH20.c,其代码如下:

#ifndef __AHT20_H
#define __AHT20_H

#include "stm32h7xx_hal.h"

// -------------------------- 硬件引脚定义 --------------------------
#define AHT20_SCL_PIN    GPIO_PIN_0    // PJ0 (SCL)
#define AHT20_SDA_PIN    GPIO_PIN_7    // PJ7 (SDA)
#define AHT20_GPIO_PORT  GPIOJ         // 端口J
#define AHT20_GPIO_CLK   __HAL_RCC_GPIOJ_CLK_ENABLE()  // GPIOJ时钟使能

// -------------------------- AHT20参数定义 --------------------------
#define AHT20_I2C_ADDR   0x38          // AHT20 7位I2C地址
#define AHT20_WRITE_ADDR (AHT20_I2C_ADDR << 1)        // 写地址(0x70)
#define AHT20_READ_ADDR  (AHT20_I2C_ADDR << 1 | 0x01) // 读地址(0x71)

// AHT20命令
#define AHT20_CMD_INIT   0xBE          // 初始化命令
#define AHT20_CMD_MEAS   0xAC          // 温湿度测量命令

// 状态码
typedef enum {
    AHT20_OK = 0,
    AHT20_INIT_FAIL,   // 初始化失败
    AHT20_COMM_ERR,    // I2C通信错误
    AHT20_MEAS_TIMEOUT,// 测量超时
    AHT20_CRC_ERR      // CRC校验失败
} AHT20_StatusTypeDef;

// -------------------------- 函数声明 --------------------------
void DWT_Delay_Init(void);             // DWT微秒延时初始化
void DWT_Delay_us(uint32_t us);        // 微秒延时
AHT20_StatusTypeDef AHT20_Init(void);  // AHT20初始化
AHT20_StatusTypeDef AHT20_ReadData(float *humidity, float *temperature); // 读取温湿度

#endif // __AHT20_H
#include "AHT20.h"

// -------------------------- 模拟I2C底层宏定义 --------------------------
#define I2C_SCL_HIGH()  HAL_GPIO_WritePin(AHT20_GPIO_PORT, AHT20_SCL_PIN, GPIO_PIN_SET)
#define I2C_SCL_LOW()   HAL_GPIO_WritePin(AHT20_GPIO_PORT, AHT20_SCL_PIN, GPIO_PIN_RESET)
#define I2C_SDA_HIGH()  HAL_GPIO_WritePin(AHT20_GPIO_PORT, AHT20_SDA_PIN, GPIO_PIN_SET)
#define I2C_SDA_LOW()   HAL_GPIO_WritePin(AHT20_GPIO_PORT, AHT20_SDA_PIN, GPIO_PIN_RESET)
#define I2C_SDA_READ()  HAL_GPIO_ReadPin(AHT20_GPIO_PORT, AHT20_SDA_PIN)

// -------------------------- DWT延时实现(精确微秒) --------------------------
// 依赖STM32H7内核DWT计数器,系统时钟需配置正确(默认480MHz)
void DWT_Delay_Init(void) {
    if (CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk) {
        DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk;
    }
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; // 使能DWT
    DWT->CYCCNT = 0;                                 // 计数器清零
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;             // 使能CYCCNT计数
}

void DWT_Delay_us(uint32_t us) {
    uint32_t start = DWT->CYCCNT;
    uint32_t ticks = (SystemCoreClock / 1000000) * us; // 1us = 系统时钟数/1e6
    while ((DWT->CYCCNT - start) < ticks);
}

// -------------------------- 模拟I2C核心函数 --------------------------
// I2C起始信号:SCL高电平时,SDA从高→低
static void I2C_Start(void) {
    I2C_SDA_HIGH();
    I2C_SCL_HIGH();
    DWT_Delay_us(5);
    I2C_SDA_LOW();
    DWT_Delay_us(5);
    I2C_SCL_LOW(); // 拉低SCL,准备发送数据
    DWT_Delay_us(5);
}

// I2C停止信号:SCL高电平时,SDA从低→高
static void I2C_Stop(void) {
    I2C_SDA_LOW();
    I2C_SCL_HIGH();
    DWT_Delay_us(5);
    I2C_SDA_HIGH();
    DWT_Delay_us(5);
}

// 发送应答信号(0=应答,1=非应答)
static void I2C_SendAck(uint8_t ack) {
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    ack ? I2C_SDA_HIGH() : I2C_SDA_LOW();
    DWT_Delay_us(5);
    I2C_SCL_HIGH();
    DWT_Delay_us(5);
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    I2C_SDA_HIGH(); // 释放SDA总线
}

// 接收应答信号(返回0=应答,1=非应答)
static uint8_t I2C_RecvAck(void) {
    uint8_t ack = 1;
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    I2C_SDA_HIGH(); // 释放SDA,由从机控制
    DWT_Delay_us(5);
    I2C_SCL_HIGH();
    DWT_Delay_us(5);
    if (I2C_SDA_READ() == GPIO_PIN_RESET) ack = 0; // 从机拉低=应答
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    return ack;
}

// 发送1字节数据
static void I2C_SendByte(uint8_t data) {
    uint8_t i;
    for (i = 0; i < 8; i++) {
        I2C_SCL_LOW();
        DWT_Delay_us(5);
        (data & 0x80) ? I2C_SDA_HIGH() : I2C_SDA_LOW(); // 高位先送
        data <<= 1;
        DWT_Delay_us(5);
        I2C_SCL_HIGH();
        DWT_Delay_us(5);
    }
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    I2C_SDA_HIGH(); // 释放SDA,等待应答
}

// 接收1字节数据(ack=0:发送应答,ack=1:发送非应答)
static uint8_t I2C_RecvByte(uint8_t ack) {
    uint8_t i, data = 0;
    I2C_SDA_HIGH(); // 释放SDA
    for (i = 0; i < 8; i++) {
        I2C_SCL_LOW();
        DWT_Delay_us(5);
        I2C_SCL_HIGH();
        DWT_Delay_us(5);
        data <<= 1;
        if (I2C_SDA_READ() == GPIO_PIN_SET) data |= 0x01;
        DWT_Delay_us(5);
    }
    I2C_SCL_LOW();
    DWT_Delay_us(5);
    I2C_SendAck(ack); // 发送应答/非应答
    return data;
}

// -------------------------- AHT20专用函数 --------------------------
// 读取AHT20状态寄存器
static uint8_t AHT20_ReadStatus(void) {
    uint8_t status = 0;
    I2C_Start();
    I2C_SendByte(AHT20_READ_ADDR);
    if (I2C_RecvAck() == 0) { // 从机应答
        status = I2C_RecvByte(1); // 接收状态字节,发送非应答
    }
    I2C_Stop();
    return status;
}

// CRC8校验(多项式0x31,AHT20专用)
static uint8_t AHT20_CRC8(uint8_t *data, uint8_t len) {
    uint8_t crc = 0xFF;
    for (uint8_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (uint8_t j = 0; j < 8; j++) {
            crc = (crc & 0x80) ? (crc << 1 ^ 0x31) : (crc << 1);
        }
    }
    return crc;
}

// AHT20初始化(GPIO+I2C+芯片初始化)
AHT20_StatusTypeDef AHT20_Init(void) {
    GPIO_InitTypeDef gpio_conf = {0};
    uint8_t retry = 0;

    // 1. 初始化DWT延时
    DWT_Delay_Init();

    // 2. 配置I2C GPIO(开漏输出+上拉,避免总线冲突)
    AHT20_GPIO_CLK; // 使能GPIOJ时钟
    gpio_conf.Pin = AHT20_SCL_PIN | AHT20_SDA_PIN;
    gpio_conf.Mode = GPIO_MODE_OUTPUT_OD;    // 开漏输出
    gpio_conf.Pull = GPIO_PULLUP;            // 上拉电阻
    gpio_conf.Speed = GPIO_SPEED_FREQ_HIGH;  // 高速
    HAL_GPIO_Init(AHT20_GPIO_PORT, &gpio_conf);

    // 3. 初始电平:SCL/SDA均拉高
    I2C_SCL_HIGH();
    I2C_SDA_HIGH();
    HAL_Delay(20); // 上电后等待20ms稳定

    // 4. 检查AHT20是否已初始化(状态位bit3=1表示已初始化)
    while (((AHT20_ReadStatus() & 0x08) == 0) && (retry < 10)) {
        // 未初始化,发送初始化命令(0xBE + 0x08 + 0x00)
        I2C_Start();
        I2C_SendByte(AHT20_WRITE_ADDR);
        if (I2C_RecvAck() == 0) {
            I2C_SendByte(AHT20_CMD_INIT);
            I2C_RecvAck();
            I2C_SendByte(0x08); // 配置参数1
            I2C_RecvAck();
            I2C_SendByte(0x00); // 配置参数2
            I2C_RecvAck();
        }
        I2C_Stop();
        HAL_Delay(10); // 等待初始化完成
        retry++;
    }

    return (retry < 10) ? AHT20_OK : AHT20_INIT_FAIL;
}

// 读取温湿度数据(返回:湿度(%)、温度(℃))
AHT20_StatusTypeDef AHT20_ReadData(float *humidity, float *temperature) {
    uint8_t data[7] = {0};
    uint32_t humi_raw = 0, temp_raw = 0;

    // 1. 发送测量命令(0xAC + 0x33 + 0x00)
    I2C_Start();
    I2C_SendByte(AHT20_WRITE_ADDR);
    if (I2C_RecvAck() != 0) {
        I2C_Stop();
        return AHT20_COMM_ERR;
    }
    I2C_SendByte(AHT20_CMD_MEAS);
    I2C_RecvAck();
    I2C_SendByte(0x33); // 测量配置参数1
    I2C_RecvAck();
    I2C_SendByte(0x00); // 测量配置参数2
    I2C_RecvAck();
    I2C_Stop();

    // 2. 等待测量完成(AHT20忙时SDA拉低,超时100ms)
    uint32_t timeout = 100;
    while ((I2C_SDA_READ() == GPIO_PIN_RESET) && (timeout-- > 0)) {
        HAL_Delay(1);
    }
    if (timeout == 0) return AHT20_MEAS_TIMEOUT;

    // 3. 读取6字节数据(状态+湿度高2字节+温湿度混合字节+温度低2字节+CRC)
    I2C_Start();
    I2C_SendByte(AHT20_READ_ADDR);
    if (I2C_RecvAck() != 0) {
        I2C_Stop();
        return AHT20_COMM_ERR;
    }
    for (uint8_t i = 0; i < 6; i++) {
        data[i] = I2C_RecvByte(0); // 前5字节:发送应答
    }
    data[6] = I2C_RecvByte(1); // 第6字节(CRC):发送非应答
    I2C_Stop();

    // 4. CRC校验(可选,注释可关闭)
    if (AHT20_CRC8(data, 6) != data[6]) {
        return AHT20_CRC_ERR;
    }

    // 5. 解析温湿度(20位数据)
    humi_raw = ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8) | data[3];
    humi_raw >>= 4; // 湿度:20位(data1[8位] + data2[8位] + data3[4位])
    *humidity = (humi_raw / 1048576.0f) * 100.0f; // 1048576 = 2^20

    temp_raw = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];
    // 温度:20位(data3[4位] + data4[8位] + data5[8位])
    *temperature = (temp_raw / 1048576.0f) * 200.0f - 50.0f;

    return AHT20_OK;
}

添加app.h以及app.c,用于用户代码实现,避免在main.h中添加过多的代码,也避免在重新生成代码后覆盖。

app.h代码如下:

#ifndef APPLICATION_USER_CORE_APP_H_
#define APPLICATION_USER_CORE_APP_H_
#include "main.h"

void Run(void);
#endif /* AP

app.c代码如下:

#include "app.h"
#include "stdio.h"
#include "AHT20.h"
void Run(void)
{
	  float humidity = 0.0f, temperature = 0.0f;
	  AHT20_StatusTypeDef aht_status;
	  printf("start\r\n");
	  // 1. 初始化AHT20
	  aht_status = AHT20_Init();
	  if (aht_status == AHT20_OK) {
		  printf("AHT20 Init Success!\r\n");
	  } else {
		  printf("AHT20 Init Fail! Code: %d\r\n", aht_status);
		  while (1); // 初始化失败,死循环
	  }
	  while(1)
	  {
		  aht_status = AHT20_ReadData(&humidity, &temperature);
		  if (aht_status == AHT20_OK) {
			  // 打印温湿度(保留2位小数)
			  printf("Humidity: %.2f %%RH | Temperature: %.2f °C\r\n",\
					  humidity, temperature);
		  } else {
			  // 打印错误信息
			  printf("Read Fail! Code: %d\r\n", aht_status);
		  }

		  HAL_Delay(1000); // 1秒读取一次
	  }

}

Run放入main中调用。

【实验现象】

接上AHT20后,编译下载工程,打开串口,打印如下:

image.png

致此,AHT20已经成功的驱动。

后续将实现OpenAMP将温度显示在TouchGFX界面中。





关键词: STM32H747I-DISCO     AHT20     传感    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]