搞这个项目的人,十有八九是被"手机远程控制"这几个字吸引过来的。我在实验室带的几个同学,大一刚学完单片机就想做这个,买了一块板子和一个串口WiFi模块,折腾一个多星期,最后卡在了"怎么让手机和单片机真正聊上天"这一步。其实整个项目拆开看一点都不神秘,就是三件事:WiFi模块把手机发来的数据变成串口数据,单片机解析串口数据,然后再根据不同的指令控制LED和舵机。难就难在中间这一环,WiFi模块的配置、串口数据的接收、协议的约定,任何一个地方没想清楚都会卡壳。
这篇文章就围绕这个项目,从方案选型、硬件接线、数据通路、控制逻辑、手机端到联调排错,把我实际跑通的整套思路写出来。整个过程完全用常见模块和最小改动的思路来实现,不依赖那些看起来很炫但门槛很高的方案,适合刚接触物联网方向的嵌入式初学者,也适合实验室做入门Demo的参考。
1. 先想清楚:这个项目到底解决什么问题,方案怎么选
1.1 拆解需求:控制链路和三个核心模块
别看标题只有一句话,拆开之后你就能看到这个项目的边界。整个控制链路是:
手机 -> WiFi模块 -> STM32串口 -> 控制逻辑 -> LED/舵机
这个链路里,手机端负责发指令,WiFi模块负责把"无线数据"翻译成"串口数据",STM32负责听懂指令并执行。真正需要你动手的点,其实是三个模块各自的内部逻辑以及它们之间的接口。
第一点是WiFi模块和STM32的串口对接。WiFi模块一般都走AT指令协议,上电之后先用AT指令把它配置成透传模式,或者配置成TCP Server,让它能接收来自手机的连接。串口波特率、数据位、停止位必须和STM32侧保持一致,这是新人最容易忽略的。
第二点是STM32侧的串口接收。WiFi模块是外设,它往串口发数据是不等你的,所以接收必须放到中断或者DMA里。直接在主循环里轮询接收标志,会丢数据。
第三点是协议约定,也就是手机发什么格式的字符串,单片机才知道要干什么。这一步没有统一标准,我后面会给出一套最省事的文本协议。
1.2 无线方案横评:为什么WiFi是这个项目的最优选
做远程控制,市面上能选的无线方案有好几种,各有各的使用场景。
| 方案 | 通信距离 | 手机支持 | 开发成本 | 适合场景 |
|---|---|---|---|---|
| 蓝牙BLE | 10米左右 | 需要做APP或小程序 | 低 | 近距离控制、穿戴设备 |
| 433MHz透传 | 几十到上百米 | 不支持 | 低 | 遥控开关、传感采集 |
| WiFi(本文方案) | 路由器覆盖范围 | 浏览器/调试助手 | 中 | 局域网控制、IoT入门 |
蓝牙的优势是手机自带,但你需要写一个APP或者小程序,这个工作量不比写单片机端小。433模块模拟开关很好用,但手机原生不支持,还得配一个网关。
利用WiFi的好处显而易见:手机不需要装额外的东西,一个浏览器、一个网络调试助手,就能直接和单片机通信。你家里的路由器本来就开着,WiFi模块连上路由器就有一个局域网IP,手机也在同一个网络里,直接把指令发过去就行。
这个项目选WiFi,本质上不是因为它技术多高级,而是它把"手机作为控制端"这件事的成本降到了最低。你不需要为手机端做专门开发,这是非常适合快速跑通Demo的路径。
1.3 模块选型:串口WiFi模块哪家强
确定用WiFi之后,市面上常见的方案有这么几类:
- 主控自带WiFi(比如WiFi SoC芯片的方案),不需要外部模块,但开发环境和STM32不是一套体系,要换IDE甚至换语言。
- 串口WiFi模块,和STM32通过串口连接,MCU端不用关心无线协议细节,只需要处理串口数据。
对入门项目来说,串口WiFi模块是更合理的选择。因为你可以把精力全部压在STM32端,无线链路对你来说只是"一个能把网络数据变成串口数据的外设"。
模块型号我就不做广告了,你搜索"串口WiFi模块"能找到大量选项,基本都兼容乐鑫的AT指令集。选择时重点看三个参数:是否支持透传模式、是否支持TCP Server模式、串口波特率是否可调。这三个参数直接决定你后面书写不书。透传模式让WiFi模块和单片机之间的串口通信变得透明,TCP Server模式让手机端可以主动连上来,波特率可调是为了配合单片机的时钟配置。
2. 硬件接线与供电细节:看着简单,最容易翻车的其实是这里
2.1 需要的物料清单
这个项目不需要什么高端器件,清单如下:
- STM32最小系统板或开发板一块(我用的系列带三个以上的定时器,方便同时输出PWM和控制灯)
- 串口WiFi模块一个,带天线那一类,F型/IPEX型天线都行
- USB转TTL模块一个(板载带串口转USB芯片的开发板可以不用)
- LED灯珠或LED灯板一个,串一个330欧左右的限流电阻
- 舵机一个,常用的SG90或类似9克舵机
- 杜邦线若干、面包板一块
- 手机(Android或iOS都行),安装一个"网络调试助手"类APP,或者直接用浏览器
2.2 接线表:照这个接基本不会错
接线是整个项目里最琐碎但也有细节讲究的一步。我直接给一张我跑通的接线表:
| STM32引脚 | 连接 | 说明 |
|---|---|---|
| PA9(USART1_TX) | WiFi模块RXD | 单片机发数据给WiFi模块 |
| PA10(USART1_RX) | WiFi模块TXD | WiFi模块发数据给单片机 |
| GND | WiFi模块GND | 共地,必须接 |
| 3.3V | WiFi模块VCC | 注意供电能力 |
| PB0 | 电阻+LED阳极 | 灯的控制引脚 |
| PA8(TIM1_CH1) | 舵机信号线 | PWM输出 |
| 外部5V | 舵机VCC | 舵机供电单独接 |
| 外部GND | 舵机GND | 舵机地和系统共地 |
这里很关键的一点,串口交叉接法。TX接RX,RX接TX,很多人第一次接反了,模块死活不回数据,其实是把收发接反了。
2.3 舵机供电是第一个"经验坑"
舵机这个东西,启动瞬间的电流能到几百毫安,反复转的时候电流波动很大。如果你的舵机直接从STM32开发板的3.3V或者板载5V取电,经常会出现这样的现象:正常跑一会儿,忽然单片机复位了,或者WiFi模块掉线了。
这不是代码问题,是供电问题。舵机电机会把整个系统的电压拖垮,低于单片机的复位阈值,系统就重启。
解决办法是单独给舵机准备一路电源。用USB转TTL模块的5V输出,或者一个外部5V电源适配器,直接给舵机供电,然后只把舵机的GND与STM32的GND连在一起,也就是共地但不共电源。信号线连到STM32的PWM输出引脚就可以。
这个"共地不共电源"的思路,在电机、舵机这类感性负载的控制项目里是通用的。单片机的地和负载的地必须连在一起,这样控制信号才有电压基准;但电源要分开,避免负载的电流波动倒灌进主控系统。
2.4 串口电平匹配:3.3V和5V的问题
另一个容易踩的坑是电平匹配。现在的串口WiFi模块基本都是3.3V逻辑电平,STM32系列主控的IO口一般也是3.3V,两者直接连没问题。但如果你用的是带板载USB转串口的老开发板,或者某些5V容错的引脚,严谨的做法是确认两侧逻辑电平一致。
如果模块是用3.3V供电的逻辑,就绝对不要接在5V的串口上,长期工作会烧掉模块的收发引脚。判断方法很简单,看模块的VCC是不是3.3V,如果是,串口逻辑就是3.3V,不要去接5V的TTL电平设备。
3. WiFi模块与STM32之间的数据通路:AT指令、透传和DMA接收
3.1 AT指令配置的完整流程
WiFi模块出厂一般默认是AT指令固件。第一次使用,先不要接单片机,直接用USB转TTL模块把它插到电脑上,用串口助手和它对话。
AT指令配置这件事,其实就是"把模块设置成你想要的网络角色"。整个流程我总结成五步:
- 发
AT,如果回OK,说明串口通信正常。 - 发
AT+CWMODE=1,设置成Station模式,让模块去连接家里的路由器。 - 发
AT+CWJAP="路由器SSID","路由器密码",让它上网。 - 发
AT+CIPMUX=1,开启多连接,这样手机可以连上来。 - 发
AT+CIPSERVER=1,8080,开启TCP Server,端口选8080或任意不冲突的端口。
配置完后,模块会把自己变成一个类似"小服务器"的角色,在路由器局域网里监听8080端口的连接请求,手机通过网络调试工具就能连上它。
如果不想让它连路由器,也可以让模块自己开热点,把AT+CWMODE改成2,模块自带AP热点,手机连上模块的WiFi就能通信。这种方式在野外、现场没有任何路由器的场景非常实用,实际测试下来,直接SPP/Socket连接会比走路由器省一道转发,延迟更低。
3.2 透传模式:让你不用关心TCP数据包格式
如果你用的是带透传功能的AT固件,配置完TCP的工作后,还需要用AT+CIPMODE=1开启透传模式,然后输入AT+CIPSEND进入发送状态。进入之后,模块和单片机之间的串口链路就"透明"了:手机发来任何数据,模块会直接原样扔到TXD引脚上;单片机发到RXD引脚的串口数据,模块也会原样转发到手机上。
透传模式的价值在于,单片机的串口代码完全不用感知"我是经过WiFi还是经过一根线通信的",你只需要处理串口数据的收发逻辑就行。这是给MCU端减负的最省心方案。
但透传模式有个副作用,单片机再也无法直接给模块发AT指令了,因为串口数据全被当成业务数据转发出去了。所以如果需要改模块配置,要么重新上电再进AT模式,要么先用非透传模式把配置写好,再切到透传。我一般直接先配好,再烧录最终固件,省得反复折腾。
3.3 DMA接收不定长数据的核心代码
ST官方标准库和HAL库都能实现串口接收,但标准的"接收一个字符进一次中断"方式在高频数据下容易丢。我在这个项目里用的是空闲中断(IDLE)结合DMA接收的方案,一次性收完一整帧,主循环再去解析。
这种方案的原理很简单:DMA一直在后台把串口收到的数据搬到内存缓冲区里,当一根线空闲下来(也就是一帧收完了),串口会产生一个空闲中断。在空闲中断里,关掉DMA,算一下DMA搬了多少字节,置一个消息标志,主循环看到标志就处理。
基于标准库的代码核心片段如下:
#define RX_BUF_LEN 256 uint8_t rx_buf[RX_BUF_LEN]; // DMA接收缓冲区 uint8_t rx_len = 0; // 本次接收到的一帧长度 volatile uint8_t rx_flag = 0; // 收到一帧的标志 void USART1_DMA_Init(void) { DMA_InitTypeDef DMA_InitStructure; USART_DMACmd(USART1, USART_DMAReq_RX, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)rx_buf; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = RX_BUF_LEN; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式 DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel5, &DMA_InitStructure); DMA_Cmd(DMA1_Channel5, ENABLE); } void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_IDLE) == SET) { USART_ReceiveData(USART1); // 读DR清IDLE标志 DMA_Cmd(DMA1_Channel5, DISABLE); // 暂停DMA rx_len = RX_BUF_LEN - DMA_GetCurrDataCounter(DMA1_Channel5); rx_flag = 1; // 通知主循环有数据 DMA_SetCurrDataCounter(DMA1_Channel5, RX_BUF_LEN); DMA_Cmd(DMA1_Channel5, ENABLE); } }这样处理之后,主循环里面只管判断rx_flag,取rx_len个字节解析。不要在主循环里用HAL的HAL_UART_Receive,那个是阻塞的,会卡死其他逻辑。
3.4 通信协议怎么定:简单文本协议最省心
WiFi模块把数据变成串口数据之后,你必须定义"一段数据怎么看懂一条指令"。我见过不少同学直接在中断里比较"\r\n",或者直接拿收到的原始数据去匹配指令,那在数据长了之后非常容易出bug。
我的建议是定一套简单的文本协议,格式如下:
LED:ON LED:OFF LED:BRIGHT:120 SERVO:90 SERVO:0 SERVO:180每条指令以换行符结尾。STM32侧在DMA中断里收集数据,然后在主循环里按冒号拆字段。第一个字段是设备名,第二个字段是动作,LED那边还有第三个字段表示亮度值。
选文本协议而不是二进制协议的原因,因为是手机端太好测试了。你用网络调试助手,直接手敲一个SERVO:90回车,就能看到舵机动,排查问题非常直观。如果用二进制协议,你发的每一帧字节都要用十六进制输入,调试体验会很痛苦。
4. 控制逻辑核心:灯光亮度与舵机角度的实现
4.1 灯的控制:从简单的开闭到呼吸灯效果
灯的开关是最基本的GPIO控制。PB0配置成推挽输出,收到LED:ON就拉高,收到LED:OFF就拉低,这没什么特别的。
真正有意思的是让灯支持亮度调节,这就需要PWM了。STM32的定时器输出PWM,可以通过改变比较寄存器CCR的值,也就是改变占空比,来控制LED亮度。
关键代码是配置一个定时器输出PWM,然后写一个简单的映射函数,把0到255的亮度值映射到CCR上:
// 假设定时器ARR设置为999,那么PWM频率 = 72MHz / (PSC + 1) / (ARR + 1) // 配置PSC为71,ARR为999,PWM频率 = 1MHz / 1000 = 1kHz,适合LED控制 void led_brightness_set(uint8_t brightness) { uint16_t ccr; if (brightness > 100) brightness = 100; // 0~100 映射到 0~999 ccr = (uint16_t)(brightness * 10) - 1; if (brightness == 0) ccr = 0; TIM_SetCompare3(TIM3, ccr); }如果你想让灯有呼吸效果,很简单,在主循环的状态机里,每10毫秒把当前亮度加1或减1,生成正弦或三角波的渐变,就实现了呼吸灯。这个呼吸灯逻辑可以作为一个美妙的调试手段,因为只要灯能一笔一画地亮暗渐变,就说明定时器中断和主循环调度都在正常工作。
4.2 舵机的PWM控制:20ms周期和角度映射
舵机的控制原理不复杂。舵机内部有一个基准电位,输入PWM信号的高电平宽度决定了输出轴的目标角度。常见舵机的PWM周期是20ms,也就是50Hz,脉冲宽度范围从0.5ms到2.5ms,对应0度和180度。
在STM32上,我用的是TIM1的通道1输出PWM,配置时把ARR设置为19999,预分频设为71,这样计数器频率是1MHz,一个计数周期就是1us。那么CCR的值就直接等于脉冲宽度(us):
- 0度对应0.5ms,CCR = 500
- 90度对应1.5ms,CCR = 1500
- 180度对应2.5ms,CCR = 2500
角度和CCR的线性映射关系:
void servo_set_angle(uint8_t angle) { uint32_t ccr; if (angle > 180) angle = 180; // 0~180度映射到 500~2500 us ccr = 500 + (uint32_t)angle * 2000 / 180; TIM_SetCompare1(TIM1, ccr); }这里有一个对新手来说非常重要的提醒:不要每5毫秒就刷新一次舵机PWM的CCR值。对于标准舵机来说,它会根据你刷新的频率来决定自己的运动速度。如果你在循环里快速改变目标角度,舵机会以全速奔过去,或者出现抖动。正确的做法是,收到一个角度指令,设置一次CCR,然后让舵机自己转到目标位置,它内部有反馈电位计会自己去闭环。
4.3 主循环与状态机:让系统不乱
这个项目虽然简单,但主循环还是应该用一个简单的状态机来组织,别把逻辑全堆在中断里,也别让主循环做成一个大死循环轮询。
我推荐的主循环结构是这样的:
while (1) { // 1. 处理串口指令 if (rx_flag) { protocol_parse(rx_buf, rx_len); rx_flag = 0; rx_len = 0; } // 2. 呼吸灯渐变定时处理 breathe_timer_tick(); // 3. 超时保护:长时间没收到指令,让舵机回中/灯保持最后状态 watchdog_timer_tick(); // 4. 状态回传:定时通过串口发回当前状态(可选) status_report_tick(); }状态机的本质是让你的"当前状态"和"输入事件"解耦。每个循环周期,系统先看有没有新指令,有就处理;没就继续执行周期性任务。这样即使手机连发多条指令,也不会互相干扰。
4.4 指令解析与错误处理
解析协议的时候,有个细节值得写一下。strtok这类函数在北京标准库的多线程环境下会保持全局状态,但在单线程单片机上用没问题。不过我在实际代码里更倾向自己写一个简单的拆分循环,避免隐式依赖:
void protocol_parse(uint8_t *buf, uint16_t len) { // 忽略空数据 if (len < 2) return; // 把缓冲区变成字符串 buf[len] = '\0'; if (strncmp((char*)buf, "LED:", 4) == 0) { // LED:ON / LED:OFF / LED:BRIGHT:value if (strstr((char*)buf, "ON")) led_set_state(1); else if (strstr((char*)buf, "OFF")) led_set_state(0); else if (strstr((char*)buf, "BRIGHT:")) { int val = atoi((char*)buf + 10); led_brightness_set(val); } } else if (strncmp((char*)buf, "SERVO:", 6) == 0) { int angle = atoi((char*)buf + 6); servo_set_angle(angle); } else { // 未知指令,忽略或回传错误标志 } }这个解析没有用复杂的JSON库,就用了字符串匹配和atoi。原因很简单,这个项目的指令集只有几条,完全不需要引入JSON解析库增加代码量。指令格式固定,用字符串函数就能可靠地解析。
5. 手机端控制:从网络调试助手到网页化进阶
5.1 第一步跑通:网络调试助手
手机端的"遥控器"有两种实现路径。先讲最容易跑通的。
在应用商店搜索"网络调试助手"或"TCP调试助手",这一类工具就是用来和TCP Server通信的。打开之后,选择TCP Client模式,输入WiFi模块在路由器上获取到的IP地址,端口填8080,点击连接。连上之后,底下的输入框直接输入LED:ON,点发送,灯就应该亮了。
这一步是整条链路的"冒烟测试"。如果灯亮了,说明无线链路、串口链路、协议解析、控制逻辑全部正常。我强烈建议先把这一步跑通,再去做花哨的手机界面,否则你会同时调试好几层问题,非常崩溃。
5.2 手机端IP地址查看与固定
每次模块上电,都会从路由器动态获取IP。如果你连路由器模式,手机上要填的IP可能会变。
一个省心的做法是,在路由器后台给WiFi模块绑定一个固定的IP地址,大部分家用路由器都支持在DHCP列表里找到模块的MAC地址,然后把它绑定到指定IP。这样手机端只需要填一次IP。
如果你用的是模块自带AP热点的方案,模块的IP固定是192.168.4.1,手机连上这个热点后,直接填这个IP就行,完全不需要路由器。
5.3 进阶:做一个本地HTML网页当遥控器
演示用网络调试助手没问题,但如果你想把这个项目拿去展示或者给别人玩,一个网页遥控器会更有说服力。
这里需要一个技巧:你的WiFi模块此时已经作为一个小的TCP服务存在了,手机浏览器不能直接和TCP服务器通信。浏览器只能访问HTTP服务。所以网页控制方案,需要额外做一个转发角色。
最省事的做法是,在STM32端不用TCP Server,而让WiFi模块工作在双模式,另一路用HTTP监听。但AT固件对普通用户来说配置这点比较绕,如果模块不支持,就用下面的方案:手机装一个支持WebSocket的调试工具或者直接写个简单的APP页面,把JavaScript和网络调试助手结合用,但这就又增加了工具依赖。
如果你只是想在局域网演示,还可以用一台电脑做中介,浏览器打开页面,电脑通过网络调试助手发指令。不过这种方式带出去演示略显繁琐。
真正的进阶方向是两个:一是换用支持Web Server固件的WiFi方案,把HTML页面直接存进模块的文件系统里;二是STM32侧挂一个简单的HTTP解析框架,直接在单片机上开一个小网页服务。这两个方向都有参考实现,等这个项目跑通后再去折腾,会顺很多。
5.4 局域网控制和公网控制的边界
标题里写的"远程",在很多人的理解里其实是"不在同一个路由器下也能控制"。这一点一定要提前说明白。
本文讲的所有通信,前提都是手机和WiFi模块在同一个局域网内。如果你想实现从办公室控制家里的灯,还需要一步路由器的端口映射,把外网的某个端口映射到WiFi模块的内网IP上,或者用云平台做中转。这一步涉及公网IP、端口转发、内网穿透等内容,复杂度会明显上升。
我建议初学阶段先吃透局域网控制,把链路通起来,再做公网也不迟。连局域网都做不通的,直接上公网只会被一波问题淹没。
6. 联调排错:几个高频故障的完整排查链路
6.1 故障一:串口助手发AT指令没反应
这是最最常见的第一个坑。你把WiFi模块插到USB转TTL上,打开串口助手,发送AT,什么都回不了。
排查顺序是:
- 看USB转TTL模块的TX/RX是否和WiFi模块反了。TX接RX,RX接TX。用万用表测一下模块TXD引脚对GND的电压,只接通电源的话应该是3.3V附近,接反线的情况电压表现不同。
- 确认串口助手的波特率和模块默认一致。大多数串口WiFi模块出厂默认是115200,但有些模块是9600或者38400,看模块丝印或者说明书,别想当然。
- 确认是否勾选了发送新行。AT指令要求以回车换行结尾,串口助手发送区右下角一定要勾选"发送新行",否则模块不认为指令结束。
按这个顺序走下来,大部分"没反应"都能解决。我见过有一半的人的"没反应"其实是忘了勾发送新行。
6.2 故障二:数据收不全或者乱码
串口收到的数据出现乱码,最常见的原因是波特率不一致。WiFi模块的工作波特率和STM32串口初始化时设置的波特率必须一模一样。AT指令配置里改了模块的波特率之后,如果单片机侧还是旧的波特率,就会直接乱码。
数据收不全还有一个隐藏原因:串口中断优先级太低,在处理其他中断的时候,串口数据被覆盖。尤其是当你在舵机控制的中断里耗时过久,串口的接收缓冲溢出。解决方法是给串口中断一个相对高的抢占优先级,以及把串口接收放到DMA里去,DMA搬运不需要CPU介入,大量降低延迟。
6.3 故障三:舵机抖动或者不动
舵机不动的时候,先看信号线接对没有,再看PWM频率。用逻辑分析仪或者示波器看波形最直观,如果没有仪器,就先把PWM周期固定为20ms,然后用官方库的示例程序验证舵机能否工作。
舵机抖动的情况则几乎可以断定是供电不足。你在舵机上电转动的瞬间,用万用表测舵机电源引脚和GND之间的电压,如果电压从5V掉到4V以下,基本就是电源问题。加一个220uF以上的电解电容在舵机电源附近储能,或者换成独立的供电适配器,抖动立刻消失。
6.4 故障四:手机连接不上模块的TCP服务
手机用网络调试助手连接模块的问题,排查链路是这样:
- 手机上先确认和模块是否在同一个局域网里(看手机的IP和模块IP的前三段)。
- 用电脑上的TCP调试工具,在同一网络里试着连一下模块IP和端口,连不上的话是模块配置问题,连得上的话是手机的问题。
- 检查模块的
AT+CIFSR返回的IP地址,确认它就是当前的路由器分配地址。 - 确认模块开启了多连接模式
AT+CIPMUX=1,默认状态是单连接,Server模式起不来。
这一套走完之后,绝大多数连接问题都能定位到某一个环节。
6.5 加一个系统级看门狗和状态回传
最后提一个提升稳定度的小技巧。WiFi模块偶尔会死机,或者网络连接断开后模块处于半死状态,这时候手机发什么都不动。我的做法是在STM32主循环里加一个软件看门狗,如果超过10秒没有收到任何有效指令,就通过GPIO控制WiFi模块的复位引脚,给它重新上电,然后重新执行AT配置流程。
这样等于给整个无线环节加了一层自愈能力。对于无人值守的物联网设备来说,这个设计是很有必要的。加入之后,系统的稳定性会有质的提升,即使WiFi模块偶尔掉线,系统也能自动恢复,不用你去现场断电重启。
7. 写在最后的一些体会
这个项目看着简单,但把链路真正跑通之后,你对嵌入式物联网的整个数据通路会有一个非常直观的理解。手机端发一条指令,经过空中、模块、串口、DMA、协议栈,最后变成引脚上的电平变化,这个过程里的每一层都有明确的边界和职责。
我在带新同学做这个项目时,最大的体会是:先跑通链路,再优化细节。别一开始就想着搞完美的APP界面、搞公网穿透、搞平台接入,先用手敲命令把灯点亮,把舵机转起来。这个"从0到1"的过程,是理解所有上层复杂技术的基础。
后续可以扩展的方向有两个,一是把控制端从网络调试助手换成带界面的小程序,二是加入传感器反向采集,让STM32不仅接收指令,还能上报温度、湿度、光照数据。思路都是通的,你在这个项目里建立的串口链路和协议设计能力,可以直接迁移过去。