RA4M2 + AHT20 温湿度传感器实战

做环境监测类小项目时,温湿度是最常采集的两个量。AHT20 是这两年被大量使用的一颗数字温湿度传感器:I2C 接口、出厂校准、带 CRC 校验、价格便宜,比 DHT11 好伺候得多。

本文记录在 RA-Eco-RA4M2 开发板 上,用板载的 J4「I2C」排针(SCI3,P408/P409) 连接 AHT20,通过 RASC 配置 FSP 的 I2C Master,写一个完整的驱动,最后在串口 Shell 里加一条 aht 命令读数的全过程。

最终实测输出:

text 复制代码
[RA4M2 /]$ aht
AHT20 @0x38 (SCI3, P408 SCL / P409 SDA)
  temperature : 28.673 C
  humidity    : 41.431 %RH
  raw         : T=0x64B38 H=0x6A107 status=0x1C crc=ok

文章重点放在三件事上:接线为什么这么接、RASC 里怎么配、驱动里哪些地方和"想当然的写法"不一样。

说明:引脚、上拉电阻等硬件信息取自 RA-Eco-RA4M2-100PIN-V2.0 原理图;AHT20 的时序与公式取自数据手册。不同厂家的 AHT20 模块丝印顺序不一致,接线请以实物丝印为准。

一、AHT20 简介

1. 器件特点

AHT20 内部集成了电容式湿度敏感元件和热敏电阻,芯片里自带校准系数和 ADC,对外只暴露一根标准 I2C 总线。

对 MCU 来说,它的使用模型非常简单:

text 复制代码
发一条"开始测量"命令 → 等 75ms → 读回 7 个字节 → 算一下

没有寄存器地址表,没有配置寄存器,也没有休眠/唤醒模式需要设置。上电、确认校准位、触发、读数,就这么四步。

2. 关键参数

项目 参数
供电电压 2.2 V ~ 5.5 V(典型 3.3 V)
通信接口 I2C,最高 400 kHz
器件地址 0x38(7 位地址)
温度量程 −40 ℃ ~ +85 ℃
温度精度 ±0.3 ℃(典型)
湿度量程 0 ~ 100 %RH
湿度精度 ±2 %RH(20 %RH ~ 80 %RH 区间)
输出位宽 温度 20 bit、湿度 20 bit
单次测量时间 典型 75 ms
封装 3 × 3 × 1.0 mm

有两点需要提前说明,避免后面看数据时产生误解:

第一,"20 bit" 是 ADC 的分辨率,不是精度。 按 2²⁰ 换算,1 个 LSB 大约对应 0.0002 ℃ 和 0.0001 %RH,但器件实际精度是 ±0.3 ℃ / ±2 %RH。程序里输出到小数点后三位是为了不额外引入截断误差,并不代表这个数字有这么准。

第二,AHT20 出厂已经做过校准,状态字节 bit3 为 1 就表示校准系数有效。只有在 bit3 为 0 时才需要发初始化命令重新校准。

3. 和 DHT11 的对比

项目 DHT11 AHT20
接口 单总线(自定义时序) I2C
温度精度 ±2 ℃ ±0.3 ℃
湿度精度 ±5 %RH ±2 %RH
分辨率 1 ℃ / 1 %RH 20 bit
数据校验 1 字节累加和 CRC-8
对中断的敏感度 高(微秒级时序) 低(标准总线)
采样间隔 ≥ 1 s 可连续测量

DHT11 的单总线协议靠微秒级的高低电平宽度编码,主循环里一旦有别的中断插进来就容易读失败。AHT20 走标准 I2C,时序由外设硬件产生,程序上只需要处理一次"发送完成 / 接收完成"回调,稳定性差别很明显。

如果对精度要求更高,可以看 SHT30(±0.2 ℃ / ±2 %RH,I2C 地址 0x44/0x45),使用方式与 AHT20 类似,主要是命令字不同。

二、硬件连接

1. 板上已经预留了 I2C 排针

