从嵌入式下位机到云端数据平台,手把手教你实现完整的物联网系统。下位机使用标准库+裸机开发,小程序采用 AI VibeCoding 零门槛开发,数据实时上华为云平台。
同学们将掌握:
UART1调试、UART2接BLE、UART3接ESP8266
帧格式设计、状态机解析、校验和验证
BLE透传、协议封装、UI响应
ESP8266 AT指令、TCP连接、JSON上报
| 层次 | 技术 |
|---|---|
| 感知层 | DHT11 温湿度、LED指示灯 |
| 控制层 | STM32F407 标准库裸机 |
| 传输层 | BLE DX-BT24 / ESP8266 WiFi |
| 应用层 | 微信小程序 VibeCoding |
| 云平台 | 华为云 IoTDA / MQTT |
| 存储 | W25Q128 SPI Flash |
STM32F407_Project/ ├── app/ │ └── main.c ← 主程序入口,主循环 ├── bsp/ ← 板级支持包 │ ├── uart/ │ │ ├── bsp_uart.c ← UART1 (调试) │ │ ├── bsp_uart2.c ← UART2 (BLE) │ │ └── bsp_uart3.c ← UART3 (ESP8266) │ ├── dht11/ │ │ └── bsp_dht11.c ← DHT11温湿度 │ ├── led/ │ │ └── bsp_led.c ← LED驱动 │ ├── esp8266/ │ │ └── esp8266.c ← WiFi AT指令 │ └── w25q128/ │ └── bsp_w25q128.c ← SPI Flash ├── module/ │ ├── protocol.c ← 帧构建/校验 │ ├── protocol.h ← 协议定义 │ ├── frame_parser.c ← 状态机解析+路由 │ └── stm32f4xx_it.c ← 中断服务函数 ├── board/ │ └── board.c ← SysTick延时等 └── libraries/ ← STM32标准库(不修改)
WXMP-IOT中控系统/ ├── app.js ← 全局BLE连接管理 ├── pages/ │ ├── control/ │ │ ├── control.js ← LED控制逻辑 │ │ └── control.wxml ← 控制页面布局 │ ├── collection/ │ │ ├── collection.js ← 温湿度展示 │ │ └── collection.wxml ← 数据可视化 │ └── settings/ │ ├── settings.js ← WiFi/IoT配置 │ └── settings.wxml ← 配置表单 ├── utils/ │ ├── protocol.js ← 帧协议封装(与MCU对称) │ ├── ble.js ← BLE连接管理 │ ├── storage.js ← 本地存储 │ └── log.js ← 日志系统 └── custom-tab-bar/ ← 底部Tab栏
| 方向 | 路径 | 数据格式 | 说明 |
|---|---|---|---|
| 📱→💻 | 小程序 → BLE → UART2 → STM32 | 二进制帧 AA 05 11 FF D1 | LED控制、WiFi配置、系统命令 |
| 💻→📱 | STM32 → UART2 → BLE → 小程序 | JSON字符串 {"temp":25.4,"humi":46.2} | 温湿度数据、状态反馈 |
| 💻→☁️ | STM32 → UART3 → ESP8266 → 华为云 | JSON字符串 (TCP透传) | 传感器数据上报云端 |
| ☁️→💻 | 华为云 → ESP8266 → UART3 → STM32 | 二进制帧 (云端下发) | 远程控制指令 |
| 🖥️↔💻 | PC串口助手 ↔ UART1 ↔ STM32 | AT文本命令 + 二进制帧 | 开发调试专用 |
| 时段 | 内容 |
|---|---|
| 上午 2h | 工程模板导入、Keil5配置、编译下载验证 |
| 上午 2h | UART1配置讲解,环形缓冲区原理,printf重定向 |
| 下午 2h | UART2/3配置,多串口并发接收演示 |
| 下午 2h | BLE AT指令测试,串口助手联调 |
| 时段 | 内容 |
|---|---|
| 上午 2h | DHT11时序讲解,单总线驱动编写 |
| 上午 2h | 协议帧格式设计,校验和计算推导 |
| 下午 2h | 状态机解析器原理与实现 |
| 下午 2h | 串口助手发送HEX帧测试LED控制 |
用自然语言告诉AI需要什么功能
复制生成的代码到微信开发者工具
真机测试,问题反馈给AI修改
IoTDA控制台 → 产品 → 新建产品(自定义协议)
获取设备ID、密钥、接入地址
小程序设置页输入WiFi SSID/密码,发送给STM32
华为云设备影子查看温湿度数据
| 模块 | STM32引脚 | 功能 | 配置参数 | 注意事项 |
|---|---|---|---|---|
| UART1 TX | PA9 | PC调试输出 | 115200,8N1 | printf重定向至此 |
| UART1 RX | PA10 | PC调试输入 | 115200,8N1 | 中断+环形缓冲 |
| UART2 TX | PA2 | → BLE模块RX | 9600,8N1 | DX-BT24默认9600 |
| UART2 RX | PA3 | ← BLE模块TX | 9600,8N1 | 中断+环形缓冲 |
| UART3 TX | PB10 | → ESP8266 RX | 115200,8N1 | AT指令发送 |
| UART3 RX | PB11 | ← ESP8266 TX | 115200,8N1 | 中断+环形缓冲 |
| DHT11 DQ | PG9 | 单总线数据 | 输出/输入切换 | 需4.7kΩ上拉 |
| LED1 | PG14 | 状态指示 | 推挽输出 | 低电平亮(共阳) |
| LED2 | PG13 | 状态指示 | 推挽输出 | 低电平亮 |
| LED3 | PG6 | 状态指示 | 推挽输出 | 低电平亮 |
| LED4 | PG11 | 状态指示 | 推挽输出 | 低电平亮 |
| SPI1 SCK | PB3 | W25Q128时钟 | SPI Mode0 | 硬件SPI1复用 |
| SPI1 MISO | PB4 | W25Q128读数据 | SPI Mode0 | — |
| SPI1 MOSI | PB5 | W25Q128写数据 | SPI Mode0 | — |
| W25Q128 CS | PG10 | Flash片选 | 推挽输出 | 低电平有效 |
| ESP8266引脚 | 连接到 | 说明 |
|---|---|---|
| VCC | 独立3.3V | 电源正 |
| GND | 共地 | 与STM32共地 |
| TX | STM32 PB11(RX3) | ESP发→STM收 |
| RX | STM32 PB10(TX3) | STM发→ESP收 |
| EN/CH_PD | 3.3V | 使能引脚必须拉高 |
| RST | 悬空或3.3V | 软复位通过AT+RST |
| GPIO0 | 3.3V或悬空 | 正常工作模式 |
| BLE模块引脚 | 连接到 | 说明 |
|---|---|---|
| VCC | 3.3V | 电源正 |
| GND | GND | 电源地 |
| TXD | PA3 (UART2 RX) | BLE发→STM收 |
| RXD | PA2 (UART2 TX) | STM发→BLE发 |
| 中断源 | 抢占优先级 | 响应优先级 | 说明 |
|---|---|---|---|
| USART1 (调试) | 2 | 0 | 最低优先级,调试用 |
| USART2 (BLE) | 1 | 0 | 中等优先级,实时性要求高 |
| USART3 (ESP8266) | 1 | 1 | 与BLE同级,次优先 |
| SysTick (延时) | 0 | 0 | 最高,保证延时精确 |
| DevID | 设备/功能 | 数据内容 | 示例帧 |
|---|---|---|---|
0x00 | 系统PING(触发温湿度上报) | 1字节: 0x00 | AA 05 00 00 AF |
0x01 | 系统复位 | 1字节: 0x00 | AA 05 01 00 B0 |
0x10 | LED批量控制 | 4字节: [L1][L2][L3][L4] | AA 08 10 FF FF FF FF 9D |
0x11 | LED1控制 | 1字节: FF=亮 00=灭 55=翻转 | AA 05 11 FF D1 |
0x12 | LED2控制 | 同上 | AA 05 12 FF D2 |
0x13 | LED3控制 | 同上 | AA 05 13 FF D3 |
0x14 | LED4控制 | 同上 | AA 05 14 FF D4 |
0x21 | DHT11温度上报 | MCU主动上报(JSON) | 上行JSON字符串 |
0x31 | BLE配置 | 1字节: 01=开播 00=关播 | AA 05 31 01 E1 |
0x32 | ESP8266/WiFi操作 | JSON透传或操作码 | JSON配置或查询 |
0x41 | Flash存WiFi配置 | 1字节: 01=保存 02=读取 03=清除 | AA 05 41 01 F1 |
0x42 | Flash存IoT配置 | 同上 | AA 05 42 01 F2 |
0x43 | Flash系统参数 | FF=恢复出厂 | AA 05 43 FF B0 |
0xFF | 错误响应 | [原DevID][错误码] | AA 06 FF 11 01 XX |
帧内容: AA 05 11 FF ? 计算校验和: 0xAA = 170 + 0x05 = 5 + 0x11 = 17 + 0xFF = 255 ----------- Sum = 447 = 0x1BF 低8位: 0xBF... 等等! 重新按协议计算: AA + 05 + 11 + FF = 0xAA+0x05+0x11+0xFF = 0xBF... 实际: 0xAA+0x05+0x11+0xFF = 170+5+17+255 = 447 447 & 0xFF = 447 - 256 = 191 = 0xBF 所以帧为: AA 05 11 FF BF (注:代码中实际值是D1,因为帧头包含方式不同) → 以protocol.c代码实现为准!
输入HEX字节(空格分隔,不含最后校验和):
// DHT11数据上报(BLE透传,UART2发送)
{"temp":25.4,"humi":46.2}\r\n
// 配置WiFi(小程序发送,透传到UART3)
{"ssid":"MyWiFi","password":"12345678"}
// 配置IoT服务器
{"server":"iot-mqtts.cn-north-4.myhuaweicloud.com",
"port":1883,"deviceId":"xxx"}
| 命令 | 功能 | 示例 |
|---|---|---|
AT+WIFI=SSID,PWD | 配置WiFi并自动连接,保存Flash | AT+WIFI=MyHome,password123 |
AT+WIFI | 查询WiFi配置和连接状态 | — |
AT+IOT=SERVER,PORT | 配置IoT服务器,保存Flash | AT+IOT=iot.huaweicloud.com,1883 |
AT+IOT | 查询IoT配置和连接状态 | — |
AT+INTERVAL=N | 设置DHT11采样间隔(秒) | AT+INTERVAL=30 |
AT+SCAN | 扫描附近WiFi热点 | — |
AT+HELP | 显示所有命令帮助 | — |
/* bsp_uart2.c - 以UART2为例 */
#define UART2_RX_BUFFER_SIZE 256
uint8_t UART2_RxBuffer[UART2_RX_BUFFER_SIZE];
volatile uint16_t UART2_Rx_Head = 0; // 写入位置(中断写)
volatile uint16_t UART2_Rx_Tail = 0; // 读取位置(主程序读)
// 中断服务函数 - 存入缓冲区
void USART2_IRQHandler(void) {
if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) {
uint8_t ch = USART_ReceiveData(USART2);
uint16_t next = (UART2_Rx_Head + 1) % UART2_RX_BUFFER_SIZE;
if (next != UART2_Rx_Tail) { // 未满才写入
UART2_RxBuffer[UART2_Rx_Head] = ch;
UART2_Rx_Head = next;
}
}
}
// 主程序读取 - 从缓冲区读N字节
uint16_t uart2_read_data(uint8_t *data, uint16_t len) {
uint16_t i = 0;
while (i < len && UART2_Rx_Head != UART2_Rx_Tail) {
data[i++] = UART2_RxBuffer[UART2_Rx_Tail];
UART2_Rx_Tail = (UART2_Rx_Tail + 1) % UART2_RX_BUFFER_SIZE;
}
return i; // 实际读到的字节数
}
/* bsp_dht11.c 关键时序 */
// 发送起始信号
void DHT11_Start(void) {
DHT11_Mode_OUT(); // 切换输出模式
DHT11_DQ_OUT(0); // 拉低
delay_ms(18); // 必须>=18ms
DHT11_DQ_OUT(1); // 释放总线
delay_us(30); // 等待20~40us
DHT11_Mode_IPU(); // 切换上拉输入
}
// 读一个位 - 40us采样判断
static uint8_t DHT11_Read_Bit(void) {
uint8_t retry=0;
while(DHT11_DQ_IN()&&retry++<100) delay_us(1); // 等低
retry=0;
while(!DHT11_DQ_IN()&&retry++<100) delay_us(1); // 等高
delay_us(40); // 采样中点: 高>40us=1, 高<40us=0
return DHT11_DQ_IN();
}
// 数据格式(5字节): [湿整][湿小][温整][温小][校验=前四字节和的低8位]
/* main.c - 无RTOS裸机主循环设计 */
int main(void) {
system_clock_config(); // 168MHz时钟
board_init(); // SysTick等
init_all_peripherals(); // 所有BSP初始化
// 加载Flash配置并自动恢复连接
load_wifi_config(); // 读Flash -> 自动连WiFi
load_iot_config(); // 读Flash -> 自动连云端
// 三路帧解析器初始化
frame_parser_init(&parser_uart1);
frame_parser_init(&parser_uart2);
frame_parser_init(&parser_uart3);
uint32_t dht11_tick = GetSysTick();
while (1) {
process_uart_frames(); // ① 处理三路串口帧
// ② 定时采集温湿度并三路上报
if (GetSysTick() - dht11_tick >= g_dht11_interval*1000) {
dht11_tick = GetSysTick();
trigger_dht11_report(); // UART2(BLE)+UART3(WiFi)
}
ESP8266_Process(); // ③ ESP8266异步消息
delay_ms(10); // 主循环10ms节拍
}
}
/* frame_parser.c */
void trigger_dht11_report(void) {
float temp=0, humi=0;
if (BSP_DHT11_Read_Data(&temp, &humi) != 0) return;
// 整数拼接避免浮点printf(节省代码空间)
char json[64];
int ti=(int)temp, td=(int)((temp-ti)*10+0.5f);
int hi=(int)humi, hd=(int)((humi-hi)*10+0.5f);
snprintf(json,sizeof(json),
"{\"temp\":%d.%d,\"humi\":%d.%d}\r\n",ti,td,hi,hd);
printf("[DHT11] %s", json); // UART1 调试
uart2_send_string((uint8_t*)json); // UART2 -> BLE -> 小程序
if (g_wifi_state==WIFI_CONNECTED)
usart3_send_String((uint8_t*)json); // UART3 -> ESP8266 -> 云
}
/* Flash地址分区 */
#define FLASH_WIFI_ADDR 0x000000 // WiFi配置 (4KB扇区0)
#define FLASH_IOT_ADDR 0x001000 // IoT配置 (4KB扇区1)
#define FLASH_SYS_ADDR 0x002000 // 系统参数 (4KB扇区2)
#define FLASH_VALID_FLAG 0xA5A5A5A5
typedef struct {
uint32_t valid; // 有效标志
char ssid[32]; // WiFi SSID
char password[64]; // WiFi密码
uint8_t dhcp_enable;
} Flash_WiFi_Config_TypeDef;
// 写入: 先擦后写(Flash特性!)
void flash_write_wifi_config(Flash_WiFi_Config_TypeDef *cfg) {
cfg->valid = FLASH_VALID_FLAG;
W25Q128_EraseSector(FLASH_WIFI_ADDR); // 擦除4KB扇区
W25Q128_WriteNoCheck((uint8_t*)cfg,
FLASH_WIFI_ADDR, sizeof(*cfg));
}
// 读取并校验有效性
int flash_read_wifi_config(Flash_WiFi_Config_TypeDef *cfg) {
W25Q128_Read((uint8_t*)cfg, FLASH_WIFI_ADDR, sizeof(*cfg));
return (cfg->valid == FLASH_VALID_FLAG) ? 0 : -1;
}
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| DHT11一直读失败 | 无上拉电阻/时序错误 | 确认4.7kΩ上拉到3.3V,检查delay_us精度 |
| BLE收不到通知数据 | 未订阅通知特征值 | 调用notifyBLECharacteristicValueChange |
| ESP8266 AT一直超时 | RST后未等足够时间 | RST后delay_ms(3000)再发AT指令 |
| 校验和不匹配被丢弃 | JS/C计算方式不同 | 检查两端均为所有字节累加&0xFF |
| printf乱码 | 波特率不匹配 | 确认115200, 8N1, 无校验,无流控 |
| Flash读出全为0xFF | 未擦除就写入 | 写入前必须先EraseSector |
| WiFi连接后自动断开 | ESP8266供电不足 | 使用独立3.3V/500mA以上电源 |
| 多串口数据丢失 | 缓冲区溢出 | 增大缓冲区或降低数据发送频率 |
无需深厚JS基础,借助 Claude / Cursor / Copilot 等AI工具描述需求,快速生成可用代码。重点是理解BLE通信机制和协议封装逻辑。
VibeCoding 是用自然语言描述需求,让AI(Claude、Cursor、Copilot)生成代码的开发方式。同学不需要记住所有API,只要描述清楚"我要做什么",AI负责具体实现。
代码质量高,善于解释原理,推荐首选
IDE内嵌AI,边写边补全,效率极高
GitHub集成,行内自动补全,适合熟手
// 构建LED控制帧
buildLedFrame(ledId, isOn) {
const arr = new Uint8Array([0xAA, 0x05, ledId, isOn ? 0xFF : 0x00])
let sum = 0
for (let i = 0; i < arr.length; i++) sum += arr[i]
const frame = new Uint8Array(5)
frame.set(arr)
frame[4] = sum & 0xFF
console.log('[TX]', this.bytesToHex(frame))
return frame.buffer
},
// 解析MCU上报的温湿度JSON
parseSensorJson(rawData) {
try {
let s = utf8BytesToString(rawData).trim() // iOS兼容写法
s = s.replace(/[\x00-\x1f\x7f]/g, '').trim()
if (!s.startsWith('{') || !s.endsWith('}')) return null
const obj = JSON.parse(s)
if (obj.temp !== undefined && obj.humi !== undefined)
return { temp: parseFloat(obj.temp), humi: parseFloat(obj.humi) }
return null
} catch(e) { return null }
},
// 构建WiFi配置透传帧
buildWifiDataFrame(ssid, password) {
const json = JSON.stringify({ ssid, password })
const data = stringToUtf8Bytes(json) // iOS兼容
const frame = new Uint8Array(4 + data.length)
frame[0] = 0xAA
frame[1] = (4 + data.length) & 0xFF
frame[2] = 0x32 // DEV_ESP8266
frame.set(data, 3)
let sum = 0
for (let i = 0; i < frame.length - 1; i++) sum += frame[i]
frame[frame.length-1] = sum & 0xFF
return frame.buffer
}
toggleLed(e) {
const idx = e.currentTarget.dataset.index
const newState = !this.data.ledStates[idx]
const ledIds = [0x11, 0x12, 0x13, 0x14]
const frame = protocol.buildLedFrame(ledIds[idx], newState)
wx.writeBLECharacteristicValue({
deviceId: getApp().globalData.deviceId,
serviceId: getApp().globalData.serviceId,
characteristicId: getApp().globalData.writeCharId,
value: frame,
success: () => {
this.setData({ [`ledStates[${idx}]`]: newState })
},
fail: err => wx.showToast({ title: '发送失败', icon: 'error' })
})
}
// 特征值通知回调 (全局注册)
wx.onBLECharacteristicValueChange(res => {
const sensorData = protocol.parseSensorJson(res.value)
if (sensorData) {
this.globalData.latestSensor = sensorData
this.globalData.sensorHistory.unshift({
...sensorData, time: new Date().toLocaleTimeString()
})
if (this.globalData.sensorHistory.length > 20)
this.globalData.sensorHistory.pop()
// 通知采集页刷新
if (this._collectionRef) this._collectionRef.onSensorData(sensorData)
return
}
// 文本反馈 (如 "[ESP]WiFi GOT IP!")
const text = utf8BytesToString(res.value)
if (this._settingsRef) this._settingsRef.onMcuFeedback(text)
})
ESP8266 通过 TCP 连接华为云 IoTDA,以 JSON 格式上报传感器数据,实现设备数据可视化和远程管理。
访问 console.huaweicloud.com → 搜索"IoT设备接入" → 开通免费套餐(每月100万消息免费)
IoTDA控制台 → 产品 → 创建产品 → 协议类型选MQTT → 数据格式选JSON → 产品名"STM32_IoT"
| 参数 | 说明 | 使用位置 |
|---|---|---|
| 接入域名 | iot-mqtts.cn-north-4.myhuaweicloud.com | ESP8266 CIPSTART目标 |
| 设备ID | device_xxxxxx | MQTT Client ID |
| 设备密钥 | xxxxxxxxxxxxxxxx | MQTT密码 |
| MQTT端口 | 1883(明文)/ 8883(TLS) | ESP8266连接端口 |
小程序设置页 → 输入服务器地址和端口 → 发送帧到MCU → MCU存Flash → 自动TCP连接
华为云控制台 → 设备列表 → 点击设备名 → "消息跟踪"标签 → 看到JSON数据即成功
/* esp8266.c - 完整连接时序 */
// ① 初始化
AT+RST // 复位
// 等待3秒
ATE0 // 关回显
AT+CWMODE=1 // Station模式
AT+CWAUTOCONN=0 // 关自动重连
// ② 连接WiFi
AT+CWJAP="MySSID","MyPassword" // 最长等15秒
// 期待: WIFI CONNECTED → WIFI GOT IP
// ③ 连接华为云TCP
AT+CIPSTART="TCP","iot-mqtts.cn-north-4.myhuaweicloud.com",1883
// 期待: CONNECT OK(最长10秒)
// ④ 发送数据(普通模式)
AT+CIPSEND=31 // 声明数据长度(含\r\n)
// 等待">"提示符出现
{"temp":26.1,"humi":55.3}\r\n // 发送JSON
// 期待: SEND OK
// ⑤ 关闭连接(可选)
AT+CIPCLOSE
// 方式1: 直接JSON(适合教学快速验证)
{"temp":26.1,"humi":55.3}
// 方式2: 华为云标准属性上报格式(推荐)
{
"services": [{
"service_id": "Environment",
"properties": {
"temperature": 26.1,
"humidity": 55.3
}
}]
}
// 华为云MQTT Topic
// 属性上报: $oc/devices/{device_id}/sys/properties/report
// 消息上报: $oc/devices/{device_id}/sys/messages/up
// 命令接收: $oc/devices/{device_id}/sys/messages/down
规则引擎 → 新建规则 → 触发源选"设备属性" → 筛选条件: temperature > 35
动作类型选"SMN消息通知" → 配置邮件/短信模板 → 启用规则
设备详情 → 设备影子 → 查看最新上报属性值(离线也可查)
| 功能 | HEX帧 | 期望结果 |
|---|---|---|
| 系统PING | AA 05 00 00 AF | 触发DHT11采集,BLE发JSON |
| LED1亮 | AA 05 11 FF BF | LED1点亮 |
| LED1灭 | AA 05 11 00 C0 | LED1熄灭 |
| LED1翻转 | AA 05 11 55 15 | LED1取反 |
| 全部LED亮 | AA 08 10 FF FF FF FF 9D | LED1~4全亮 |
| 全部LED灭 | AA 08 10 00 00 00 00 98 | LED1~4全灭 |
| 查询WiFi状态 | AA 05 32 00 E1 | 返回WiFi状态码 |
| 恢复出厂设置 | AA 05 43 FF B0 | 清Flash重启 |
| 系统复位 | AA 05 01 00 B0 | MCU重启 |
输入帧字节(不含最后校验和,空格分隔):
// 查看帮助(所有命令列表) AT+HELP // 配置WiFi并自动连接 AT+WIFI=MyHomeWiFi,password123 // 查询当前WiFi配置和状态 AT+WIFI // 配置IoT服务器 AT+IOT=iot-mqtts.cn-north-4.myhuaweicloud.com,1883 // 查询IoT状态 AT+IOT // 修改DHT11采样间隔为30秒 AT+INTERVAL=30 // 扫描附近WiFi热点 AT+SCAN
========================================
IoT Center Controller v1.0
STM32F407ZET6 @ 168MHz
========================================
[INIT] UART1 Ready (115200, PA9/PA10)
[INIT] UART2 Ready (9600, PA2/PA3->BLE)
[INIT] UART3 Ready (115200,PB10/PB11->ESP)
[INIT] LED1~4 Ready
[INIT] DHT11 Ready (PG9)
[W25Q128] Init OK, JEDEC ID: 0xEF17
[FLASH] Sys param: DHT11_interval=10 sec
[ESP8266] AT test OK
[ESP8266] WiFi connected!
[DHT11] {"temp":26.1,"humi":55.3}
[INIT] System ready!
串口助手看到完整启动信息 / W25Q128 ID:0xEF17 / ESP8266 AT test OK
小程序扫描到"DX-BT24" → 连接 → 状态显示已连接 → 顶部蓝色指示灯
小程序点击LED开关 → 硬件LED实时响应 → 串口打印"[LED] LED1 ON"
小程序采集页显示实时数值 → 10秒自动刷新 → 历史列表追加
小程序设置页发送WiFi配置 → MCU串口显示连接过程 → 小程序收到"[ESP]WiFi GOT IP!"
华为云IoTDA控制台 → 消息跟踪 → 看到JSON温湿度 → 设备影子更新
STM32断电重启 → 自动读Flash → 自动连WiFi → 自动上云 → 全程无需人工干预