做环境监测类小项目时,温湿度是最常采集的两个量。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. 前缀 |
八、写在最后
这个传感器本身不复杂,但整个过程里有几个点比"抄一段初始化代码"更有价值:
- RASC 生成的配置不要手改 。改
configuration.xml后重新 configure,生成器会把hal_data.c、pin_data.c、vector_data.c全部重写,手改的部分会丢。 - FSP 的 I2C 是异步 API 。
FSP_SUCCESS只表示已启动,不等传输完成。所有 RA 系列的r_i2c_master、r_sci_i2c、r_spi、r_sci_uart都是这个模型,理解一次可以套用到整个 FSP。 - 出错时要能区分"No ACK"和"超时"。前者是通信对象不对,后者是物理层有问题,方向完全不同。
- 把 raw 值打出来。多花的几行代码,省下的是上板时几十分钟的排查。
- 拿 raw 值手算一遍。这是验证传感器驱动最便宜、最有效的手段。
当前工程已经具备完整的温湿度采集能力,后续如果要继续扩展,可以直接在这个基础上加:
- 连续采样统计(
aht watch,含 min/max/avg) - CSV 日志输出,导出到 Excel 画曲线
- 把
rate_standard改成rate_fast,跑 400 kHz - 温度或湿度越限时主动告警,为联动控制(风扇、加热)做准备
参考文档