概述
官方基础工程已预置 SHT3x 驱动(components/SHT3x)并在 sht3x_read_task 任务中完成初始化与周期读取;sht3x 查询工具已在创建 MCP 工具第 5 章注册。本章接线并实测 AI 查询温湿度。
SHT3x 走 I²C 总线(SDA/SCL 两根线),开发板使用软件模拟 I²C(Bsp/i2c 目录),任意两个 GPIO 即可当 I²C 用,本项目使用 PB6(SDA)/PB7(SCL)。
官方基础工程已预置 SHT3x 驱动(components/SHT3x),无需复制。若从零工程起步,才需要从 SCBB 库复制:
mkdir -p components/SHT3x
cp ~/AiPi-SCBB/SHT3x/axk_sht3x.c components/SHT3x/
cp ~/AiPi-SCBB/SHT3x/axk_sht3x.h components/SHT3x/
官方基础工程已将 I²C 宏绑定到软 I²C 驱动(Bsp/i2c)。从零工程起步时需要手动绑定,写法如下(已验证):
/* axk_sht3x.h —— I2C 操作函数绑定 */
#if __has_include("stm32f10x_bsp_i2c.h")
#include "stm32f10x_bsp_i2c.h"
#define AXK_SHT3X_DELAY_MS(x) delay_ms(x)
#define AXK_SHT3X_I2C_ACLL(_func, ...) bsp_i2c_##_func(__VA_ARGS__)
#else
#error "Please include the appropriate I2C header for AXK_SHT3X."
#endif
#define AXK_SHT3X_ADDRESS 0x44 // SHT3x 默认 I2C 地址
bsp_i2c_##_func 的含义:驱动里写 AXK_SHT3X_I2C_ACLL(start),编译时展开为 bsp_i2c_start()。因此工程中必须有软 I²C 的 BSP 实现(Bsp/i2c 目录,本项目已实现):
/* Bsp/i2c/stm32f10x_bsp_i2c.h —— 本项目已实现 */
void bsp_i2c_init(void); // 初始化(PB6/PB7 配为开漏输出)
void bsp_i2c_start(void); // 起始条件
void bsp_i2c_stop(void); // 停止条件
void bsp_i2c_send_ack(u8 ack); // 发送应答
u8 bsp_i2c_wait_ack(void); // 等待应答
void bsp_i2c_send_byte(u8 _dat); // 发送一个字节
u8 bsp_i2c_read_byte(void); // 读取一个字节
官方基础工程 components/CMakeLists.txt 已包含 SHT3x 目录;sht3x_read_task 任务已实现初始化与周期读取(0x2c06 模式,10Hz)。从零工程起步时的写法如下:
/* 初始化:返回 0 表示成功 */
uint8_t res = axk_sht3x_init();
if (res != 0) { log_error("sht3x not driver"); sht30_is_init = false; }
else { sht30_is_init = true; log_info("sht3x init OK!"); }
/* 周期读取:0x2c06 = 周期测量模式,10Hz,高重复性 */
double temperature = 0.0, humidity = 0.0;
res = axk_sht3x_read(0x2c06, &temperature, &humidity);
if (res == 0) {
g_temp = temperature; // 保存全局,供 OLED / MCP 工具使用
g_hum = humidity;
} else {
log_error("sht3x read error: %d", res);
}
🧩 九章板温湿度传感器已板载接好(SHT30/GXHT30C,板边 4Pin 排针引出),无需接线。下表供对照认识引脚,或扩展外部 SHT30 模块时参考。
| SHT30 模块 | 接开发板 |
|---|---|
| SDA | PB6 |
| SCL | PB7 |
| VCC | 3.3V |
| GND | GND |
⚠️ 同一条 I²C 总线上的邻居:SHT3x(地址 0x44)与 CH224(地址 0x22)共用 PB6/PB7 软 I²C 总线,地址不冲突可共存。但调试时两个设备都要接好,否则总线被未接设备的状态干扰,两个都读不到。
sht3x 查询工具的回调与注册已在创建 MCP 工具第 5 章完成(sht3x_query_set_handler 读取温湿度并以 JSON 回执)。工具回调核心逻辑如下(对照理解):
/* ---- SHT3x 温湿度查询工具回调 ---- */
static void sht3x_query_set_handler(void *arg) {
(void)arg;
log_info("[sht3x] query called");
double temp = 0.0, hum = 0.0;
if (axk_sht3x_read(0x2c06, &temp, &hum) == 0) {
char rsp[64];
snprintf(rsp, sizeof(rsp), "{\"temperature\":%.1f,\"humidity\":%.1f}", temp, hum);
log_info("[sht3x] read ok: %s", rsp);
emMCP_ResponseValue(rsp);
} else {
log_error("[sht3x] read failed");
emMCP_ResponseValue(emMCP_CTRL_ERROR);
}
}
static void sht3x_query_check_handler(void *arg) {
sht3x_query_set_handler(arg); // 查询 = 执行,直接复用
}
/* 注册(StartDefaultTask 中,紧跟 relay 之后) */
memset(&t, 0, sizeof(t));
t.name = "sht3x";
t.description = "温湿度查询工具,返回当前温度和湿度";
t.setRequestHandler = sht3x_query_set_handler;
t.checkRequestHandler = sht3x_query_check_handler;
emMCP_AddToolToToolList(&t);
别忘了:
emMCP_config.h中MCP_SERVER_TOOL_NUMBLE_MAX已设为 7(本项目 4 个工具,足够,无需修改);若使用手动 JSON 注册模式,需在mcp_tools_fmt字符串中包含该工具的 JSON(见 创建 MCP 工具)。
-
编译烧录,串口日志应出现:
[INFO] sht3x init OK! -
串口手动测试(USART2,115200):
{"role":"AI","msgType":"MCP","data":{"name":"sht3x","args":{}}}预期回执:
mcp-responsive {"role":"MCU","msgType":"status","data":{"temperature":26.5,"humidity":45.2}} -
AI 语音测试:说 “小安,现在温度多少?” 或 “查询温湿度”,AI 调用 sht3x 工具后语音回复结果。
常见问题与踩坑提示
🔧 日志一直 sht3x not driver / SHT3x ERR
原因:① 接线错 ② 供电错 ③ 总线被 CH224 干扰
解决:① SDA→PB6、SCL→PB7(接反必挂)② 模块 VCC 接 3.3V(带电平转换的也可 5V)③ CH224 也必须接好,同一 I²C 总线少一个设备都可能影响另一个
🔧 读数一直是 0 或 -1
原因:I²C 通信失败
解决:检查 bsp_i2c 的引脚宏(Bsp/i2c 头文件里 GPIO_SDA/GPIO_SCL 是否为 PB6/PB7);用示波器/逻辑分析仪看 SDA/SCL 波形
🔧 读出的温度湿度值明显不对(如 80 度)
原因:① 传感器被遮挡/靠近热源 ② 接线接触不良
解决:传感器离开热源;重新插拔杜邦线;检查 GND 是否稳定共地
🔧 温度湿度翻倍或一半
原因:测量模式/数据解析问题
解决:用 axk_sht3x_read(0x2c06, ...) 标准模式;确认是 SHT30 而非其他型号(SHT31/35 兼容但校准略有差异)
🔧 两个 I²C 设备(SHT3x + CH224)只有一个能读
原因:总线时序/上拉问题
解决:确认 PB6/PB7 外部有上拉电阻(或模块自带);两个设备地址不同(0x44 / 0x22),不会冲突,通常是接线
🔧 查询工具回执报错 data:"false"
原因:读取瞬间失败(偶发)
解决:代码里加"重试一次"逻辑;检查传感器电源是否稳定
🔧 AI 查询温湿度播报失败,日志里 read ok: {"temperature":,"humidity":}(数值是空的)原因:⚠️ 浮点 printf 未链接(newlib-nano 默认不支持 %f) 解决:见下方"浮点 printf 链接选项",影响所有用 %.1f 的回执(sht3x 温湿度、ch224 电压)
🔧 日志乱码/丢字节(如中文变 ���、JSON 字段残缺)
原因:多任务同时打日志,HAL_UART_Transmit 非重入互相打坏
解决:见下方"日志互斥锁"说明
🔧 加了 sht3x 工具后 AI 找不到
原因:工具上限不够
解决:MCP_SERVER_TOOL_NUMBLE_MAX 保持 7(4 个工具足够);若自定义更多工具记得 +1 并重新编译
🔧 手动 JSON 测试返回的温度是字符串不是数字
原因:回执格式问题
解决:确认 snprintf 生成的是 {"temperature":26.5,"humidity":45.2}(数字无引号),AI 才能正确解析
传感器状态速查
上电后串口日志 [INFO] sht3x init OK! 表示通信正常;[INFO] ch224 init OK! 表示 CH224 正常。两者均正常但读数为 0 时,优先排查杜邦线接触不良——这是最常见的接触类故障。
⚠️ 必读:浮点 printf 链接选项(%.1f 输出为空的根因)
现象:AI 查询温湿度播报"查询失败";串口日志显示 [sht3x] read ok: {"temperature":,"humidity":}——温湿度数值是空的。屏幕/任务日志(用 %d)显示正常。
根因:工程使用 newlib-nano(--specs=nano.specs),它默认不包含浮点格式化代码。snprintf 里的 %.1f 直接输出空字符串,拼出的 JSON 变成 {"temperature":,"humidity":}(非法 JSON),AI 端解析失败 → 播报失败。
修复:在两个 CMake 工具链文件里给链接选项加 -u _printf_float:
# cmake/gcc-arm-none-eabi.cmake
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --specs=nano.specs -u _printf_float")
# cmake/starm-clang.cmake(如使用 STARM_HYBRID 工具链)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --gcc-specs=nano.specs -u _printf_float")重新编译后 FLASH 会增加约 4~5KB(浮点格式化代码),%.1f 恢复输出。
影响范围:所有用浮点格式化的工具回执——本项目里 sht3x(温湿度)、ch224_voltage(电压)都用 %.1f,一并修复。
⚠️ 必读:日志互斥锁(多任务日志乱码)
现象:串口日志中文乱码(查询温湿度 变成 查���温湿度)、字段残缺("msgType" 变 "msgT")、两行日志拼在一起——但 AI 播报是正常的(说明数据链路没问题,只是日志显示坏了)。
根因:log_printf 底层是 HAL_UART_Transmit(非重入)。defaultTask(处理消息)和 sht3x_read_task(每秒打温度日志)同时打日志时,字节互相穿插打坏。日志打印得越频繁(如 1.5Mbps 调试口),越明显。
修复:给 log_printf 加静态互斥锁(components/log/log.c):
#include "FreeRTOS.h"
#include "semphr.h"
static StaticSemaphore_t g_log_mutex_buf;
static SemaphoreHandle_t g_log_mutex = NULL;
void log_printf(const char *format, ...) {
char buffer[LOG_BUFFER_SIZE];
va_list args;
va_start(args, format);
int len = vsnprintf(buffer, LOG_BUFFER_SIZE, format, args);
va_end(args);
if (len <= 0) return;
/* 互斥保护串口发送,避免多任务竞争导致日志字节交错 */
if (g_log_mutex == NULL) {
g_log_mutex = xSemaphoreCreateMutexStatic(&g_log_mutex_buf);
}
if (g_log_mutex != NULL &&
xSemaphoreTake(g_log_mutex, pdMS_TO_TICKS(20)) == pdTRUE) {
HAL_UART_Transmit(&huart1, (uint8_t *)buffer, len, 100);
xSemaphoreGive(g_log_mutex);
}
}要点:
- 用
xSemaphoreCreateMutexStatic(静态分配),不要用动态版——FreeRTOS 堆被 WS2812 缓冲占用后可能分配失败(见灯带案例内存说明); log_printf只能在任务上下文调用(main()里调度器启动前调用也安全,因为静态锁不依赖堆);不要在中断里打日志(xSemaphoreTake在中断上下文会崩);- 若只想快速验证,可先临时删掉
sht3x_read_task里的每秒日志,确认乱码消失即证明是日志竞争。

