文章目录
系列最后一篇。前十三章,每个模块都单独跑通过:传感器能读、PWM 能调、按键能响应、蓝牙能通、MQTT 能连、网页能开。按说拼起来就该能用。
但"每个零件都好"和"整机好"是两件事。我联调那一晚,五个问题全是零件之间的:
- 蓝牙测完忘了拨回 WiFi 侧,进 MQTT 全 FAIL,我还在查代码;
- MQTT 初始化要十几秒,我等不及连按了两下 KEY4,第二下被丢弃,日志里一句
[KEY4] busy我根本没看;- 网页点了风扇,转了两秒自己停了 —— 因为板子停在自动模式,自动逻辑每 2 秒把它覆盖掉;
- 看到一条
[PUB] FAILED就判定"MQTT 坏了",其实下一条就 OK;- 烧完程序没按复位,串口里还是旧程序的日志,对着日志查了半天新功能。
这一篇不再讲"某个模块怎么实现",只讲怎么按顺序把整机验一遍、怎么读日志、出问题时怎么在 30 秒内定位到哪一层,最后给一份能直接照着念的演示脚本。
一、先把"真相源"定下来
1.1 联调时你有三个窗口
整机联调时,同时能观察到现象的地方有三个:
| 窗口 | 看什么 | 可信度 |
|---|---|---|
| 串口助手(USART1,115200) | 板子自己的日志:模式、传感器、命令、ESP 回包 | ★★★★★ 唯一"真相" |
| 实物(风扇/加热/舵机/LED) | 到底动没动 | ★★★★☆ 真实但滞后 |
| H5 网页 | 展示状态、连接状态 | ★★☆☆☆ 只是"展示" |
为什么 H5 只能给两星?因为它是本地乐观更新 + 单向订阅:
- 按钮颜色是网页自己翻的,不代表设备执行了(第 13 章 6.4);
- 网页只订阅上报主题,收不到设备的命令回执(第 13 章 6.7);
- "MQTT 已连接"只代表浏览器连上了 broker,不代表板子在线(第 13 章 4.4)。
所以:排查永远以串口为准,H5 只用来确认"最后一跳"通不通。
1.2 一张分层真相表
联调时脑子里的第一张表应该是这个 —— 出问题先定位到层,再动手:
第 7 层 H5 显示 / 控制 ← 只有前 6 层全绿,这一层才有意义
第 6 层 broker 收发 ← 看 [PUB] OK、看 MQTTX
第 5 层 ESP-AT 会话 ← 看 +MQTTCONNECTED、setMqttPara
第 4 层 WiFi 连接 ← 看 connectWifi
第 3 层 USART2 物理链路 ← 看 [ESP] online
第 2 层 传感器 + 执行器 ← 看 [ENV]、看实物
第 1 层 供电 + 时钟 + 串口 ← 看有没有任何日志
下层没通,上层的一切现象都不可信。 这句话在第 1 章就写过,到第十四章它才真正发挥威力:因为只有整机联调时,上层才会出现"看起来能动其实没动"的假象。
1.3 联调工具清单
别小看这张表,缺一样就会多花一小时:
| 工具 | 用途 | 缺了会怎样 |
|---|---|---|
| 串口助手(SSCOM/XCOM) | USART1 115200 | 没有真相源,只能瞎猜 |
| 浏览器 + F12 | H5 与 Console | 不知道网页哪一步断了 |
| MQTTX / EMQX 在线客户端 | 独立于网页验证 broker | 分不清是网页问题还是设备问题 |
| 手机蓝牙调试 App | 蓝牙层 | 只能靠猜 |
| 万用表 | 量 3.3V / 5V / 通断 | 供电问题查不出来 |
| 手机热点 | 备用网络 | 校园网封 8084 时抓瞎 |
| 一张纸 | 打勾验收清单 | 漏测,答辩现场才发现 |
1.4 一个反直觉的规矩
联调时只改一个变量。 这条听着像废话,但联调现场最容易犯的错就是"同时拨了开关、改了代码、换了热点",然后现象变了,你根本不知道是谁的功劳。
每次只动一处,动完立刻看串口。慢,但是唯一可靠的方式。
二、上电与模式:把状态走对
2.1 固定上电顺序(七步)
① 拨码开关打到蓝牙侧(先验蓝牙,链路最短)
② 插 ST-Link,烧录固件
③ 烧完手动按一下板子的 RESET ← 别省这一步
④ 打开串口助手:USART1,115200,8N1
⑤ 看启动日志(应该只有一两行)
⑥ 蓝牙验证 → 拔蓝牙 / 拨到 WiFi 侧 → KEY2 退出 → KEY4 → KEY4 进 MQTT
⑦ 打开 H5,观察数据;点按钮,观察实物
第 ③ 步单独拎出来说:烧录成功 ≠ 新程序在跑。Keil 下载后有时不会自动复位,尤其勾了 “Reset and Run” 之外的情况。日志对不上新功能时,第一件事是手动复位一次,而不是去翻代码。
2.2 按键语义总表(一张表看完 8 种)
按键行为完全取决于当前模式,这是本项目最容易被自己绕晕的地方:
| 按键 | 非手动模式(自动/远程) | 手动模式(MODE_MANUAL) |
|---|---|---|
| KEY1(PC2) | 串口周期打印开关,[KEY1] send_flag=1/0 | 风扇 开/关,[MANUAL] FAN ON/OFF |
| KEY2(PC3) | 进手动模式 | 退出手动 → 回自动模式,[MODE] Auto ... |
| KEY3(PA1) | 进自动模式 | 窗帘 拉开/关闭,[MANUAL] CURTAIN OPEN/CLOSE |
| KEY4(PB10) | 进远程 / 蓝牙↔MQTT 切换 | 加热 开/关,[MANUAL] HEATER ON/OFF |
三个必须记住的反直觉点:
- 手动模式下 KEY2 是"回到自动",不是"退出手动到别处"。 因为手动模式的另一头就是自动(代码里写死
MODE_MANUAL ? MODE_AUTO : MODE_MANUAL)。 - 手动模式下 KEY4 是加热,不会切网络协议。 所以你想从"MQTT 远程"切回蓝牙,必须先 KEY2 离开手动,再在远程模式里按 KEY4。
- 进任何模式都会"全关"。
Switch_Mode()无条件把三路 CCR 清 0、标志清零,进手动时还会把窗帘归位到"关闭"(这样标志位 0 和实物一致,之后按 KEY3 一定能看到动作)。
2.3 KEY4 的三段循环,以及为什么按快了没用
KEY4 在非手动模式下的状态机:
当前不是远程
│ 按 KEY4
▼
[MODE] Remote (KEY4) | Bluetooth ON
g_net_req = NET_REQ_BT
│ 主循环执行
▼
[NET] Bluetooth @9600
│ 按 KEY4(g_net == NET_BT)
▼
[KEY4] request MQTT
│ 主循环执行(首次要十几秒)
▼
[NET] Switch HW dial to WiFi(W_TXD/W_RXD)! MQTT init ~10s...
...
[NET] MQTT ready (WiFi + broker + sub all OK)
│ 按 KEY4(g_net != NET_BT)
▼
[KEY4] request Bluetooth → [NET] Bluetooth @9600
关键在中间那个"十几秒":
else if(g_net_busy)
{
printf("[KEY4] busy: MQTT init in progress, ignored\r\n");
}
g_net_busy 在 Mqtt_Server_Init() 期间是 1。这时按 KEY4 直接被丢弃,只打印一行 [KEY4] busy。
为什么必须丢?因为 Mqtt_Server_Init() 是一长串 发指令 → 等应答 的阻塞流程。半路插进一条"切回蓝牙"的请求,会在 AT 指令流中间切波特率,把 ESP 彻底搅乱 —— 后面所有指令都回 ERROR,你还会以为是 ESP 坏了。
所以:进 MQTT 后耐心等
[NET] MQTT ready出来再动按键。 屏幕上没反应不代表死机,只代表在等 ESP 回话。
2.4 为什么开机不连 WiFi
这一点和第 12 章呼应,但联调时特别重要:
// 初始状态:手动模式,全关
Switch_Mode(MODE_MANUAL);
// ESP-12F 改为"按需初始化":开机不连WiFi/MQTT,避免拨在蓝牙侧时白等十几秒。
// 首次按 KEY4 切到 MQTT 模式时,才由主循环的 g_net_req 处理段调用 Mqtt_Server_Init()。
好处是开机 1 秒内就有日志、按键立刻可用;代价是首次进 MQTT 必须等十几秒。这是设计取舍,不是 bug。
2.5 上电日志应该长什么样(三段)
第一段:冷启动
[MODE] Manual (KEY2) | Keys: 1=FAN 3=CURTAIN 4=HEATER
如果一分钟过去了连这一行都没有 → 第 1 层就挂了(供电 / USART1 接线 / MicroLIB / 波特率)。
第二段:进入远程(默认蓝牙)
[MODE] Remote (KEY4) | Bluetooth ON
[NET] Bluetooth @9600
注意 [MODE] Remote ... | Bluetooth ON 里的 “Bluetooth ON” 是写死的文案,不代表此刻真的走蓝牙。真正的协议状态看紧接着的 [NET] Bluetooth @9600 或 [NET] WiFi/MQTT @115200。这是日志本身的一个小瑕疵。
第三段:首次进 MQTT(完整正常流程)
[KEY4] request MQTT
[NET] Switch HW dial to WiFi(W_TXD/W_RXD)! MQTT init ~10s...
[NET] MQTT init start...
[ESP] online @115200
setEchoOff : OK!!!
setWifiMode:OK!!!
connectWifi : OK!!!
[NET] link after wifi: connected=0 recv...
[NET] reset MQTT link (DISCONN + CLEAN)...
setMqttPara : OK!!!
MqttConn : OK!!!
MqttSub : OK!!!
[NET] test publish: {"t":27,"h":45,"a":2,"c":612,"v":123}
MqttPub(test) : OK!!!
[NET] MQTT ready (WiFi + broker + sub all OK)
[NET] WiFi/MQTT @115200
[PUB] OK
[PUB] OK
...
只要看到 [NET] MQTT ready 和后面连续的 [PUB] OK,固件端就完全正常了。 之后所有问题都在 broker / 网页那一侧,不用再碰代码。
2.6 一个容易漏掉的状态细节
Switch_Mode() 会把 g_net 置回 NET_BT,但不在这里切波特率(因为可能在中断里调用,而切波特率要等待)。
后果:如果你在 MQTT 模式下直接按 KEY2 进手动,USART2 仍然是 115200,而 g_net 已经写成 BT。
这时如果蓝牙模块还接着,你发的命令在 9600 的蓝牙看来就是乱码;反过来蓝牙回的也是乱码。而且手动模式下 KEY4 是加热,你没法把波特率切回去。
恢复办法只有一个:KEY2 回自动 → KEY4 进远程 → 再按 KEY4 切蓝牙([NET] Bluetooth @9600)。
这是"整机联调"里典型的跨模块副作用:逻辑正确,但物理通道没跟着回来。日志里看不出来,只有"蓝牙突然不听话"这个现象。
2.7 一次完整的正常流程(逐秒对照)
把上面所有片段串起来,就是一条完整的时间线。第一次联调时照着这个对照,哪一行没出现,就停在哪一行。
t=0.0s 上电/复位
[MODE] Manual (KEY2) | Keys: 1=FAN 3=CURTAIN 4=HEATER
t=0.5s 按 KEY1(手动模式下 = 风扇)
[ENV] Temp:27.5C Humi:45.0% AQI:2 TVOC:123 ppb eCO2:612 ppm Air:GOOD Mode=0
[MANUAL] FAN ON
t=1.5s 按 KEY1 再一下
[MANUAL] FAN OFF
t=2.0s 按 KEY2(进自动)
[MODE] Auto | Temp Control ON
(之后每 2 秒一条 [ENV],状态变化时才多打一行 [HIGH]/[LOW]/[AIR])
t=60s 按 KEY2(回手动)→ KEY4(进远程,默认蓝牙)
[MODE] Manual (KEY2) | Keys: 1=FAN 3=CURTAIN 4=HEATER
[MODE] Remote (KEY4) | Bluetooth ON
[NET] Bluetooth @9600
t=62s 手机发 FAN_ON(蓝牙通道)
[CMD] FAN ON ← 前提:11 章那个清空顺序 bug 已修
t=70s 拨码开关切到 WiFi 侧,按 KEY4
[KEY4] request MQTT
[NET] Switch HW dial to WiFi(W_TXD/W_RXD)! MQTT init ~10s...
[NET] MQTT init start...
[ESP] online @115200
setEchoOff : OK!!!
setWifiMode:OK!!!
connectWifi : OK!!!
t=74s [NET] reset MQTT link (DISCONN + CLEAN)...
setMqttPara : OK!!!
MqttConn : OK!!!
MqttSub : OK!!!
[NET] test publish: {"t":27,"h":45,"a":2,"c":612,"v":123}
MqttPub(test) : OK!!!
[NET] MQTT ready (WiFi + broker + sub all OK)
[NET] WiFi/MQTT @115200
t=75s [PUB] OK
t=77s [PUB] OK
t=79s [PUB] OK ← 到这里固件端 100% 正常
t=80s 浏览器打开页面
Console: [MQTT] 已连接 wss://broker.emqx.io:8084/mqtt
Console: [MQTT←STM32] 2604/topic-HH {"t":27,"h":45,"a":2,"c":612,"v":123}
页面:温度 27.0、湿度 45、圆环约 54%/45%、空气"空气一般"
t=90s 网页点"风扇"
Console: [MQTT→STM32] cmd: FAN_ON
串口: [MQTT] cmd: FAN_ON
[CMD] FAN ON
实物: 风扇转
这张表里有四个"卡点",联调时最容易停在这四个地方:
| 卡点 | 位置 | 判据 |
|---|---|---|
| ① 连启动日志都没有 | t=0 | 第 1 层(供电/串口) |
② [NET] Bluetooth @9600 没出现 | t=60 | KEY4 没按对 / 模式不对 |
③ 停在 [ESP] online 之前 | t=70 | 拨码 / 供电 |
④ [MQTT] cmd: 有但 [CMD] 没有 | t=90 | 命令字面量拼错 |
三、分层验收:A 到 G 七层
按顺序打勾。哪一层没通过,就先修那一层,别往上跳。
3.1 A 层:基础
| 检查项 | 期望现象 |
|---|---|
| 上电 1 秒内有日志 | [MODE] Manual (KEY2) ... |
| 任意键打印一次环境 | [ENV] Temp:.. Humi:.. AQI:.. TVOC:.. eCO2:.. Air:.. Mode:.. |
| 非手动下按 KEY1 | [KEY1] send_flag=1,之后每秒一条 [ENV] |
[ENV] 的完整样子(注意 Air 是 GOOD/BAD,Mode 是 0/1/2):
[ENV] Temp:27.5C Humi:45.0% AQI:2 TVOC:123 ppb eCO2:612 ppm Air:GOOD Mode=0
注意
Humi:45.0%中间那个点:DHT11 的湿度是"整数.小数"两字节,打印格式是%d.%d%%。最后那个%%在 printf 里输出一个%。别误以为格式错了。
3.2 B 层:传感器
| 检查项 | 判据 | 不合格怎么办 |
|---|---|---|
| 温度接近室温 | ±3℃ 内 | 查 DHT11 上拉、线长、delay_us |
| 湿度合理 | 30%~80% | 同上 |
| AQI 在 1~5 | 1~5 | 查 ENS160 I2C 地址(0x53/0x52)、OPMODE |
| eCO2 在 400~2000 | 合理范围 | ENS160 需要预热,刚上电偏高正常 |
| TVOC 在 0~2000 | 合理范围 | 同上 |
| 对着传感器哈气 | eCO2/TVOC 明显上升 | 没反应 → 数据没刷新,查 Read_ENS160 |
ENS160 有个"预热期":刚上电头几分钟读数会漂,AQI 可能一直给 1 或 5。这是传感器自身的算法在收敛,不是代码错。验收时至少让它跑 3~5 分钟再看趋势。
3.3 C 层:执行器(手动模式)
先 KEY2 进手动(日志 [MODE] Manual ...),然后:
| 操作 | 期望日志 | 期望现象 |
|---|---|---|
| KEY1 | [MANUAL] FAN ON / FAN OFF | 风扇转 / 停 |
| KEY3 | [MANUAL] CURTAIN OPEN / CLOSE | 舵机两个明显位置 |
| KEY4 | [MANUAL] HEATER ON / OFF | 加热片发热(手别碰) |
进手动时窗帘会先归位到"关闭",这是 Switch_Mode() 里那句 CURTAIN_CLOSE_PULSE 干的。所以你第一次按 KEY3 一定是"拉开",能看到明显动作 —— 这是故意设计的,方便验收。
如果风扇不转但日志有 [MANUAL] FAN ON:
- 量 PA6 有没有 PWM(万用表测平均电压,约 2.6V 左右);
- 查
HAL_TIM_PWM_Start有没有在main()里调用; - 查 MOS 管驱动供电。
3.4 D 层:自动模式
按 KEY3 进自动([MODE] Auto | Temp Control ON),然后用人为手段制造四种条件:
| 条件 | 怎么造 | 期望日志 | 期望动作 |
|---|---|---|---|
| 高温 | 电吹风/手握传感器加热到 ≥30℃ | [HIGH] Temp=30C | FAN ON | CURTAIN OPEN | 风扇转 + 窗帘开 |
| 低温 | 冰袋/空调吹到 ≤29℃ | [LOW] Temp=29C | HEATER ON | CURTAIN CLOSE | 加热 + 窗帘关 |
| 温度正常 + 空气差 | 喷点酒精/香水靠近 ENS160 | [AIR] AQI=.. eCO2=..ppm TVOC=..ppb | FAN ON | CURTAIN OPEN | 风扇通风 + 窗帘开 |
| 温度正常 + 空气好 | 开窗通风等读数降下来 | 无状态日志([ENV] 照常) | 全关,窗帘保持拉开 |
两个"排错难想到"的点:
- 状态日志只在"状态变化"时打印一次。 代码里用
last_state记住上次状态,if(cur_state != last_state)才打印。所以[HIGH]只出现一次,不会每 2 秒刷一条。"只打印了一次"不是卡住了,是防刷屏。 - 正常态窗帘是"拉开"的(不是关闭)。因为通风/采光的默认姿态是开。验收时别把"正常态窗帘开着"判成 bug。
3.5 E 层:蓝牙
| 检查项 | 期望 |
|---|---|
| 手机能连上 HC-06 | 配对成功(默认 PIN 1234) |
发 FAN_ON | 风扇转,手机收到回显 FAN_ON |
发 ALL_OFF | 全关 |
发乱码 ABC | 串口 [CMD] unknown: ABC |
⚠ 这一层有个前提,必须说清楚。
第 11 章我们发现
main.c的蓝牙分支有一个清空顺序 bug:Remote_Cmd_Exec((char*)bt_cmd_buf, 1); bt_cmd_buf[0] = '\0'; // ← 每收到一个字节就清空因为
HAL_UART_Receive_IT(&huart2, bt_buf, 1)是一次收一个字节,所以缓冲区永远是"一个字符 + 结束符",strstr永远匹配不上。日志会呈现成:[CMD] unknown: F [CMD] unknown/A [CMD] unknown: N ...(一个字符一行)用仓库原始代码做 E 层验收,这一项必然过不了。 这不是联调新发现的故障,是已知问题。按第 11 章 5.4 节的最小改法(让
Remote_Cmd_Exec返回是否命中、只在命中时清空)改完,E 层即可通过。MQTT 通道不受影响,因为那条路径用的是整包
esp_sub_data,不是逐字节拼。
3.6 F 层:MQTT(固件侧,先不碰网页)
| 检查项 | 期望 |
|---|---|
日志出现 [NET] MQTT ready | 初始化全绿 |
每 2 秒一条 [PUB] OK | 上报正常 |
MQTTX 订阅 2604/topic-HH | 每 2 秒收到 {"t":..,...} |
MQTTX 往 2604/cmd-HH 发 FAN_ON | 串口 [MQTT] cmd: FAN_ON + [CMD] FAN ON,风扇转 |
注意 F 层要在没有网页的情况下测。 用 MQTTX 而不是网页,才能把"网页问题"和"固件问题"切开。这一步全绿再去开网页。
3.7 G 层:H5
| 检查项 | 期望 |
|---|---|
| 顶部状态 | MQTT 已连接,绿点 |
| 温度/湿度 | 数值在刷新,圆环跟着动 |
| 空气质量 | AQI / eCO2 / TVOC 三个数都有 |
| 点"风扇" | 实物转,串口 [MQTT] cmd: FAN_ON |
| 点"全部关闭" | 全停 |
| F12 Console | [MQTT←STM32] 和 [MQTT→STM32] 都有 |
3.8 一条铁律
A 不过 → 不上 B B 不过 → 不上 C
C 不过 → 不上 D D 不过 → 可以上 E(E 不依赖 D)
E 不过 → 可以上 F(但先解释清楚为什么不过)
F 不过 → 绝不上 G
F 不过就上 G,等于给自己制造一堆无法解释的现象。 网页显示的 --、按钮不灵、状态乱跳,全都可能是 F 层的下游表现。
四、日志判读速查表
这一节建议截图保存,联调时对着看。
4.1 模式与按键
| 你看到的 | 含义 | 下一步 |
|---|---|---|
[MODE] Manual (KEY2) ... | 进/回到手动,全关 | 正常 |
[MODE] Auto | Temp Control ON | 进自动 | 正常,2 秒后开始控 |
[MODE] Remote (KEY4) | Bluetooth ON | 进远程(默认蓝牙) | 看下一行 [NET] |
[KEY4] request MQTT | 请求切 MQTT | 等十几秒 |
[KEY4] busy: MQTT init in progress, ignored | 初始化期间按键被丢 | 正常,等 [NET] MQTT ready |
[KEY1] send_flag=1 | 开了每秒打印 | 正常 |
[MANUAL] FAN ON | 手动开风扇 | 正常 |
4.2 ESP 探测
| 你看到的 | 含义 | 下一步 |
|---|---|---|
[ESP] online @115200 | 探测通过 | 继续 |
[ESP] online @9600 (not 115200), keep 9600 | ESP 被改成过 9600 | 能用,但慢;建议 AT+UART 改回 115200 |
[ESP] no valid answer, but bytes did arrive -> baud mismatch (or ESP still booting) | 线通了,波特率不对/没启动完 | 等 2 秒重试;或确认 ESP 波特率 |
[ESP] zero bytes received -> USART2 RX not connected to ESP (dial on Bluetooth side? ESP unpowered?) | 一个字节都没收到 | 查拨码、ESP 供电、TX/RX 交叉 |
[NET] ESP probe failed -> skip WiFi/broker init ... | 探测失败,后面全部跳过 | 先解决探测,别往下看 |
zero bytes 是命中率最高的一条。 十个"MQTT 全 FAIL"里至少五个是它:拨码开关还在蓝牙侧。
4.3 WiFi
| 你看到的 | 含义 | 下一步 |
|---|---|---|
connectWifi : OK!!! | 连上了 | 继续 |
connectWifi : FAIL,recv(len=0) | 超时无应答 | SSID/密码、2.4G、信号 |
connectWifi : CWJAP? -> ... | 失败后核实真实状态 | 看返回的 AP 是不是你要的 |
connectWifi : actually connected (state confirmed) | 其实连上了 | 正常,继续 |
校园网/公司网的 WiFi 常带网页认证,ESP 连得上但出不了外网,表现为后面 MqttConn : FAIL 或 +PING:TIMEOUT。用手机热点最省事。
4.4 MQTT 会话
| 你看到的 | 含义 | 下一步 |
|---|---|---|
[NET] reset MQTT link (DISCONN + CLEAN)... | 正在擦 LinkID 0 | 正常 |
setMqttPara : OK!!! | 配置成功 | 继续 |
setMqttPara : FAIL,recv...ERROR | LinkID 0 被旧配置占着 | 确认 AT+MQTTCLEAN=0 执行了;重启 ESP 再试 |
MqttConn : OK!!! | 连上 broker | 继续 |
MqttConn : FAIL(try 1),recv... | 连不上 | 查外网、DNS、broker 地址 |
MqttConn : network check (AT+PING)... | 失败后自动 PING 诊断 | 看 ping 结果 |
[NET] reuse existing broker session, skip USERCFG/CONN | ESP 自己已经连上了 | 正常,代码走了复用分支 |
MqttSub : OK!!! | 订阅成功 | 继续 |
MqttPub(test) : OK!!! | 试发成功 | 即将 ready |
4.5 发布
| 你看到的 | 含义 | 下一步 |
|---|---|---|
[PUB] OK 连续 | 上报正常 | ✓ |
[PUB] FAILED, payload was -> {"t":..} 偶发 | 网络抖动 | 偶发可接受,看下一条 |
| 同上,连续出现 | 会话已失效 | 重进 MQTT(KEY4 两次) |
[PUB] FAILED 且 payload 明显不对 | JSON 拼错了 | 看 Mqtt_Build_Json |
[MQTT] cmd: FAN_ON + [CMD] FAN ON | 命令链路全通 | ✓ |
[MQTT] cmd: ... 但 [CMD] unknown | 命令字面量错 | 对照第 13 章 6.6 命令表 |
注意 [PUB] FAILED, payload was -> ... 打印的是JSON 载荷本身(不是 AT 指令),这是刻意保存的,方便对照内容。别把它当成"发给 ESP 的指令"。
4.6 异常与重启
| 你看到的 | 含义 | 下一步 |
|---|---|---|
[ESP] ready 反复出现 | ESP 在重启 | 供电不足,换独立 3.3V ≥500mA |
[NET] ESP rebooted -> MQTT session lost, stop publishing... | 检测到 ESP 重启,停止发布 | 按 KEY4 两次重新初始化 |
[WARN] DHT11 read failed, keep last state | DHT11 读失败,保持上次状态 | 查上拉、线长;偶发正常 |
[ENV] DHT11 not ready | 缓存里没有有效值 | 同上 |
| 一行日志都没有 | 程序没跑/串口没通 | 复位、查 USART1、MicroLIB |
4.7 30 秒定位决策树
现场出问题时,按这棵树往下走,最多四步就能定位到层:
┌─────────────────────┐
│ 现象是什么? │
└──────────┬──────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
"串口没日志" "有日志但没数据" "网页不对"
│ │ │
▼ ▼ ▼
复位一次 看 [ESP] 行 看 Console
│ ┌────┴────┐ ┌────┴────┐
还是没有? zero bytes online 无"已连接" 有"已连接"
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
查供电/USART1 查拨码/供电 看 connectWifi wss/8084/ 看主题
/MicroLIB/波特率 ┌────┴────┐ /mqtt 大小写
FAIL OK │
│ │ ▼
SSID/密码 看 setMqttPara 有没有
/2.4G ┌────┴────┐ [MQTT←STM32]
FAIL OK 有→id写错
│ │ 无→设备没发
CLEAN未执行 看 [PUB]
再给一张"症状→第一动作"的极简版,贴在显示器边上:
| 症状 | 第一动作(不要想,直接做) |
|---|---|
| 串口没日志 | 按复位 |
有日志,没 [ESP] 行 | 看拨码开关 |
[ESP] zero bytes | 看拨码 + 量 ESP 3.3V |
connectWifi : FAIL | 手机连同一热点,确认能上网 |
setMqttPara : FAIL | 重启 ESP,重走 CLEAN |
没 [PUB] OK | KEY4 两次重初始化 |
| 网页"已连接"没数据 | 串口有没有 [PUB] OK |
| 网页有数据,按钮不灵 | 板子是不是远程模式(Mode=2) |
| 按钮灵,设备不动 | 串口有没有 [CMD] |
核心思路只有一句:先用串口把故障切到"设备侧"或"网页侧",再在那一侧细分。 不要一上来就两边同时改。
五、端到端时序:对着表看
5.1 上报链路(设备 → 网页)
STM32 ESP-12F broker.emqx.io 浏览器 H5
│ │ │ │
│ 每2s: Sensors_Update() │ │ │
│ Mqtt_Set_Env(t,h,a,c,v) │ │ │
│ │ │ │
│──AT+MQTTPUBRAW=0,..─────▶│ │ │
│◀──────── '>' ────────────│ │ │
│──{"t":27,"h":45,...}────▶│ │ │
│ │──MQTT PUBLISH(qos1)──▶│ │
│ │ │──推送(qos1)───────▶│
│◀──────── OK ─────────────│ │ │
│ printf("[PUB] OK") │ │ 页面更新 │
5.2 命令链路(网页 → 设备)
浏览器 H5 broker.emqx.io ESP-12F STM32
│ │ │ │
│──publish(FAN_ON)───────▶│ │ │
│ qos1, 主题 2604/cmd │──推送──────────────▶│ │
│ │ │──+MQTTSUBRECV:0,..─▶│
│ │ │ │
│ │ │ printf("[MQTT] │
│ │ │ cmd: FAN_ON") │
│ │ │ Remote_Cmd_Exec() │
│ │ │ CCR=800 → 风扇转 │
两条链路是独立的。 上报由主循环的 2 秒定时器驱动,命令由 ESP 的 +MQTTSUBRECV 异步推来。所以你点按钮的响应时间不一定是 2 秒,可能是几十毫秒。
5.3 关键时间常数(心里有个数)
| 常量 | 值 | 在哪里 |
|---|---|---|
| 上电到 ESP 就绪 | HAL_Delay(3000) + 探测 | Mqtt_Server_Init 开头 |
| 首次 MQTT 初始化总时长 | 约 10~15 秒 | 多条 AT 各自超时叠加 |
| 传感器采样 | 2 秒 | 主循环 last_ctrl_tick |
| MQTT 上报 | 2 秒 | MQTT_PUB_PERIOD |
| 按键消抖 | 40 毫秒 | EXTI 回调 |
| ESP 单条指令超时 | 1500~2000 毫秒 | Esp_Cmd_Expect |
发布等 > | 1500 毫秒 | Mqtt_Pub_Raw_Blocking |
| 发布等 OK | 2000 毫秒 | 同上 |
| 一个字节 @115200 | 86.8 微秒 | 8N1 |
| 一个字节 @9600 | 1.04 毫秒 | 8N1 |
最后两行是"为什么 DHT11 会丢字节"的根源:读一次 DHT11 关中断约 4ms,9600 下正好丢 3~4 个字节。第 6 章讲过,这里只是让你在联调时别把它当成新问题。
六、「看起来是 bug 其实不是」十二条
这一节是给答辩和自测用的。每条都写清"要不要改"。
| # | 现象 | 真正原因 | 要不要改 |
|---|---|---|---|
| 1 | 网页显示"已连接"但没数据 | 只代表浏览器连上 broker;设备可能没进远程 MQTT | 不用改,先看串口 |
| 2 | 点按钮,设备动 2 秒又停 | 板子在自动模式,自动逻辑每 2 秒覆盖 | 不用改,先进远程模式 |
| 3 | 温度跳动 ±1~2℃ | DHT11 精度就是 ±2℃ | 不用改 |
| 4 | 网页按钮状态与实物不一致 | 本地乐观更新,无回执 | 不用改(想要回执得加设备状态上报) |
| 5 | 偶尔一条 [PUB] FAILED | 公网 broker 抖动 | 不用改,看是否连续 |
| 6 | 自动模式下 [ENV] 每 2 秒一条 | 采集即打印 | 不用改 |
| 7 | [HIGH] 只出现一次 | last_state 防重复打印 | 不用改 |
| 8 | [MODE] Remote ... | Bluetooth ON 但走的是 MQTT | 文案写死 | 不用改,看 [NET] 行 |
| 9 | 进 MQTT 后十几秒没反应 | 阻塞初始化 | 不用改,等 ready |
| 10 | 初始化期间按 KEY4 无效 | g_net_busy 主动丢弃 | 不用改 |
| 11 | 正常态窗帘是"拉开"的 | 默认姿态就是开 | 不用改 |
| 12 | 网页看不到自己发的命令 | 只订阅上报主题 | 不用改(设计如此) |
再补三条容易被当成 bug 的:
| # | 现象 | 原因 | 结论 |
|---|---|---|---|
| 13 | 湿度显示 45.0% 看着像小数精度 | DHT11 湿度本来就是"整数.小数"两字节,小数字节通常为 0 | 正常 |
| 14 | 温度统计卡 27.0、圆环 27° | 两处写法不同(toFixed(1) vs 直接拼接) | 正常,别以为是精度问题 |
| 15 | 手动模式下蓝牙突然全是乱码 | 从 MQTT 直接按 KEY2 进手动,USART2 还停在 115200 | 见 2.6 的恢复办法 |
七、六个真故障的复盘
这一节是我联调当晚实际踩的(或见过最多的),每个都写了"我当时以为是"和"实际是"。
案例 1:进 MQTT 后全 FAIL
[ESP] zero bytes received -> USART2 RX not connected to ESP
[NET] ESP probe failed -> skip WiFi/broker init
setEchoOff : FAIL...
我当时以为是:ESP 坏了 / 代码回归了。
实际是:拨码开关还在蓝牙侧。USART2 的 RX 根本没接到 ESP 的 TX。
教训:zero bytes 出现,先看开关,别看代码。这是一条 30 秒能排除的检查。
案例 2:[PUB] OK 连续,网页没数据
我当时以为是:网页的解析写错了。
实际是:H5 的 TOPIC_DATA 写成了 2604/topic-hh(小写),固件是 2604/topic-HH。
MQTT 主题区分大小写。而且这是最容易发生、最难发现的差异 —— 两个字符串看起来一模一样。
教训:比对主题时,把两边并排打印出来逐字符看,不要用眼睛扫。Console 里一条 [MQTT←STM32] 都没有,就说明根本没收到,问题在订阅侧。
案例 3:点按钮,风扇转两秒就停
我当时以为是:命令丢了 / 定时器有问题。
实际是:板子停在自动模式(Mode=1)。自动逻辑每 2 秒把风扇关掉,因为当时温度正常、空气也好。
远程控制必须在远程模式下用。这是本项目最反直觉的一条:手动/远程/自动是三个互斥的状态机,自动模式下所有手动命令都会被覆盖。
教训:点按钮前先看 [ENV] ... Mode=?。Mode=0 才是手动,Mode=2 才是远程。
案例 4:[ESP] ready 反复出现
[ESP] ready
[ESP] ready
[ESP] ready
...
我当时以为是:ESP 固件有问题。
实际是:供电不足。USB 转 TTL 的 3.3V 输出电流不够,ESP 一跑 WiFi 就掉电重启。
教训:ESP-12F 必须独立 3.3V ≥500mA。看到反复 ready,先换电源,别刷固件。
案例 5:第二次上电 setMqttPara : FAIL
setMqttPara : FAIL,recv(len=7)"\r\nERROR"
我当时以为是:broker 拒绝了我。
实际是:ESP-AT 把 MQTT 配置存进 flash,上电 WiFi 一恢复它就自己 +MQTTCONNECTED 了,LinkID 0 被占着,再配置就 ERROR。
教训:必须 AT+MQTTDISCONN=0 + AT+MQTTCLEAN=0。这是第 12 章最重要的一个坑,第二次上电必现。
案例 6:离开远程一段时间,回来后没有 [PUB] OK
我当时以为是:MQTT 自己好了又坏了。
实际是:g_mqtt_inited 是 1,所以重新进 MQTT 只重挂了接收、没有重新初始化;而期间 ESP 可能重启过,mqtt_ready 已经是 0。
代码里清理 g_mqtt_inited 的判断在这段里:
if(g_mode == MODE_REMOTE && g_net == NET_MQTT)
{
Mqtt_Poll();
...
if(g_mqtt_inited && !Mqtt_Is_Ready())
{
g_mqtt_inited = 0;
}
}
只有在远程 + MQTT 状态下才会清。你离开远程的那段时间,它不会清。
恢复办法:回到 MQTT 后如果一直没有 [PUB] OK,再按两次 KEY4(先切蓝牙、再切回 MQTT)。这时 g_mqtt_inited 已经被清 0,会重新走完整初始化。
教训:日志里那句 press KEY4 twice to retry 不是客套话,是真的要按两次。
八、稳定性与恢复能力
功能通了只是及格。真正的验收还要看"折腾它会不会死"。
8.1 连续跑 30 分钟,看四件事
| 观察 | 正常 | 异常 |
|---|---|---|
[PUB] OK 连续性 | 连续出现,偶尔 FAILED 后恢复 | 连续 FAILED |
| 温度/湿度 | 平滑变化,不卡死 | 长时间不变 → 采集卡了 |
| 风扇/舵机 | 状态稳定 | 自己乱动 → 状态机被反复覆盖 |
| ESP 是否重启 | 无 [ESP] ready | 反复 ready → 供电/看门狗 |
8.2 四种故障注入(建议全做一遍)
这一节是联调里最有价值的,因为它验证的是恢复能力,不是功能。
| 注入 | 做法 | 期望行为 |
|---|---|---|
| ① 断外网 | 关掉热点/拔路由器 | [PUB] FAILED 开始出现;恢复网络后自动回到 [PUB] OK |
| ② ESP 复位 | 按 ESP 的 RST,或拔插 ESP 电源 | 日志出现 [ESP] ready → [NET] ESP rebooted -> MQTT session lost |
| ③ 拔 USB 再插 | 整板断电重启 | 回到 [MODE] Manual,需要重新按键进远程 |
| ④ 只断 STM32 | 按板子 RESET | 同 ③,但 ESP 可能还连着(这时容易踩"复用会话"分支) |
第 ② 种恢复要有心理准备:不会自动恢复。 检测到 ESP 重启后,代码只是停止发布 + 清 mqtt_ready,需要你按 KEY4 两次重新初始化。这是有意的保守设计 —— 自动重连会在后台偷偷跑十几秒 AT 指令,期间按键全废,体验更差。
8.3 恢复路径表
| 故障 | 最简恢复操作 |
|---|---|
单条 [PUB] FAILED | 什么都不用做 |
连续 [PUB] FAILED | KEY4 → KEY4(回蓝牙再回 MQTT) |
| ESP 重启 | KEY4 → KEY4 |
| 网页掉线 | 等 3 秒自动重连,或刷新 |
| 设备命令没反应 | 确认在远程模式;重发一次 |
| 全部乱套 | 断电 → 拨码回蓝牙 → 上电 → 重走 2.1 |
8.4 缓冲与内存:三个数字
| 缓冲 | 大小 | 满了会怎样 |
|---|---|---|
BUF[1024](USART1 接收) | 1024 | 只挂 2 字节接收,基本不会满 |
esp_rxbuf[512](ESP 环形缓冲) | 实际容量 511 | 主循环不取就覆盖丢失;留一空位防"满/空"歧义 |
bt_cmd_buf[16] | 16 | 攒满还没匹配 → 判为乱码清空 |
bt_cmd_buf 只有 16 字节,最长命令 CURTAIN_OFF 是 11 个字符 + 结束符,够用。但它没有"按换行断句"的机制,所以蓝牙通道必须"一条命令一次发完、中间不带换行、发完就停"。手机 App 里如果设了"每条自动加 \r\n",就会变成 FAN_ON\r\n —— strstr 仍然能匹配上 FAN_ON,所以实际能用,但缓冲区里会残留 \r\n 影响下一条。这是蓝牙通道比较脆的地方。
九、演示脚本与答辩
9.1 五分钟演示动线
开场前 5 分钟准备(比演示本身更重要):
□ 拨码打到 WiFi 侧
□ 手机热点开着,SSID/密码与 mqtt.c 一致(7 / 66668888)
□ 板子通电,串口助手开着(115200)
□ 浏览器页面已打开并显示"MQTT 已连接"(提前进好远程)
□ MQTTX 备用窗口开着(万一网页挂了能顶上)
□ 一段 30 秒的"待机"时间:确认 [PUB] OK 已连续出现
演示动线(按这个顺序讲,逻辑最顺):
| 时间 | 动作 | 讲解点 |
|---|---|---|
| 0:00 | 复位板子,串口滚出 [MODE] Manual | “上电默认手动,安全第一,四路全关” |
| 0:30 | 按 KEY1,风扇转;再按,停 | “手动模式:KEY1 风扇、KEY3 窗帘、KEY4 加热” |
| 1:00 | 按 KEY3,展示 [ENV] 一行 | “按键顺便打印环境数据:温湿度 + AQI/eCO2/TVOC” |
| 1:30 | 按 KEY3 进自动,用手捂传感器 | “温度 ≥30℃ → 风扇 + 窗帘开;≤29℃ → 加热 + 窗帘关,1℃ 回差” |
| 2:30 | 喷一点酒精靠近 ENS160 | “空气差 → 通风,不用你管” |
| 3:00 | KEY2 进手动,KEY2 回自动,KEY4 进远程 | “三模式互斥,切换时先全关” |
| 3:30 | 切到 MQTT(KEY4),等 ready | “这十几秒是在跟 ESP 一问一答,不是死机” |
| 4:00 | 打开网页,指着实时数据 | “ESP 每 2 秒发一条 JSON 到 broker,网页订阅” |
| 4:30 | 网页点风扇 / 全部关闭 | “浏览器 → wss → broker → ESP → 串口 → 板子,五跳” |
讲的时候别念代码,念"为什么"。 评委不关心 HAL_TIM_SET_COMPARE,关心你为什么用 50Hz/CCR 500~2000、为什么 1℃ 回差、为什么自动模式优先温度。
9.2 一定会被问的十个问题
| 问题 | 回答要点 |
|---|---|
| 为什么温湿度用 DHT11 不用 SHT30? | 成本 + 单总线省引脚;精度 ±2℃/±5%RH,教学够用 |
| ENS160 和普通 MQ 传感器区别? | 数字 I2C,直接给 AQI/eCO2/TVOC 三指标,免校准 |
| 为什么自动模式温度优先? | 温度影响体感更直接;温度正常才让空气质量接管 |
| 1℃ 回差是干什么的? | 防止 29.9/30.0 反复横跳,避免执行器抖动 |
| 为什么用 1kHz PWM 驱动风扇? | 高于人耳可听范围附近,且 MOS 开关损耗可接受;低了会听到啸叫 |
| 舵机为什么 50Hz、CCR 500~2000? | SG90 标准:20ms 周期,0.5~2.5ms 脉宽对应角度 |
| 蓝牙和 WiFi 怎么共存? | 硬件拨码二选一 + 软件切波特率,USART2 分时复用 |
| 为什么网页连 8084 不连 1883? | 浏览器只有 WebSocket,1883 是裸 TCP |
| 为什么网页显示"已连接"却没数据? | 那只代表连上 broker,设备可能不在远程模式 |
| 网页按钮和设备状态不同步怎么办? | 目前是本地乐观更新、无回执;要同步得让设备上报状态 |
9.3 现场保命清单
□ 提前把 mqtt.min.js 下载到本地,改 index.html 引用(别赌教室网)
□ 手机热点提前测试能上 broker.emqx.io
□ 串口助手保存一份"正常日志"截图,异常时对比
□ 浏览器提前 Ctrl+F5 硬刷新一次
□ 板子多带一根 USB 线(线坏是最隐蔽的故障)
□ 会背"拨码 / 远程模式 / 主题"这三件事 —— 90% 的现场故障是它们
□ 演示前别改代码。演示前改代码 = 自找麻烦
十、本章易错点总结
| 现象 | 真正原因 | 处理 |
|---|---|---|
| 上电无任何日志 | 没复位 / USART1 接线 / 波特率 / MicroLIB | 手动复位,逐项查 |
| 烧完日志还是旧的 | 下载后没复位,旧程序在跑 | 按板子 RESET |
| 一上来就测网页,出问题无法定位 | 跳过分层验收 | 按 A→G 顺序 |
[ESP] zero bytes received | 拨码还在蓝牙侧 / ESP 没电 / TX-RX 未交叉 | 先查拨码 |
[ESP] no valid answer, but bytes did arrive | 波特率不对或 ESP 没启动完 | 等 2 秒重试,或查 ESP 波特率 |
| 进 MQTT 后十几秒没反应就以为死机 | 阻塞初始化,属预期 | 等 [NET] MQTT ready |
| 初始化期间连按 KEY4 | g_net_busy 主动丢弃 | 等 ready 再按 |
setMqttPara : FAIL(第二次上电) | 旧配置占着 LinkID 0 | 必须 AT+MQTTCLEAN=0 |
connectWifi : FAIL | SSID/密码错、5G 热点、信号弱 | 2.4G + 手机热点 |
MqttConn : FAIL + +PING:TIMEOUT | 出不了外网(网页认证 WiFi) | 换热点 |
[PUB] OK 连续,网页没数据 | 主题大小写/字符不一致 | 并排逐字符比对 |
一条 [PUB] FAILED 就判失败 | 公网抖动 | 看是否连续 |
连续 [PUB] FAILED | 会话失效 | KEY4 两次重初始化 |
[ESP] ready 反复 | 供电不足 | 独立 3.3V ≥500mA |
| ESP 重启后不自动恢复 | 保守设计,需手动重进 | KEY4 两次 |
| 点按钮设备动 2 秒又停 | 板子在自动模式 | 先进远程模式 |
| 手动模式下蓝牙乱码 | 从 MQTT 直接 KEY2 进手动,波特率还是 115200 | KEY2→KEY4→KEY4 回 9600 |
| 手动模式下按 KEY4 想切网络 | 手动模式 KEY4 = 加热 | 先离开手动 |
| 手动 KEY2 以为"退出手动到远程" | 实际回到自动 | 记住三模式互斥 |
蓝牙发命令全 unknown 单字符 | 第 11 章那个清空顺序 bug 未修 | 按 11 章 5.4 改 |
蓝牙发了带 \r\n 的命令 | 缓冲没断句机制 | 一条一次发完,别加换行 |
自动模式 [ENV] 刷屏 | 每 2 秒采集即打印 | 正常 |
[HIGH] 只出现一次以为卡住 | last_state 防重复打印 | 正常 |
| 网页"已连接"但设备不动 | 只代表连上 broker | 看串口 [PUB] OK |
| 网页命令看不到回显 | 只订阅上报主题 | 正常 |
| 全关后风扇还转 | 固件 ALL_OFF 漏路 / 在自动模式被覆盖 | 查 main.c ALL_OFF 与 g_mode |
| 现场连不上 | 依赖外网 CDN + 公网 broker | 提前离线化 + 热点 |
| 演示前临时改代码 | 引入未验证变更 | 别改 |
十一、小结:这个工程到底教会了什么
到这一章,整个系列闭环了。回头看,十四篇文章其实只在讲五件事:
① 读传感器 DHT11 单总线时序 / ENS160 I2C 寄存器
② 控执行器 TIM3 1kHz PWM / TIM5 50Hz 舵机
③ 管状态 按键消抖 + 三模式互斥 + 1℃ 回差
④ 通串口 调试口 / 蓝牙 / WiFi 分时复用 + 环形缓冲
⑤ 连云端 ESP-AT 一问一答 + MQTT 两条主题 + H5
真正难的从来不是写代码,而是把每一层的现象分离清楚:
- 串口日志是唯一可信的"真相";
- H5 是"展示",不是"状态";
- 设备动作是"最终事实",但滞后;
- 任何一层没通,上层的一切现象都不可信。
这套方法不只适用于这个项目。以后你调任何一块板子、任何一条通信链路,都是同一套动作:分层、定真相源、一次只改一个变量。
三个模块拼不起来的原因,往往不是代码写错,而是:
- 硬件开关没跟着软件走(拨码);
- 状态机没跟着操作走(自动模式覆盖手动命令);
- 时间没跟着流程走(初始化没等完就按键);
- 观察没跟着事实走(信网页不信串口)。
把这四句话记住,比记住任何一行代码都有用。
附录 A:可打印的验收清单
──────────────────────────────────────────────
智能家居整机验收清单 日期:____ ___
──────────────────────────────────────────────
A 基础
[ ] 上电 1 秒内出现 [MODE] Manual (KEY2) ...
[ ] 按任意键打印 [ENV] Temp:.. Humi:.. AQI:..
[ ] 非手动下 KEY1 → [KEY1] send_flag=1
B 传感器
[ ] 温度 ±3℃ 内接近室温
[ ] 湿度 30%~80%
[ ] AQI 1~5
[ ] eCO2 400~2000 ppm
[ ] TVOC 0~2000 ppb
[ ] 哈气后 eCO2/TVOC 上升
C 执行器(手动)
[ ] KEY1 风扇开/关
[ ] KEY3 窗帘两个位置
[ ] KEY4 加热开/关
[ ] 进手动时窗帘自动归位到关闭
D 自动模式
[ ] ≥30℃ → [HIGH],风扇+窗帘开
[ ] ≤29℃ → [LOW],加热+窗帘关
[ ] 温度正常+空气差 → [AIR],风扇+窗帘开
[ ] 温度正常+空气好 → 全关,窗帘保持拉开
E 蓝牙
[ ] 手机能连 HC-06
[ ] FAN_ON → 风扇转 + 回显
[ ] ALL_OFF → 全关
[ ] (前提:第 11 章清空顺序 bug 已修)
F MQTT(固件侧,用 MQTTX 测)
[ ] [NET] MQTT ready
[ ] 每 2 秒 [PUB] OK
[ ] MQTTX 订阅 topic-HH 收到 JSON
[ ] MQTTX 往 cmd-HH 发 FAN_ON → [MQTT] cmd + 风扇转
G H5
[ ] 顶部"MQTT 已连接"
[ ] 温湿度在刷新,圆环跟着动
[ ] 空气质量三个数都有
[ ] 点风扇 → 实物转
[ ] 点全部关闭 → 全停
[ ] Console 有 [MQTT←STM32] 与 [MQTT→STM32]
H 稳定性
[ ] 连续 30 分钟 [PUB] OK 基本连续
[ ] 断网后恢复 → 自动回到 [PUB] OK
[ ] ESP 复位 → 能通过 KEY4×2 恢复
[ ] 整板断电重启 → 能重走流程
──────────────────────────────────────────────
附录 B:正常 / 异常日志对照卡
正常(首次进 MQTT 全流程)
[KEY4] request MQTT
[NET] Switch HW dial to WiFi(W_TXD/W_RXD)! MQTT init ~10s...
[NET] MQTT init start...
[ESP] online @115200
setEchoOff : OK!!!
setWifiMode:OK!!!
connectWifi : OK!!!
[NET] reset MQTT link (DISCONN + CLEAN)...
setMqttPara : OK!!!
MqttConn : OK!!!
MqttSub : OK!!!
[NET] test publish: {"t":27,"h":45,"a":2,"c":612,"v":123}
MqttPub(test) : OK!!!
[NET] MQTT ready (WiFi + broker + sub all OK)
[NET] WiFi/MQTT @115200
[PUB] OK
异常 A:拨码在蓝牙侧
[ESP] zero bytes received -> USART2 RX not connected to ESP (dial on Bluetooth side? ESP unpowered?)
[NET] ESP probe failed -> skip WiFi/broker init (cause printed above)
[NET] MQTT init failed (see FAIL line above). press KEY4 twice to retry
异常 B:旧配置占着 LinkID 0
setMqttPara : FAIL,recv(len=7)"\r\nERROR"
异常 C:ESP 反复重启(供电不足)
[ESP] ready
[ESP] ready
[ESP] ready
[NET] ESP rebooted -> MQTT session lost, stop publishing. press KEY4 twice to re-init
异常 D:发布失败
[PUB] FAILED, payload was -> {"t":27,"h":45,"a":2,"c":612,"v":123}
附录 C:全系列索引
| 章 | 一句话 |
|---|---|
| 1 | 项目总览:五个功能、三条链路、硬件与软件清单 |
| 2 | 软件环境:Keil / CubeMX / 串口助手 / 本地 HTTP 服务 |
| 3 | 硬件连线:引脚总表、拨码开关、上电自检 |
| 4 | CubeMX:时钟树 100MHz、外设配置、生成工程 |
| 5 | 工程结构:App 目录、添加 .c/.h、USER CODE 纪律 |
| 6 | DHT11:单总线时序、微秒延时、校验 |
| 7 | ENS160:I2C3、寄存器、AQI/eCO2/TVOC |
| 8 | PWM 执行器:TIM3 1kHz、TIM5 50Hz、占空比与脉宽 |
| 9 | 按键与三模式:EXTI、消抖、状态机、1℃ 回差 |
| 10 | 串口与 printf 重定向:USART1、fputc、接收中断 |
| 11 | 蓝牙 HC-06:AT 指令、逐字节拼命令、回显 |
| 12 | USART2 分时复用:ESP-AT、WiFi、MQTT、PUBRAW、环形缓冲 |
| 13 | H5 前端:mqtt.js、wss 8084、JSON 解析、圆环与空气卡 |
| 14 | 整机联调:分层验收、日志判读、故障复盘、演示脚本 |
附录 D:全工程参数速查
引脚
| 引脚 | 功能 | 外设 |
|---|---|---|
| PA0 | SG90 舵机 | TIM5_CH1 |
| PA1 / PC2 / PC3 / PB10 | KEY3 / KEY1 / KEY2 / KEY4 | EXTI1 / 2 / 3 / 15_10 |
| PA2 / PA3 | USART2 TX/RX | 蓝牙 / WiFi 复用 |
| PA6 / PA7 / PB0 | 风扇 / 加热 / 窗帘电机 | TIM3_CH1/CH2/CH3 |
| PA8 / PC9 | I2C3 SCL/SDA | ENS160 |
| PA9 / PA10 | USART1 TX/RX | 调试 115200 |
| PB1 | DHT11 DATA | GPIO |
| PC6 / PC7 | USART6 | 语音预留(AF8) |
| PC13 | 调试 LED | GPIO(发送完成翻转) |
关键参数
| 参数 | 值 |
|---|---|
| 系统时钟 | 100 MHz(HSE 25M,PLL M25/N400/P4) |
| APB1 / APB2 | 50 / 100 MHz |
| TIM3 | PSC=99, ARR=999 → 1 kHz |
| TIM5 | PSC=99, ARR=19999 → 50 Hz |
| 风扇 / 加热 PWM | CCR=800(约 80%) |
| 窗帘 | CCR=500(开)/ 2000(关) |
| 高温 / 低温 | ≥30℃ / ≤29℃ |
| 空气差 | AQI≥3 或 eCO2>1000 或 TVOC>650 |
| 传感器采样 / MQTT 上报 | 2 s / 2 s |
| 按键消抖 | 40 ms |
| WiFi | 7 / 66668888 |
| broker | broker.emqx.io:1883(设备) |
| H5 | wss://broker.emqx.io:8084/mqtt |
| ClientID(设备 / 网页) | ESP12F_2604_HH / h5_ + 随机 |
| 主题 | 2604/topic-HH / 2604/cmd-HH |
命令
| 命令 | 动作 |
|---|---|
FAN_ON / FAN_OFF | 风扇开 / 关 |
HER_ON / HER_OFF | 加热开 / 关 |
CURTAIN_ON / CURTAIN_OFF | 窗帘拉开 / 关闭 |
ALL_OFF | 全部关闭(含窗帘落下) |
本文聚焦STM32智能家居系统的整机联调与验收,涵盖上电流程、七层分层验证(A-G层)、日志判读速查、端到端时序分析及典型故障复盘。强调以串口日志为唯一真相源,遵循分层定位、单变量调试原则,解决模块间交互问题如拨码开关误置、MQTT初始化未完成即操作、自动模式覆盖手动命令等。内容面向嵌入式固件侧联调,不涉及网页前端实现。
674

被折叠的 条评论
为什么被折叠?