RA-Eco-RA4M2-100PIN-V2.0 上有一个 4 pin 排针 J4 ,丝印为 I2C,它直接连到 MCU 的 SCI3 通道:

J4 脚号 网络名 MCU 引脚
1 3.3V ---
2 GND ---
3 SCL3 P408
4 SDA3 P409

接 AHT20 只需要 4 根杜邦线:

text 复制代码
J4-1  3.3V  ─────→  AHT20 VDD
J4-2  GND   ─────→  AHT20 GND
J4-3  SCL3  ─────→  AHT20 SCL
J4-4  SDA3  ─────→  AHT20 SDA

2. 三个容易接错的地方

(1)不要按"顺序直插"。

市面上常见的 AHT20 模块(紫色或黑色小板)丝印顺序大多是这样:

text 复制代码
模块侧:VDD  SDA  GND  SCL
板载侧:3.3V GND  SCL3 SDA3

两边的顺序完全不一致。如果图省事把 4 根线按位置一一对上,正好会把 SCL 和 SDA 接反 ------ 这时读数的典型表现是 aht 报 no ACK from 0x38,因为地址字节在错误的线上发出去了。所以一定要按丝印逐根对应,不要按排列位置对应。

(2)供电只用 3.3V。

AHT20 芯片本身 2.2 V ~ 5.5 V 都能工作,但很多模块板带 5V 兼容设计(比如板上有电平转换或 LDO)。如果模块按 5V 供电,SDA/SCL 上的高电平可能接近 5 V,会超过 RA4M2 的 3.3 V IO 域耐压。J4-1 就是 3.3V,直接用即可,不要去接旁边的 5V 排针。

(3)不需要外加上拉电阻。

板上 R14/R15 已经用 1.5 kΩ 把 SCL3/SDA3 上拉到 3.3 V 。AHT20 模块自带的 4.7 kΩ ~ 10 kΩ 上拉与它们并联后约为 1.1 kΩ ~ 1.3 kΩ,对 100 kHz 和 400 kHz 都完全够用。反过来,如果板上没有上拉、模块也没有,就必须补,否则总线空闲时电平不确定,现象是超时而不是 NACK。

另外线尽量短(30 cm 以内),SCL/SDA 不要和 PMOD1 的 UART 线捆在一起平行走线。

3. 为什么用 SCI3 而不是 RIIC

RA4M2 有两类 I2C 外设:

类型 通道 说明
RIIC IIC0、IIC1 专用 I2C 外设,功能更全(支持多主、从机模式等)
SCI I2C SCI0 ~ SCI9 由串口外设模拟 I2C 时序,FSP 里是 r_sci_i2c

本次之所以选 SCI3,原因很直接:板载 J4 排针上的 P408/P409,在芯片引脚复用表里只有 SCI3 的 SCL/SDA 功能,没有 RIIC 的复用选项。想用 RIIC 就得从别的地方引线(比如 PMOD2 的 P410/P411),反而更麻烦。

SCI I2C 在功能上做主机读写 AHT20 这类从机绰绰有余,唯一需要注意的是它不支持多主仲裁。

三、RASC 配置

1. 添加组件

在 RASC(本工程用的是 FSP 6.6.0)里打开 configuration.xml:

text 复制代码
Stacks → New Stack → Driver → I2C Communication
  → I2C Master (r_sci_i2c)

添加后会生成一个名为 g_i2c3 的实例。然后用 SCI3 对应的引脚,在 Pins 页把 P408、P409 配成外设功能:

text 复制代码
P408 → SCI3 SCL(Peripheral mode)
P409 → SCI3 SDA(Peripheral mode)

2. 实例参数

g_i2c3 的配置如下:

参数 取值
Channel 3(即 SCI3)
Slave Address 0x38
Address Mode 7 bit
Rate Standard(100 kHz)
SDA Delay 300 ns
Bitrate Modulation Enabled
Callback sci3_i2c_callback
Interrupt Priority 2
DTC 关闭

