物联网项目介绍
165.1物联网项目介绍
/*
* 本代码块只收录第 165 课在 Keil 中实际停留展示的业务/接口适配片段,内容来自视频版 gizwits_product.c。
* 本课只是介绍完整项目,没有现场编写或讲解这些代码;以下片段也不是可独立编译的完整文件。
* 视频快速滚动时没有完整展示 USART3 回调的结尾,因此这里只抄清晰可见且边界可确认的代码,不擅自补齐。
* 课程配套目录中的最终工程与视频画面在少量变量名、定时器编号上存在差异,这里以本课视频画面为准。
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) /* HAL 定时器周期到达后的统一回调;本项目在其中维护云协议节拍和 ZigBee 心跳计数。 */
{ /* 回调函数体开始。 */
if(htim==&htim2) /* 判断本次回调是否由 TIM2 触发。 */
{ /* TIM2 分支开始。 */
keyHandle(); /* 调用机智云工程的按键处理函数。 */
gizTimerMs(); /* 将机智云协议使用的毫秒计数递增一次。 */
} /* TIM2 分支结束。 */
if(htim==&htim3) /* 判断本次回调是否由 TIM3 触发。 */
{ /* TIM3 分支开始。 */
zigbee_heart_count++; /* 增加 ZigBee 心跳超时计数;收到心跳帧时会在下方代码中清零。 */
if(zigbee_heart_count>100) /* 计数超过 100 后进入本项目设置的保持区间。 */
zigbee_heart_count=91; /* 回写 91,使掉线状态可持续被上层逻辑识别,而不是无界累加。 */
} /* TIM3 分支结束。 */
} /* 定时器周期回调结束;具体一次回调代表多久,要由 TIM2/TIM3 配置决定。 */
void timerInit(void) /* 启动本项目使用的两个基本定时器中断。 */
{ /* 定时器启动函数开始。 */
HAL_TIM_Base_Start_IT(&htim2); /* 启动 TIM2 基本定时器并打开更新中断。 */
HAL_TIM_Base_Start_IT(&htim3); /* 启动 TIM3 基本定时器并打开更新中断,用于心跳计数。 */
} /* 定时器启动函数结束;课程本节没有展示两个定时器的周期参数。 */
/* HAL_UART_RxCpltCallback() 中视频清晰展示的 USART2 分支。 */
if(UartHandle->Instance == USART2) /* 判断收到 1 字节的串口是不是连接 Wi-Fi 模组的 USART2。 */
{ /* USART2 接收完成分支开始。 */
gizPutData((uint8_t *)&aRxBuffer, 1); /* 把刚收到的 1 字节送进机智云协议层的接收缓冲区。 */
HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1); /* 重新挂接下一次 1 字节中断接收。 */ //开启下一次接收中断
} /* USART2 接收完成分支结束。 */
/* HAL_UART_RxCpltCallback() 中视频清晰展示的 USART3 数据帧校验与分发片段。 */
if(0x55==recv_buff[0]&&recv_buff[0]+recv_buff[1]+recv_buff[2]==recv_buff[3]&&recv_buff[4]==0xAA) /* 同时检查帧头、三字节加和及帧尾。 */
{ /* 只有格式与简单校验都符合时才处理这 5 字节帧。 */
switch(recv_buff[1]) /* 第 2 字节作为命令码,决定怎样解释第 3 字节或清除哪个状态。 */
{ /* 命令分发表开始。 */
case 0x5: /* 收到命令码 0x05。 */
led2_on_cmd=0; /* 按视频源码把卧室灯“开”命令标志清零;不是直接给 LED 引脚写 0。 */
break; /* 结束 0x05 分支,避免继续落入下一个 case。 */
case 0x6: /* 收到命令码 0x06。 */
led2_off_cmd=0; /* 把卧室灯“关”命令标志清零。 */
break; /* 结束 0x06 分支。 */
case 0x7: /* 收到命令码 0x07。 */
curtain_on_cmd=0; /* 把窗帘“开”命令标志清零。 */
break; /* 结束 0x07 分支。 */
case 0x8: /* 收到命令码 0x08。 */
curtain_off_cmd=0; /* 把窗帘“关”命令标志清零。 */
break; /* 结束 0x08 分支。 */
case 0x9: /* 收到命令码 0x09,表示远端温度数据。 */
currentDataPoint.valuetemp2 = recv_buff[2]; /* 把第 3 字节保存到机智云数据点 temp2。 */
break; /* 结束温度分支。 */
case 0xa: /* 收到命令码 0x0A,表示远端湿度数据。 */
currentDataPoint.valuehumi2 = recv_buff[2]; /* 把第 3 字节保存到机智云数据点 humi2。 */
break; /* 结束湿度分支。 */
case 0xb: /* 收到命令码 0x0B,表示远端仍在线的心跳。 */
zigbee_heart_count = 0; /* 心跳到达即清零超时计数,供屏幕显示 ZigBee OK。 */
break; /* 结束心跳分支。 */
default:break; /* 未识别命令不做处理,并离开 switch。 */
} /* 命令分发表结束。 */
} /* 当前 5 字节帧处理结束;回调其余部分在本课视频中未完整展示。 */
void uartInit(void) /* 初始化机智云协议使用的串口接收。 */
{ /* 串口接收启动函数开始。 */
HAL_UART_Receive_IT(&huart2, (uint8_t *)&aRxBuffer, 1); /* 首次挂接 USART2 的单字节中断接收。 */ //开启下一次接收中断
} /* 本课画面中的 uartInit() 到此结束,不补入其他串口初始化。 */
int32_t uartWrite(uint8_t *buf, uint32_t len) /* 机智云协议层调用的发送适配函数:把 buf 中 len 字节送给 Wi-Fi 模组。 */
{ /* 串口发送函数开始。 */
uint8_t crc[1] = {0x55}; /* 保存需要追加的 0x55 转义字节;变量名叫 crc,但这里并不是 CRC 计算。 */
uint32_t i = 0; /* 32 位循环下标,从缓冲区第 0 字节开始发送。 */
if(NULL == buf) /* 发送前检查调用者是否传入空指针。 */
{ /* 空指针错误分支开始。 */
return -1; /* 返回 -1 表示没有可发送的有效缓冲区。 */
} /* 空指针错误分支结束。 */
for(i=0; i<len; i++) /* 从头到尾遍历 len 个原始协议字节。 */
{ /* 单字节发送循环开始。 */
HAL_UART_Transmit_IT(&huart2, (uint8_t *)&buf[i], 1); /* 通过 USART2 中断方式启动当前 1 字节发送。 */
while (huart2.gState != HAL_UART_STATE_READY); /* 空循环等待句柄恢复 READY,保证下一字节不会过早启动。 */ //Loop until the end of transmission
if(i >=2 && buf[i] == 0xFF) /* 跳过帧头两个 0xFF;若后续数据出现 0xFF,就执行协议转义。 */
{ /* 追加转义字节分支开始。 */
HAL_UART_Transmit_IT(&huart2, (uint8_t *)&crc, 1); /* 紧跟原 0xFF 再发送一个 0x55。 */
while (huart2.gState != HAL_UART_STATE_READY); /* 等待追加的 0x55 发送完成。 */ //Loop until the end of transmission
} /* 追加转义字节分支结束。 */
} /* 原始缓冲区遍历结束。 */
#ifdef PROTOCOL_DEBUG /* 只有定义协议调试宏时才编译下面的十六进制日志。 */
GIZWITS_LOG("MCU2WiFi[%4d:%4d]: ", gizGetTimerCount(), len); /* 打印当前协议计时值和原始数据长度。 */
for(i=0; i<len; i++) /* 再遍历一遍原始缓冲区,用于输出调试日志。 */
{ /* 调试打印循环开始。 */
GIZWITS_LOG("%02x ", buf[i]); /* 以两位十六进制打印当前原始字节。 */
if(i >=2 && buf[i] == 0xFF) /* 日志也要反映真正在线路上追加的转义字节。 */
{ /* 日志转义分支开始。 */
GIZWITS_LOG("%02x ", 0x55); /* 在日志中额外打印 0x55。 */
} /* 日志转义分支结束。 */
} /* 调试打印循环结束。 */
GIZWITS_LOG("\n"); /* 一帧打印完成后换行。 */
#endif /* 协议调试条件编译结束。 */
return len; /* 返回调用者要求发送的原始数据长度,不包含额外插入的 0x55。 */
} /* 串口发送适配函数结束。 */
一、物联网项目
本课先给出项目全貌,再演示最终效果和硬件连接,并没有开始从零实现代码。完整系统不是“手机直接连单片机”,而是由卧室终端、客厅中控、无线串口链路、Wi-Fi 模组、云平台和手机 App 共同组成。理解每段链路负责什么,比一开始钻进整个工程更重要。
1.1 物联网项目框图
flowchart LR
A[卧室温湿度] --> M51[51 单片机<br/>卧室终端]
M51 --> B[舵机/窗帘]
M51 --> C[卧室 LED]
M51 --> D[卧室 OLED]
M51 <--> Z1[透明串口模块]
Z1 <-->|2.4 GHz ZigBee 无线链路| Z2[透明串口模块]
Z2 <--> M32[STM32<br/>客厅中控]
E[客厅温湿度] --> M32
F[光敏传感器] --> M32
G[烟雾传感器] --> M32
M32 --> H[客厅 LED]
M32 --> I[客厅 OLED]
M32 <--> W[ESP8266 Wi-Fi 模组]
W <--> Cloud[机智云]
Cloud <--> App[手机 App]
- 51 单片机模拟卧室终端:采集卧室温湿度,驱动舵机模拟窗帘,控制卧室灯,并在 OLED 上显示本地状态。
- STM32 模拟客厅中控:采集客厅温湿度、光照和烟雾状态,控制客厅灯,在 OLED 上汇总显示本机数据与卧室端数据。
- 两块 MCU 之间各接一个无线透明串口模块。MCU 看见的是 UART 字节流,模块负责把字节无线转发给另一端。
- STM32 再通过 USART2 与带机智云固件的 ESP8266 模组交换协议帧。ESP8266 连接 2.4 GHz Wi-Fi,把数据送入机智云;手机 App 通过互联网访问云端。
- 图中手机侧的“WiFi、5G”表示手机可经 Wi-Fi 或蜂窝网络访问云平台;它不表示 ESP8266 支持 5 GHz Wi-Fi,也不表示手机的 5G 蜂窝信号直接发给 ESP8266。
系统中有两条方向相反的数据链:
| 方向 | 典型路径 | 本课示例 |
|---|---|---|
| 状态上报 | 传感器 → MCU → ZigBee/ESP8266 → 云平台 → App | 温湿度、光照、烟雾、灯和窗帘状态 |
| 远程控制 | App → 云平台 → ESP8266 → STM32 → 本地执行或 ZigBee → 51 | 客厅灯、卧室灯、窗帘开关 |
- 远程链路中的每一跳都有不同职责:传感器把物理量变成电信号,MCU 做采集和业务判断,透明串口模块转发本地帧,ESP8266 提供 IP 网络接入,云平台负责设备与 App 间的消息中转。
- “ZigBee 本质上也是串口”是原笔记中过度简化的说法。准确地说,本课购买的成品模块向 MCU 暴露 UART 透明传输接口;其无线侧仍是基于 IEEE 802.15.4 的 2.4 GHz 无线通信,ZigBee 本身并不等同于 UART。
- 本项目使用点对点透明传输,没有在课上实现通用 ZigBee Mesh 网络。成品模块已经封装无线协议,因此 MCU 代码主要面对自定义的 5 字节应用帧。
1.2 物联网项目演示
- 视频中的“三个屏幕”是两块开发板的 OLED 加手机 App:STM32 OLED 代表“智能家居中控”,51 OLED 代表卧室终端,手机 App 是远程查看和控制界面。
- STM32 端显示客厅温湿度、烟雾状态、光照百分比、LED 状态和
ZigBee OK/Error。画面中的Sound标签实际被课程代码拿来显示光照强度,不是声音传感器读数。 - 51 端显示卧室温湿度与窗帘状态;讲师说明演示版界面漏掉了卧室灯状态显示,这不影响灯本身受控。
- App 点击“客厅灯”后,STM32 板上的 LED 随之开关;点击“卧室灯”后,命令还要经过 STM32 和两个无线串口模块到达 51;点击窗帘开关后,51 控制舵机转到相应位置。
- App 同时显示客厅温湿度、光照、烟雾以及卧室温湿度。这些是设备上报到云端后再由 App 读取的实时业务数据,并不是手机自身传感器测得的数据。
心跳不是无线模块自带的“通电检测”,而是两块 MCU 约定的应用层消息:
- 51 端周期发送心跳命令帧。
- STM32 的 USART3 收到命令码
0x0B后,把zigbee_heart_count清零。 - TIM3 周期中断持续增加该计数;长时间没有新心跳时,计数进入超时区间。
- 上层显示逻辑据此显示
ZigBee OK或ZigBee Error。
- 视频拔掉 51 开发板电源后,STM32 过几秒显示错误;重新上电并恢复帧交换后又显示正常。这说明检测的是“远端程序仍在通信”,而不只是两个模块是否亮灯。
- 心跳只能证明最近收到过符合协议的远端帧,不能单独证明温湿度值一定正确,也不能定位故障究竟出在 51、UART、无线链路还是供电。
- 源码中
0x55是帧头、0xAA是帧尾,第 4 字节等于前三个字节之和才继续处理。这只是课程自定义的简单加和校验,不是 CRC,也不具备强错误检测能力。
关于供电,视频中的现象是 ESP8266 经常掉线,改用外部手机充电头供电、并把所用模组板接到其标注的 5 V 供电端后更稳定。应把这个经验准确理解为:
- ESP8266 发射时存在明显电流脉冲;稳压器能力不足、线材压降、接口接触、去耦不佳都可能让 3.3 V 电源瞬间下跌,引起复位或断线。
- 电脑 USB 和手机充电头都通常提供 5 V,稳定性差异来自具体端口限流能力、线缆和板上稳压设计,不能概括成“电脑 USB 一定不稳定”。
- 只有带板载稳压器且明确标出
5V/VIN的 ESP8266 模组板才能从该端输入 5 V。ESP8266EX 芯片工作电压是 2.5~3.6 V,GPIO 也不耐 5 V;绝不能把 5 V 直接接到芯片、3V3引脚或 UART IO。 - 视频改电压的对象是课程使用的模组/转接板供电端,不是把 ESP8266 芯片供电改成 5 V。接线前必须以自己购买模块的原理图、丝印和数据手册为准。
1.3 讲课思路
- 第一阶段先认识系统框图和硬件,明确每个模块的供电、接口、数据方向与作用。
- 第二阶段只用 STM32、温湿度传感器、ESP8266 和机智云完成一个最小 Demo,把“传感器 → 云 → 手机”单链路先跑通。
- 第三阶段再把客厅传感器、显示、灯控、51 卧室终端、ZigBee 链路、心跳和控制协议逐步合并成完整项目。
- 这样的拆分能把故障限制在较小范围。若直接一次接上所有模块,任何一个串口波特率、供电、数据点映射或帧格式错误都可能表现为“App 没反应”。
- 机智云代码不是全部手写:平台根据产品数据点生成协议框架,开发者再完成 UART、定时、复位等硬件适配,并把传感器值和控制事件接入
currentDataPoint。 - 视频只是快速滚动 STM32 与 51 工程,用来说明完整项目代码量较大;没有逐段讲解,也没有要求此时照抄整个工程。因此代码块只保留画面中与架构直接相关的三个片段。
代码块能提前看出三个接口边界:
HAL_TIM_PeriodElapsedCallback()给机智云协议时基和 ZigBee 心跳超时提供周期事件。- USART3 接收片段解析 51 与 STM32 之间的 5 字节帧,更新卧室温湿度、命令确认和心跳状态。
uartWrite()把机智云协议帧经 USART2 发给 Wi-Fi 模组;从第 3 字节开始,数据中的0xFF后要附加0x55,这是机智云协议要求的字节转义,源码变量名crc并不准确。HAL_UART_Transmit_IT()名义上是中断发送,但随后用空while等待gState回到READY,所以本函数仍逐字节串行等待。精教版只解释现状,不在本节擅自改成 DMA、阻塞发送或队列方案。
1.4 硬件选择
1.4.1 ZigBee无线串口收发模块
- 视频使用的是 CC2530 类 2.4 GHz 无线串口透明传输模块,页面标有 VCC、TX、RX、GND、四个状态 LED 和一个配置按键。
- “透明传输”表示模块尽量按原顺序转发 MCU 送入 UART 的字节;它不理解
0x55、温湿度或灯控的业务意义,帧格式和重传/心跳等应用规则由两端 MCU 约定。 - CC2530 是支持 2.4 GHz IEEE 802.15.4/ZigBee 的无线 SoC,并带有 USART 外设。成品模块把协议栈、射频和串口接口封装在一起,才形成“像无线串口一样使用”的体验。
两块 MCU 不使用无线模块时,也可以先用杜邦线直接验证 UART 协议:
STM32 TX ─────────► 51 RX
STM32 RX ◄───────── 51 TX
STM32 GND ────────── 51 GND
- TX 必须接对方 RX,RX 必须接对方 TX,并且两块板必须共地。共地给双方提供相同的电压参考,否则“高/低电平”没有可靠共同基准。
- 还要确认双方 UART IO 电平兼容。供电引脚允许 5 V 不自动代表 TX/RX 是 5 V 逻辑耐受;模块板是否做电平转换必须查其说明。
- 两端设置相同的帧格式,包括数据位、校验位和停止位;每个 MCU 的波特率要与本地连接的模块一致。
- 视频建议先用线缆完成协议和业务调试,再替换成无线模块。这样可以把“应用协议错误”和“无线配置/射频问题”分开排查。
烧写 51 程序时可能出现冲突,因为下载器和 STM32/ZigBee 都会占用 51 的同一组串口线:
- PC 的 USB 转串口正在向 51 RX 发送下载数据时,如果 STM32 TX 也连接并发送,两个数据源会混在一起,下载协议无法正确识别。
- 若两个推挽输出被直接并接且电平相反,不只是数据混乱,还可能产生硬件争用电流。
- 处理方法是烧写前断开 STM32/ZigBee 到 51 串口的连接,只保留下载器;烧写完成后再断电恢复业务接线。
- 更稳妥的硬件设计可以加入跳帽、串联电阻、模拟开关或使用不同 UART,把“下载通道”和“运行通信通道”隔离。
1.4.2 ZigBee无线串口收发模块配置
视频所示 DL-2X 系列页面给出的配置流程是:进入设置模式 → 设置波特率 → 设置信道 → 设置工作模式 → 确认设置。具体 LED 图案只适用于该型号,购买不同模块时不能照搬。
- 模块断电,按住配置键不松手,再给模块供电。
- 四个 LED 循环闪烁后松开,进入设置模式。
- 在波特率阶段短按按键轮换选项;页面列出 2400、4800、9600、14400、19200、38400、57600、115200,课程选择 9600 bit/s。
- 长按直到 LED 再次循环闪烁,松开进入信道设置;两个点对点模块必须选到同一信道。
- 再进入工作模式设置,一个模块选点对点 A 端,另一个选点对点 B 端;课程不使用广播模式。
- 按该型号说明完成确认并重新上电,然后先用简单串口数据验证双向通信,再接入完整业务帧。
- “同信道”决定两个无线端能否在同一射频频道上通信;“UART 波特率”决定 MCU 与其本地模块能否正确交换字节。这是两层不同配置。
- 页面提供 16 个 2.4 GHz 信道。若周围 2.4 GHz Wi-Fi 较拥挤,可以在两个模块保持同信道的前提下尝试其他信道,但课程没有展开频谱规划。
- 页面宣传传输距离约 250 m,应理解为特定模块在理想测试条件下的标称值。墙体、天线方向、安装高度、干扰、发射功率和供电都会显著缩短实际距离,不能把 250 m 当成室内保证值。
- 广播/多对一模式可以扩展为多个卧室或书房终端向一个 STM32 中控上报数据,但多个终端同时发送会产生冲突风险,还需要地址、时隙/退避、重试和去重等应用协议;本课只提出扩展设想,没有实现代码。
ESP8266 的 Wi-Fi 与 ZigBee 模块配置不要混淆:
- ESP8266EX 支持 802.11 b/g/n 的 2.4 GHz Wi-Fi,不支持 5 GHz Wi-Fi。手机显示的 5G 蜂窝网络与路由器的 5 GHz Wi-Fi 是两件完全不同的事。
- 双频路由器把 2.4 GHz 与 5 GHz 共用一个 SSID 时,ESP8266 理论上仍可能连接 2.4 GHz,但某些配网流程或频段引导会造成困难。把两个频段临时分成不同名称,能更明确地保证手机和模组在配网时使用 2.4 GHz。
- 改 SSID、换充电头或改供电端后“更稳定”是课程实测经验,不等于所有掉线都由同一原因造成。还应检查 RSSI、路由器兼容性、模组固件、串口错误、稳压器余量和去耦电容。
参考资料
- Espressif ESP8266EX Datasheet:核对 802.11 b/g/n、2.4 GHz 频段、2.5~3.6 V 芯片工作电压和 IO 电气限制。
- Espressif ESP-FAQ:Hardware Design:核对 ESP8266 不耐 5 V,以及供电与电平兼容注意事项。
- Texas Instruments CC2530 Datasheet:核对 CC2530 是面向 2.4 GHz IEEE 802.15.4/ZigBee 的 SoC,并带 USART 外设。
- Connectivity Standards Alliance ZigBee FAQ:核对 ZigBee 与 IEEE 802.15.4、2.4 GHz 信道、典型距离及 Mesh 能力的关系。
- 机智云通用平台版 MCU SDK 移植指南:核对
uartWrite()的移植职责和数据中出现0xFF时追加0x55的协议要求。 - STMicroelectronics STM32F1 HAL UART 头文件:核对中断收发接口、UART 状态与接收完成回调的 HAL 定义。