十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32+ESP8266主从机排队叫号系统:架构设计、无线通信与工程实践

STM32+ESP8266主从机排队叫号系统:架构设计、无线通信与工程实践 简介这套基于STM32ESP8266的主从机排队叫号系统是面向嵌入式方向毕业设计与课程设计的完整项目资料适用于银行、政务大厅等需要取号叫号的公共场所。系统采用客户主机、柜员从机的架构主机以ESP8266 AP模式搭建TCP服务器管理排队数据从机通过STA模式接入实现多柜员协同叫号结合OLED显示、按键、蜂鸣器、打印机与TTS语音模块完成业务闭环。压缩包共681个文件约16.33MB涵盖STM32工程源码.c/.h、Keil/IAR工程配置文件.uvproj/.ewp、编译生成的hex固件以及o、d、crf等构建中间文件并含部分说明文档便于对照工程结构和编译流程学习。已有53人学习下载。资料保留了完整的主从机通信协议与逻辑处理代码可帮助读者快速理解TCP组网、无线数据传输、外设驱动与排队调度实现为毕业设计文档撰写、功能调试和二次功能扩展提供扎实参考。1. 排队叫号系统为什么要用STM32ESP8266做主从机医院分诊、政务大厅、银行柜台、奶茶店取餐这些场景的共同痛点是一个队排在主机前多个窗口各自叫号叫号的人和排队的人互相看不到。纯粹用单片机做单机版按键、屏幕、蜂鸣器全堆在一个板子上窗口一多布线就乱号码也不能跨窗口流转。把系统拆成“主机管队列、从机管窗口”的主从机架构主机负责取号和派号每个窗口放一个从机按“呼叫/重呼/过号”两侧通过ESP8266走WiFi通信是这类设计里性价比最高、也最容易在毕设或工程原型里跑通的做法。这篇文章要讲的就是从零把这个系统拆开主从机职责怎么分、STM32和ESP8266之间用什么协议通信、主机端队列和叫号规则怎么写、以及真正调试时最容易踩的波特率、丢包、透传退出这几个坑。全程围绕STM32F103C8T6和ESP8266-01S展开代码基于标准库和HAL库都能复用新手能照着接线烧录做过几年的工程师也能在协议帧和掉电恢复部分找到可抄的细节。2. 主从机架构怎么定单机方案的短板与ESP8266组网选型2.1 为什么是“主机从机”而不是一块板子干到底排队叫号系统如果只用一块单片机所有按键、所有窗口屏、取号机全接到同一块板子上那就是一台“集中式终端”。查询机上取一个号窗口屏亮一下这在窗口少于两个、距离小于3米时没问题。一旦窗口拉开到十几米或者中间隔着墙体按键线、显示线、电源线全往主机引线缆成本和故障点立刻失控。常见做法是改成“主机从机”两层。主机只做三件事产生排队号码、维护待叫队列、把号码分发给指定窗口的从机。从机只做两件事采集窗口人员的按键操作、显示和播报被叫号码。两者之间只需要一条无线链路而ESP8266在这类系统里是几乎不会被绕过的最优选。它成本低一个模块几块钱支持Station和AP模式既能连家里路由器也能自己开热点组网且官方AT固件让STM32只需要用串口发几条指令就能收发数据不要求你在STM32上移植TCP/IP协议栈。RS485也是这个场景的常见备选优点是抗干扰强、无丢包、半双工两根线就能串起所有从机。但RS485需要主机单独拉线到每个窗口且从机多了要配地址。WiFi方案在20米以内的室内环境足够稳定部署时不需要考虑线缆路径毕设验收时也更容易演示“无线叫号”这个亮点。我的选择是房内固定窗口用RS485跨房间或需要灵活摆放时用ESP8266走UDP。下文按WiFi方案展开。2.2 数据流向与状态划分先把系统里流动的消息列清楚。假设有三个窗口从机地址分别叫1、2、3。整个系统只存在两类数据帧下行帧主机 → 从机指派号码例如“请A012号到2号窗口”。这类帧由主机在“叫号”动作发生时推送。上行帧从机 → 主机窗口人员按下“呼叫”键后通知主机“我空闲了给我派一个号”按下“完成”键后通知主机“我服务完了”。主机内部维护一个待叫队列队列里是“已经取号但还没被窗口叫到的号码”。当收到从机发来的空闲通知主机从队头弹出一个号码打包成下行帧发给该从机同时把这个号码标记为“正在服务”。从机收到后显示并播报直到工作人员按“完成”主机的该窗口状态回到空闲。这个语义简单到不需要数据库一张结构体数组就能跑。2.3 通信协议不用JSON用精简二进制帧ESP8266以透传方式工作本质是串口数据的搬运工所以STM32之间互相发的就是纯粹的字节流。这里不推荐用JSON原因有两个一是STM32端解析JSON需要额外引入cJSON库Flash小的芯片容易吃紧二是串口波特率通常只有9600或115200JSON里大量的花括号、引号、字段名会让有效载荷占比下降。自定义二进制帧是嵌入式里最常见、也最省资源的做法。每帧固定格式如下字节偏移长度含义01帧头 0xAA11帧头 0x5521命令字如 0x10空闲请求0x20派号下发31数据长度 n不含帧头和本字节4n数据区4n1校验和从第2字节到数据区末尾逐字节累加取低8位为什么帧头用 0xAA 0x55 两个字节因为0xAA是101010100x55是01010101单字节偶发噪声撞上这种特征的概率极低且这两个字节出现在数据区时也不会影响状态机解析——接收端只有在连续收到这两个字节后才进入“接收数据”状态。校验和选累加和而不是CRC16是为了让STC和STM32系列都能用几行C代码在中断里算完CRC16的查表开销在这种低速无线链路上没必要。3. STM32从机端按键采集、OLED显示与ESP8266串口桥接3.1 从机硬件最小集与引脚分配从机用STM32F103C8T6最小系统板加上三个按键、一块0.96寸OLED、一只无源蜂鸣器、一个ESP8266-01S。ESP8266-01S的VCC接3.3V但要注意它WiFi发射瞬间电流可能到300mA直接用STM32板载LDO容易造成电压跌落导致重启正确做法是给ESP8266单独供电或用AMS1117-3.3的独立稳压模块STM32与ESP8266之间只连TXD、RXD、GND。常用引脚分配如下外设STM32引脚说明按键1-呼叫PA0拉低有效配内部上拉按键2-重呼PA1重复播报当前号码按键3-完成PA2通知主机本窗口空闲OLED SCLPB8I2C1OLED SDAPB9I2C1蜂鸣器PB12有源蜂鸣器高电平驱动ESP8266 TXPA10接STM32的USART1_RXESP8266 RXPA9接STM32的USART1_TX注意ESP8266-01S的RX引脚正常工作电平是3.3VSTM32的PA9输出也是3.3V可以直接连。如果用的ESP8266开发板带USB转串口那就要从开发板的TXD/RXD引脚接不能接在USB转串口芯片的输出脚上否则电平会互掐。3.2 按键引入状态机防止边沿抖动和重复触发窗口人员按“呼叫”键的动作是瞬时的但机械按键从按下到稳定的过程中会产生10~20ms的抖动。在这里不能用简单delay消抖因为消抖期间CPU被占住串口数据无法及时接收ESP8266透传进来的数据就可能丢。惯用做法是开启一个1ms的定时器中断主循环里对所有按键做“连续读到50次稳定电平才确认状态变化”的判断。核心逻辑如下#define KEY_PRESSED_LEVEL 0 // 按键按下为低电平 #define KEY_SAMPLE_MS 1 // 每1ms采样一次 #define KEY_CONFIRM_COUNT 50 // 连续50ms稳定才算有效 typedef struct { GPIO_TypeDef *port; uint16_t pin; uint8_t last_level; uint8_t stable_count; uint8_t state; // 0空闲 1按下未处理 2释放未处理 } key_t; void key_scan(key_t *key) { uint8_t level HAL_GPIO_ReadPin(key-port, key-pin); if (level key-last_level) { key-stable_count; if (key-stable_count KEY_CONFIRM_COUNT) { if (key-state 0 level KEY_PRESSED_LEVEL) { key-state 1; // 产生一次按下事件 } } } else { key-last_level level; key-stable_count 0; } }这段代码的思路是不追求“检测到变化立刻触发”而是强制要求电平连续稳定50ms才认为真的按下。后面的逻辑只需要在每轮循环里检查每个按键的state是否为1处理完置回0即可。阈值50ms既能滤掉机械抖动又不会让操作者感觉按下没反应。3.3 ESP8266的AT指令配网与串口透传从机上电后STM32需要通过串口给ESP8266发AT指令完成三件事进入Station模式、连接指定WiFi、建立一次到主机IP的TCP连接。主机侧IP固定设为192.168.1.100如果主机是AP模式ESP8266连主机热点时直接把目标IP写成192.168.4.1。// 依次发送以下AT指令每条指令发送后等待返回OK再发下一条 ATCWMODE1\r\n // 1表示Station模式0表示AP模式 ATCWJAPqueue_ap,12345678\r\n // 连接主机开的热点 ATCIPMUX0\r\n // 0单连接模式 ATCIPSTARTTCP,192.168.4.1,8080\r\n // 连接主机的TCP服务端口 ATCIPMODE1\r\n // 进入透传模式 ATCIPSEND\r\n // 开始发送数据代码调用时要注意AT指令的每条响应末尾是\r\nOK\r\n不能只查OK两个字符就继续否则可能把上一条指令的残影当成下一条响应。透传模式下ESP8266会把从TCP收到的所有数据原样推到串口也会把串口收到的所有数据原样通过TCP发送。从机发送二进制帧时只需要用HAL_UART_Transmit(huart1, frame, len, 100)把帧头、命令、校验码全丢给ESP8266即可。我在实际项目里会把AT配网结果打印出来配合串口助手看返回。若ATCWJAP一直返回FAIL先检查WiFi密码是否包含大写字母或特殊字符再确认ESP8266的供电电容有没有加。ESP8266断电重连后TCP连接会失效所以STM32端至少要在启动后做个“连接状态查询”发ATCIPSTATUS\r\n返回不是STATUS:3就自动重连。4. 主机端调度核心FIFO队列、叫号规则与掉电恢复4.1 队列数据结构环形缓冲实现待叫池主机需要同时处理三类号码已取号待叫、正在服务中、过号未办理。最简单可靠的结构是环形队列。用一个数组、一个头指针、一个尾指针维护待叫号码取号时尾指针递增叫号时头指针递增。队列深度按业务量设置一般取32或64就够每个元素只存一个uint16_t号码总数4到128个时占用内存非常小。#define QUEUE_SIZE 32 typedef struct { uint16_t buf[QUEUE_SIZE]; uint8_t head; uint8_t tail; uint8_t count; } queue_t; void queue_push(queue_t *q, uint16_t num) { if (q-count QUEUE_SIZE) { return; // 队列满无法再取号 } q-buf[q-tail] num; q-tail (q-tail 1) % QUEUE_SIZE; q-count; } uint16_t queue_pop(queue_t *q) { uint16_t num; if (q-count 0) { return 0xFFFF; // 空队列标记 } num q-buf[q-head]; q-head (q-head 1) % QUEUE_SIZE; q-count--; return num; }head和tail的循环取模让数组空间被重复利用避免了频繁搬移数据。代码里的0xFFFF作为空队列返回的哨兵值因为号码从0开始递增任何真实号码都不可能等于0xFFFF。实际调用queue_pop之后必须判断返回值不是哨兵否则会把0xFFFF当真实号码下发到窗口屏。4.2 叫号规则先到先服务、过号回捞、窗口绑定轮询排队叫号系统的业务规则可以分解成三个动作取号、叫号、完成。取号由查询机上按键触发主机收到后给号码自增并queue_push。叫号则由窗口按“呼叫”触发主机从队列弹出一个号码下发给该窗口。一个容易被忽略的场景是“过号回捞”窗口人员连续呼叫两次第一次号码到了窗口但客户没到第二次再按“呼叫”时系统应该从队头取新号而不是把上一个号再叫一遍。为此我习惯给每个从机维护两个状态区current_num当前正在服务的号码和current_state0空闲、1忙、2超时。窗口按“呼叫”时如果是空闲态则queue_pop新号码如果上一次的号码还没完成按“呼叫”就只重呼和播报不弹新号。这样把窗口的动作语义收敛成“空闲请求派号”和“忙时重复播报”两种代码分支少容错高。主机端对下行帧的组装如下void host_send_assign(uint8_t window_id, uint16_t num) { uint8_t frame[8]; frame[0] 0xAA; frame[1] 0x55; frame[2] 0x20; // 命令字派号下发 frame[3] 2; // 数据长度 2 字节 frame[4] window_id; // 窗口号1~3 frame[5] num 0xFF; // 号码低字节 frame[6] (num 8) 0xFF; // 号码高字节 frame[7] frame[2] frame[3] frame[4] frame[5] frame[6]; // 校验和 send_to_esp8266(frame, 8); }校验和为什么要把命令字和数据区都算进去不能只算数据区因为从机接收端如果只校验数据区命令字在传输中被改成一帧错值但校验和不变从机就会当成合法帧执行错误动作。把命令字纳入校验后任何字节损坏都有大概率被捕获TCP链路内虽然极少丢字节但初学调试时常有接线不良导致的串口错位多一层校验能少排查半天。4.3 掉电恢复把队尾号和窗口状态写进Flash系统运行中主机意外断电重启后如果队列全丢客户取过的号就凭空消失了这在真实场景不可接受。免外部Flash的常见做法是用STM32内部Flash的最后一个扇区存状态数据。F103系列整片Flash是1KB一页F103C8T6的最后一页通常从地址0x0800FC00开始在这页开头写入标志位和若干结构体字段。数据结构我一般是这么设计的typedef struct { uint32_t magic; // 固定值 0xA5A5A5A5用于判断是否有效 uint16_t last_num; // 已取到的最大号码 uint8_t window_state[3]; // 每个窗口的状态0空闲 1忙 uint8_t crc8; // 对上面字段的简单校验 } sys_save_t;保存时机有两个每次取号成功后就写一次每次窗口状态变化后就写一次。注意不要在主循环里每帧都写FlashSTM32内部Flash擦写寿命约1万次高频写入会快速损耗。这里保存的last_num是用于“号码续接”的掉电重启后新号码从last_num 1继续而不必恢复完整队列。待叫队列本身就带着“过号视为作废”的语义重启后窗口空闲即可继续叫新号用户的号码单上印的是几号就等几号原则上不会冲突。5. ESP8266无线通信的稳定性TCP保活、UDP广播与抗干扰参数5.1 主机服务端选TCP还是UDPESP8266既支持TCP Client也支持UDP。这套系统里主机是服务端从机是客户端。TCP有ACK和重传可靠性高但ESP8266的AT固件在TCP连接断开后不会自动重连主机程序要定期用ATCIPSTATUS查询。UDP则不用维护连接发就完了省了握手和保活逻辑代价是极端情况下丢包后号码显示不出来。我的取舍标准是窗口数小于等于4时用TCP大于4时改UDP加确认重发。为什么因为TCP连接数少的时候主机只需要为每个从机维护一个socket代码简单可靠窗口超过4个ESP8266在AP模式下带多客户端的能力开始吃紧TCP连接状态管理复杂化不如UDP广播一发所有从机同时收到然后每个从机单独回一个ACK帧主机对没收到ACK的从机补发一次。这个设计比“TCP连接保活”那一套省太多事而且广播天然支持“所有窗口屏同步更新”的场景。5.2 ESP8266供电与天线布局ESP8266的射频突发功耗很高尤其在发送数据瞬间电流尖峰可达300mA。如果供电回路上的退耦电容不够模块会反复复位表现就是串口偶尔收到乱码、AT指令不响应。惯用做法是在ESP8266的VCC与GND之间并两个电容一个100uF电解电容吸收低频跌落一个0.1uF陶瓷电容滤高频噪声。PCB设计时这两个电容要尽量贴近ESP8266的电源引脚不要隔着两三厘米走线再接否则电容寄生电感会抵消滤除效果。天线区正下方不要铺铜。ESP8266-01S是板载PCB天线如果主控板的天线正下方有大面积覆铜射频能量会被铜皮吸收通信距离直接砍掉一半以上。我自己测试过同一块板子天线下方铺铜和镂空相比极限距离从28米掉到11米。系统如果安装在金属柜内天线必须伸出来且附近不要有金属螺丝或屏蔽罩。5.3 通信参数推荐表参数推荐值备注串口波特率115200ESP8266默认固件常用波特率9600太慢影响透传吞吐TCP KeepAlive间隔30秒在AT固件里不支持直接调用ATPING代替UDP广播地址192.168.4.255AP模式子网掩码默认255.255.255.0主机监听端口8080避开21、23、80等容易被系统占用的端口从机重连周期5秒断线后用ATCIPSTART重连失败则延时5秒再试AT指令超时2秒超过2秒没返回就认为失败主动重发防止死等这些参数不是拍脑袋定的核心原则是“短超时 快速重试”。TCP连接断开后如果从机端不做重连窗口按“呼叫”就会没反应用户感知就是系统坏了。把5秒重连周期写进状态机后掉线最多恢复时间是5秒体验上可以接受。6. 从机收到号码后的处理OLED显示、蜂鸣提示与串口协议解析6.1 OLED显示更新逻辑从机屏幕要显示的信息只有一行当前被叫号码和窗口号例如“A012 → 3号窗口”。0.96寸OLED是I2C接口刷新一帧全屏数据需要约20到30ms。如果在主循环里每次都全屏刷新会占用大量CPU时间。这里应该做“局部刷新”只有号码或窗口号变化时才清屏重绘否则跳过。uint16_t last_display_num 0; uint8_t last_display_win 0; void display_number(uint16_t num, uint8_t win) { if (num last_display_num win last_display_win) { return; // 无变化不刷新 } OLED_Clear(); OLED_ShowNum(0, 0, num, 4); OLED_ShowString(0, 4, Window ); OLED_ShowNum(0, 6, win, 1); last_display_num num; last_display_win win; }代码里用两个静态变量记录上一次显示的值这样避免了无意义的重复绘制也减少了I2C总线上的无效通信。刷屏函数本身没做复杂排布只是为了说明“先比较、再刷新”这个优化点实际项目可以根据UI需求加字库和左移滚动。6.2 从机串口接收状态机从机的串口中断收到一帧数据后不能简单地把字节一个个存进数组完事帧头、长度、校验都要在这个过程里依次判定。常用状态机写法#define FRAME_STATE_HEAD1 0 #define FRAME_STATE_HEAD2 1 #define FRAME_STATE_CMD 2 #define FRAME_STATE_LEN 3 #define FRAME_STATE_DATA 4 #define FRAME_STATE_CHECK 5 uint8_t rx_state FRAME_STATE_HEAD1; uint8_t rx_buf[64]; uint8_t rx_len; uint8_t rx_index; void uart_rx_handler(uint8_t byte) { switch (rx_state) { case FRAME_STATE_HEAD1: if (byte 0xAA) rx_state FRAME_STATE_HEAD2; break; case FRAME_STATE_HEAD2: if (byte 0x55) { rx_state FRAME_STATE_CMD; } else { rx_state FRAME_STATE_HEAD1; // 帧头不匹配回到初始 } break; case FRAME_STATE_CMD: rx_buf[0] byte; rx_state FRAME_STATE_LEN; break; case FRAME_STATE_LEN: rx_len byte; rx_index 1; if (rx_len 0 || rx_len 32) { rx_state FRAME_STATE_HEAD1; // 长度不合理丢弃 } else { rx_state FRAME_STATE_DATA; } break; case FRAME_STATE_DATA: rx_buf[rx_index] byte; if (rx_index rx_len 1) { rx_state FRAME_STATE_CHECK; } break; case FRAME_STATE_CHECK: // byte 是校验和计算累加和比对 rx_state FRAME_STATE_HEAD1; break; default: rx_state FRAME_STATE_HEAD1; break; } }这个状态机的精妙在于数据区长度从一开始就是已知的所以不用等超时判断帧结束收到指定长度字节后自然进入校验状态。rx_len限制在32以内能挡住因为干扰导致的长帧覆盖缓冲区。进入FRAME_STATE_CHECK后还需要把收到的校验和与累加和比对一次比对通过才把命令字节和数据交给业务层失败则整个帧丢弃从机状态留在“等下一个帧头”。6.3 蜂鸣器提示音与按钮反馈蜂鸣器驱动不能直接在主循环里拉高电平后靠delay延时关闭否则系统进入阻塞串口数据又丢了。惯用做法是定义一个“蜂鸣器响N次每次持续M毫秒”的参数表用非阻塞状态机在主循环里轮询判断时间片。void beep_tick(void) { static uint32_t last_time 0; static uint8_t beep_count 0; static uint8_t beep_on 0; uint32_t now HAL_GetTick(); if (beep_count 0) { HAL_GPIO_WritePin(BEEP_PORT, BEEP_PIN, GPIO_PIN_RESET); return; } if (now - last_time 150) { last_time now; if (beep_on) { HAL_GPIO_WritePin(BEEP_PORT, BEEP_PIN, GPIO_PIN_RESET); beep_on 0; } else { HAL_GPIO_WritePin(BEEP_PORT, BEEP_PIN, GPIO_PIN_SET); beep_on 1; beep_count--; } } }调用beep_set(3)让蜂鸣器响3次每次150ms间隔。调用后beep_tick()放在主循环里即可不阻塞任何串口收发。给“呼叫”和“完成”设置不同的响法呼叫两声短音完成一声长音操作员不用看屏幕也能知道按键生效。7. 主机侧的取号机与多窗口并发处理技巧7.1 取号机按键与号码递增查询机上的取号按键一般是物理轻触开关按一下出一个小票号码。主机端要处理的号码格式分两类一类是纯数字一类是“字母数字”组合。字母代表业务类型数字才是真正递增的序号。用结构体存当前计数每次取号后last_num写入Flash备份。uint16_t take_ticket(uint8_t biz_type) { uint16_t num sys_param.last_num 1; if (num 9999) { num 1; // 超过4位归零重来 } sys_param.last_num num; save_sys_param(); queue_push(g_queue, num); return num; }号码超过9999后归零是个业务决策点。如果在真实医院场景每天取号量不会超过几千归零没问题。但如果号码用于留痕追溯建议改到65535再归零这样uint16_t能直接用不会溢出。7.2 多窗口同时按压“呼叫”时的互斥处理三个窗口几乎同时按下呼叫键三帧上行数据会在几百毫秒内先后到达主机串口。如果主机在HAL_UART_Receive_IT中断里直接把数据入队主循环随后逐条处理天然就是串行的不会有并发写队列的问题。但要注意一点queue_pop操作一旦发现队列为空不能给窗口派号必须单独发一帧“无号可叫”的指令让从机显示“请等待”。if (g_queue.count 0) { host_send_no_ticket(window_id); } else { uint16_t num queue_pop(g_queue); host_send_assign(window_id, num); }如果不用count成员判断queue_pop会返回0xFFFF再把这个值组装进帧下发窗口屏就显示65535号。必须把“空队列”当作独立分支处理这比依赖哨兵值更清晰。7.3 多帧上行同时到达时防止粘包主机收到的上行帧如果连续两帧紧紧挨着串口缓冲里可能是 [帧1][校验][帧2][帧2校验] 的连续字节流。接收状态机按帧头重新同步即使第一帧的数据区里出现0xAA 0x55也不会导致解析错乱。但有一种情况会出错第一帧的rx_len因为干扰变成0状态机直接把帧丢弃然后第二帧的帧头还留在缓冲里被下一轮HAL_UART_Receive_IT再次读出来就会出现“一帧拆成两半”的假象。解决办法是在FRAME_STATE_DATA里增加对rx_index越界的保护同时每次进入FRAME_STATE_CHECK后无论校验结果如何都把状态机强制拉回FRAME_STATE_HEAD1不保留任何中间态。这样即使丢帧也只丢一帧不会滚雪球导致后续所有帧全部解析失败。8. 整个系统的验证方法串口模拟、逻辑分析与断线测试8.1 用串口助手模拟主机帧从机端联调时不一定要把主机代码烧进去才能测。可以在PC上用USB转TTL连接从机的ESP8266串口用串口助手手工发送一帧AA 55 20 02 01 0C 00 2F按第2节的帧格式拆一下帧头是AA 55命令字0x20是派号下发数据长度02数据区是01 0C 00代表窗口号1、号码0x000C即12号最后一个字节0x2F是累加校验和。从机收到后OLED应该显示12号蜂鸣器响两声。如果没反应先查接收状态机里校验比对部分再查ESP8266是否处于透传模式。8.2 逻辑分析仪抓串口波形STM32串口配置成115200-8-N-1后如果想看ESP8266到底有没有把数据发出来用逻辑分析仪同时抓PA9和PA10两条线是最直接的。开机时能从PA10看到一堆AT指令和响应的波形如果有指令没被正确响应从波形的时间间隔就能看出来。不少新手遇到的“串口调试助手能收到数据但STM32收不到”问题本质是接线把TX和RX接反了。STM32的PA9是发送脚必须接到ESP8266的RXD也就是ESP8266的接收脚PA10接ESP8266的TXD。很多ESP8266模块上丝印标的是TXD/RXD站在模块自身角度接STM32时正好交叉。用逻辑分析仪一眼就能确认是否交叉接反。8.3 断线重连与看门狗的配合ESP8266在信号差的时候TCP连接可能悄然断开从机自己并不知道。应对办法是让主机每隔30秒发一帧“心跳查询”给所有从机从机收到后回一帧“心跳应答”。如果主机连续3个周期没收到某个从机的应答就在主机屏幕上标记该窗口离线如果从机30秒内没收到任何主机帧则主动执行一次ATCIPSTART重连。void slave_watchdog_tick(void) { static uint32_t last_rx_tick 0; uint32_t now HAL_GetTick(); if (now - last_rx_tick 30000) { esp8266_reconnect(); // 断线重连 last_rx_tick now; } }这段代码放在从机主循环里HAL_GetTick()的返回值是毫秒级。重连动作要放在主循环里执行不能在中断里做因为AT指令的收发和等待响应需要占用串口较长时间中断里做会阻塞其他中断处理。8.4 蜂鸣器与OLED的状态灯位系统联调时我会在OLED屏幕角落预留一个状态小点红色表示WiFi未连接绿色表示TCP已连接蓝色表示空闲黄色表示正在服务。这个小点用不同颜色区分调试时扫一眼屏幕就能判断链路状态不用每次都接串口打印。实现上就是在全局状态变量变化时重绘对应像素代码量很少但对现场排障的帮助非常大值得写进系统里。8.5 关于“项目资料ID26.zip”里常见文件的使用建议拿到这类压缩包通常里面会有原理图、源码、接线图、功能演示视频、参考论文或设计文档。我建议按这个顺序看先看原理图确认引脚分配再看源码里的UART初始化函数确认波特率和引脚最后对照文档里的“操作说明”跑一遍硬件。不要先打开论文从头读因为论文里的文字描述往往落后于代码实现而且很多文档是从模板复制而来引脚号可能与源码不一致。如果源码编译报错先检查Keil5里有没有装对应的STM32芯片包再查ST-Link驱动是否正常这俩是绝大多数编译烧录失败的根源。本文还有配套的精品资源点击获取
返回列表