RASC 算出的实际参数是:CKS = 0、BRR = 20、MDDR = 172、SNFR = 1,实际波特率 99981 Hz,相对 100 kHz 的误差只有 0.019%,通常不需要手动调。

关于速率的选择:板上是 1.5 kΩ 上拉,属于偏强的上拉,跑 400 kHz 完全没有问题。之所以先取 100 kHz,是为了在第一次接线上板时把"总线问题"和"速率问题"分开------100 kHz 对走线、上拉、线长的容忍度最高,先确认通信正常,再提速才有意义。

3. 生成结果

生成后的关键文件:

文件 内容
ra_gen/hal_data.c g_i2c3_ctrl、g_i2c3_cfg、g_i2c3_cfg_extend、g_i2c3 实例
ra_gen/hal_data.h 上述对象的 extern 声明
ra_gen/pin_data.c P408/P409 配置为 IOPORT_PERIPHERAL_SCI1_3_5_7_9
ra_gen/vector_data.c SCI3 TXI / TEI 两个中断向量
ra_cfg/fsp_cfg/r_sci_i2c_cfg.h 参数检查等编译期配置
ra/fsp/src/r_sci_i2c/ FSP 驱动源码

这些文件全部由 RASC 生成,不要手工改 。改了配置重新执行一次 cmake -S . -B build/Debug,生成过程会自动重跑。

4. 踩坑:ICU 用户中断的写法

这一步是本次最费时间的地方,值得单独记一下。

本工程的基础版本(一个 BLE 串口示例)里,src/pmod1_uart.c 有一段手工写的向量表:

c 复制代码
BSP_DONT_REMOVE const fsp_vector_t g_vector_table[BSP_ICU_VECTOR_NUM_ENTRIES]
    BSP_PLACE_IN_SECTION(BSP_SECTION_APPLICATION_VECTORS) = { pmod1_rxi_isr };

也就是说,整个工程唯一的 application vector 槽位被 SCI4 RXI 占用了,而且原代码还加了保护:

c 复制代码
#if VECTOR_DATA_IRQ_COUNT != 0 || BSP_ICU_VECTOR_NUM_ENTRIES != 1
#error "Migrate SCI4 RXI into generated vectors before adding other peripheral IRQs"
#endif

SCI3 I2C 需要 TXI、TEI 两个向量,加进去以后这个 #error 立刻命中,编译直接失败。

按照注释的提示,正确做法是把 SCI4 RXI 迁移到 RASC 配置里,让所有向量统一由生成器管理:

xml 复制代码
<raIcuConfiguration>
  <interrupt event="event.sci4.rxi" isr="pmod1_rxi_isr"/>
</raIcuConfiguration>

这里有个必须注意的细节:event 属性必须带 event. 前缀。

写成 event="sci4.rxi" 时,RASC 不会报错,但在日志里悄悄丢弃这条配置("User event discarded"),生成的 vector_data.c 依然是空的,编译继续失败,而且看不出原因。

迁移完成后,ra_gen/vector_data.c 里的三个槽位是:

索引 向量
0 SCI3 TXI
1 SCI3 TEI
2 SCI4 RXI

对应的 src/pmod1_uart.c 也做了简化:删掉手写向量表,接收中断改用 VECTOR_NUMBER_SCI4_RXI 常量。BLE 串口功能不受影响。

四、通信协议

1. 命令字

AHT20 只有 4 条命令,而且都是"写 3 个字节"的格式:

命令 字节序列 作用
读状态 0x71 读回 1 字节状态
初始化 / 校准 0xBE 0x08 0x00 加载校准系数,只在未校准时应调用
触发测量 0xAC 0x33 0x00 启动一次温湿度转换
软复位 0xBA 复位芯片内部状态

注意 AHT20 不是"寄存器地址 + 读"的模型。命令就是直接写进从机的数据字节,没有寄存器指针一说,所以每次读数前都要重新发触发命令,不能"发一次命令、连续读多次"。

2. 一次完整测量的时序

text 复制代码
主机                           AHT20
 │                              │
 ├── 0xAC 0x33 0x00 ──────────→ │  启动转换
 │                              │
 │        等待 75 ms(典型)     │  内部 ADC 采样
 │                              │
 ├── 读 7 字节 ───────────────→ │
 │  ←──────────── 7 bytes ───── │

等待时间不能省。手册给的是典型 75 ms,本工程统一取 80 ms 留余量;上电后要等 40 ms;发完 0xBE 初始化命令后至少等 10 ms(本工程取 20 ms)。

这三个延时都用了 R_BSP_SoftwareDelay(),是纯忙等,不依赖 SysTick,所以即使用在中断以外的地方也不会因为调度问题抖动。

3. 7 个字节怎么拆

读回来的 7 个字节结构如下:

字节 内容
buf0 状态字节
buf1 湿度 H19:12
buf2 湿度 H11:4
buf3 高 4 位 = 湿度 H3:0,低 4 位 = 温度 T19:16
buf4 温度 T15:8
buf5 温度 T7:0
buf6 CRC-8 校验值

最容易错的地方是 buf3 ------它被湿度低 4 位和温度高 4 位共用,一个字节劈成两半。如果这里移位写错了,或者把湿度温度搞反了,算出来的温度往往是 −40 ℃ 或 100 ℃ 这种极端值,很容易辨认。

拼接代码:

c 复制代码
raw_h = ((uint32_t) buf[1] << 12) | ((uint32_t) buf[2] << 4) | ((uint32_t) buf[3] >> 4);
raw_t = (((uint32_t) buf[3] & 0x0FU) << 16) | ((uint32_t) buf[4] << 8) | (uint32_t) buf[5];

buf[3] 在两次拼接里分别取"高 4 位"和"低 4 位",一个是 >> 4,一个是 & 0x0F,不能颠倒。

4. 换算公式

text 复制代码
RH[%]  = raw_h / 2^20 × 100
T[℃]  = raw_t / 2^20 × 200 − 50

温度公式里的 −50 是因为 20 位满量程对应 −50 ℃ ~ +150 ℃ 的 200 ℃ 跨度,不是 0 ~ 200 ℃。

拿实测数据核对一遍:读回的 raw_t = 0x64B38 = 412472,

text 复制代码
412472 / 1048576 × 200 − 50 = 28.6735 ℃

和打印出来的 28.673 C 完全对上。湿度 raw_h = 0x6A107 = 434439:

text 复制代码
434439 / 1048576 × 100 = 41.4310 %RH

也对上了。这种"拿 raw 值手算一遍、再和显示值比对"的验证方法,对任何传感器驱动都值得做一遍,能很快发现移位、字节序、公式之类的错误。

5. 状态字节的含义

位 名称 含义
bit7 BUSY 1 = 正在测量,此时读取会拿到旧数据或无效数据
bit6:5 MODE 00 = 正常模式,01 = 命令模式
bit4 --- 保留
bit3 CAL 1 = 校准系数已加载
bit2:0 --- 保留(AHT20 实测恒为 100)

所以 status = 0x1C = 0b0001_1100 的含义是:空闲、正常模式、已校准,最后三位是芯片固定的保留值,不是故障位。

6. CRC-8

AHT20 用 CRC-8 校验前 6 个字节,参数是:

text 复制代码
多项式 0x31、初值 0xFF、不取反、不异或输出
c 复制代码
static uint8_t aht20_crc8(const uint8_t * p_data, uint32_t len)
{
    uint8_t crc = 0xFFU;

    for (uint32_t i = 0U; i < len; i++)
    {
        crc ^= p_data[i];
        for (uint32_t bit = 0U; bit < 8U; bit++)
        {
            if (0U != (crc & 0x80U))
            {
                crc = (uint8_t) (((uint32_t) crc << 1) ^ 0x31U);
            }
            else
            {
                crc = (uint8_t) (crc << 1);
            }
        }
    }

    return crc;
}

驱动里的处理方式是:CRC 不符时数据照常返回,只把标志位置成 NG,由上层决定怎么处理。这样接错线或受干扰时,能看到"数值明显不对 + crc=NG"的组合,比直接返回错误更容易定位问题。

五、驱动实现

1. 文件划分

文件 职责
src/aht20.c / .h 传感器协议:命令、时序、CRC、换算
ra_gen/hal_data.c RASC 生成的 g_i2c3 实例(传输层)
src/uart_shell.c aht 命令、输出格式化
src/sci9_uart.c PC 调试串口、board_millis() 毫秒计时

传感器逻辑与 FSP 实例解耦得比较干净:如果以后换到板上的其它 I2C 通道(或换芯片型号),只需要改 aht20.c 里对 g_i2c3 的引用。

2. 关键点:FSP 的 I2C 是异步的

这是写 RA 系列 I2C 驱动时最容易踩的坑。

g_i2c3.p_api->write() 返回 FSP_SUCCESS 只代表"传输已经启动",不代表"已经传完"。真正的结束时刻在中断回调里:

c 复制代码
void sci3_i2c_callback(i2c_master_callback_args_t * p_args)
{
    s_xfer_event = p_args->event;
    s_xfer_done  = true;
}

如果照着一个"同步 API"的思路写:

c 复制代码
/* 错误示范:buffer 里还是旧数据,函数就已经返回了 */
g_i2c3.p_api->read(&g_i2c3_ctrl, buf, 7, false);
raw_h = ... buf[1] ...;

buf 里要么是 0,要么是上一次读的残留值,但代码没有任何报错 ------ 这种 bug 极难查。

所以驱动里用"完成标志 + 超时"把异步接口包成阻塞调用:

c 复制代码
static fsp_err_t aht20_i2c_wait(void)
{
    uint32_t start = board_millis();

    while (!s_xfer_done)
    {
        if ((uint32_t) (board_millis() - start) >= AHT20_I2C_TIMEOUT_MS)
        {
            (void) g_i2c3.p_api->abort(&g_i2c3_ctrl);
            return FSP_ERR_TIMEOUT;
        }
    }

    if (I2C_MASTER_EVENT_TX_COMPLETE == s_xfer_event) { return FSP_SUCCESS; }
    if (I2C_MASTER_EVENT_RX_COMPLETE == s_xfer_event) { return FSP_SUCCESS; }

    /* I2C_MASTER_EVENT_ABORTED:从机对地址或数据回了 NACK */
    return FSP_ERR_ABORTED;
}

超时值取 20 ms。100 kHz 下传 10 个字节不到 1 ms,20 ms 已经是两个数量级的余量,正常通信绝不会触发;一旦触发,说明总线被拉死或者根本没有从机响应。

超时后要调 abort(),否则下一次传输会卡在"控制器仍然忙"的状态。

3. 两种错误要分开报告

封装后把错误分成两类,直接决定排查方向:

返回码 触发条件 大概率原因
FSP_ERR_ABORTED 从机回了 NACK 地址错、SCL/SDA 接反、传感器没供电
FSP_ERR_TIMEOUT 20 ms 内没有完成事件 总线被拉低、上拉电阻缺失、线路断开

驱动里配上对应的提示语:

c 复制代码
case FSP_ERR_ABORTED:
    return "no ACK from 0x38; check JUMPER order (SCL/SDA) and 3.3V";
case FSP_ERR_TIMEOUT:
    return "I2C timeout; check SDA/SCL wiring and pull-ups";

上板调试时,看到具体是"没有应答"还是"一直超时",能省掉很多盲猜。

4. 出错后要 close/open 一次

SCI I2C 在发生 NACK 或超时后,内部可能有残留的 FER/ORER 错误状态,下一次 write() 会直接失败。

驱动在每次传输失败时调一次 recover:

c 复制代码
static void aht20_i2c_recover(void)
{
    (void) g_i2c3.p_api->close(&g_i2c3_ctrl);
    s_opened = false;
    s_ready  = false;
}

下次调用时 aht20_i2c_open() 会重新 open,把通道状态清干净。这样"拔掉传感器→再插上→重新执行 aht"就能恢复正常,不需要复位整板。

5. 定点换算,不引入浮点 printf

裸机上用 %.3f 打印浮点,会链进一整份 printf 浮点支持(newlib 下通常十几 KB)。AHT20 的数据本来就是整数,直接用定点数:

c 复制代码
/* RH[%] = raw / 2^20 * 100,换算成 0.001 %RH;T[℃] = raw / 2^20 * 200 - 50 */
p_out->rh_milli_pct = (uint32_t) ((((uint64_t) raw_h * 100000ULL) + (1ULL << 19)) >> 20);
p_out->temp_milli_c = (int32_t) (((((int64_t) raw_t * 200000LL) + (1LL << 19)) >> 20) - 50000LL);

两个细节:

  • 先乘后除 ,用 64 位中间量避免溢出(raw_h × 100000 最大约 1e11,超过 32 位)
  • 加 1 << 19 是四舍五入,右移 20 位相当于除以 2²⁰,加半个 LSB 就能把截断改成四舍五入

打印时再拆成整数部分和小数部分:

c 复制代码
(void)snprintf(tb, sizeof(tb), "%s%ld.%03ld",
               (t_milli < 0) ? "-" : "",
               (long)(t_abs / 1000), (long)(t_abs % 1000));

负温度要单独处理符号。如果直接把负数交给 %ld.%03ld,整数部分取整后是 0,小数部分仍是负数,打印出来会变成 0.-123 这种结果。

六、Shell 命令与实测结果

PC 端串口(SCI9)参数:115200、8N1、无流控。

1. 命令

命令 功能
aht 读一次温湿度
aht read 同上
aht status 只读状态字节,检查 busy / calibrated 位
aht init 重新发 0xBE 初始化(校准位为 0 时会自动执行)

设计上有两点考虑:

第一,启动时自动初始化一次。 uart_shell_init() 里会调 aht20_init(),如果成功,后续 aht 命令直接进入测量流程;如果失败,只在启动横幅里打一行原因,不影响 Shell 其它命令和 BLE 功能。也就是说,没接传感器时板子照样能正常用。

第二,aht status 单独一条命令。 读数异常时,先看状态字节比反复执行 aht 有效:calibrated=0 说明是初始化问题,busy=1 说明上一次测量还没结束(时序没等够)。

2. 实测输出

text 复制代码
[RA4M2 /]$ aht
AHT20 @0x38 (SCI3, P408 SCL / P409 SDA)
  temperature : 28.673 C
  humidity    : 41.431 %RH
  raw         : T=0x64B38 H=0x6A107 status=0x1C crc=ok

[RA4M2 /]$ aht status
AHT20 status=0x1C busy=0 calibrated=1

输出的设计思路是把 raw 值一起打出来。这一步很省事:

  • 温度湿度明显不对时,直接看 raw 值判断是"传感器问题"还是"换算问题"
  • crc=ok 说明 7 个字节完整无误,总线时序、上拉、电平都是好的
  • 出问题时把 raw 值贴到手册公式里手算,几秒钟就能定位

3. 数据自洽性核对

项目 raw 计算过程 显示值
温度 0x64B38 = 412472 412472 / 2²⁰ × 200 − 50 = 28.6735 28.673 ℃
湿度 0x6A107 = 434439 434439 / 2²⁰ × 100 = 41.4310 41.431 %RH

两组数据都在 0.001 的精度内吻合,说明字节拼接、移位、公式、定点换算整条链路都是对的。

读数本身也在合理区间:28.67 ℃ / 41.4 %RH,属于典型室内环境。需要注意的是,MCU 和稳压器自身会发热,板子贴着传感器装时,测到的温度通常比环境温度高 0.3 ℃ ~ 1 ℃,做演示时值得说明一句。

七、常见问题排查

现象 可能原因 处理
no ACK from 0x38 SCL/SDA 接反;传感器没供电;地址不对 按丝印逐根核对;量模块 VDD 是否为 3.3 V
I2C timeout 上拉缺失;SDA 被从机拉低后不释放;线太长 检查上拉电阻;示波器看总线空闲电平是否为高
读数稳定但明显偏差 供电不是 3.3 V;传感器靠近热源 换 J4-1 供电;远离稳压器和 MCU
温度永远是 −40 ℃ 或 100 ℃ buf[3] 的高低位取反了,或温湿度 raw 搞反 检查 >> 4 和 & 0x0F
crc=NG 线太长、干扰、速率过高 缩短线长;降到 100 kHz;检查地线
连续读第二次就失败 两次测量间隔太短,芯片还在忙 保证两次 aht 之间有 80 ms 以上间隔
编译报 ICU 向量表错误 手工向量表占用了 application vector 槽位 把中断迁移到 <raIcuConfiguration>,注意 event. 前缀

八、写在最后

这个传感器本身不复杂,但整个过程里有几个点比"抄一段初始化代码"更有价值:

  1. RASC 生成的配置不要手改 。改 configuration.xml 后重新 configure,生成器会把 hal_data.c、pin_data.c、vector_data.c 全部重写,手改的部分会丢。
  2. FSP 的 I2C 是异步 API 。FSP_SUCCESS 只表示已启动,不等传输完成。所有 RA 系列的 r_i2c_master、r_sci_i2c、r_spi、r_sci_uart 都是这个模型,理解一次可以套用到整个 FSP。
  3. 出错时要能区分"No ACK"和"超时"。前者是通信对象不对,后者是物理层有问题,方向完全不同。
  4. 把 raw 值打出来。多花的几行代码,省下的是上板时几十分钟的排查。
  5. 拿 raw 值手算一遍。这是验证传感器驱动最便宜、最有效的手段。

当前工程已经具备完整的温湿度采集能力,后续如果要继续扩展,可以直接在这个基础上加:

  • 连续采样统计(aht watch,含 min/max/avg)
  • CSV 日志输出,导出到 Excel 画曲线
  • 把 rate_standard 改成 rate_fast,跑 400 kHz
  • 温度或湿度越限时主动告警,为联动控制(风扇、加热)做准备

参考文档

相关推荐
我是人✓2 小时前
STM32上跑FreeRTOS:实时任务调度全解析
stm32·单片机·嵌入式硬件
ITKEY_4 小时前
Arduino cli 写hello world
单片机
HQB20264 小时前
EG1195S|屹晶微电子 120V 外置 MOS 异步降压 DC‑DC 控制器
嵌入式硬件·硬件工程
俊基科技5 小时前
A-59P 多功能语音处理模组:一芯集成 AI 降噪、消回音与波束成形,重塑全双工通话体验
嵌入式硬件·硬件开发·ai降噪·语音模块·回音消除
POMAGTOR5 小时前
硬件接口选型|磁吸式连接器原理与应用,不止磁吸充电
网络·嵌入式硬件·电脑
深圳老胡6 小时前
C语言实现C++面向对象程序设计示例
笔记·stm32·单片机·代码规范
2501_922639586 小时前
率能半导体 SS6226双通道直流电机驱动芯片:7.2V/1.4A双路,覆盖玩具、小家电、直流有刷电机三大应用场景
单片机·嵌入式硬件·硬件工程
LCG米6 小时前
STM32时钟树配置实测:HSE起振失败回退HSI、PLL分频倍频计算与Flash等待周期陷阱
stm32·单片机·嵌入式硬件·嵌入式
巴巴媛6667 小时前
【Debug1】程序死机,while(1)循环,怎么办?
c语言·笔记·stm32·嵌入式硬件·bug