1. 项目概述:排队叫号系统的嵌入式实现
在各类服务场所中,排队叫号系统已经成为提升服务效率、优化用户体验的重要工具。传统的有线排队系统存在布线复杂、扩展性差等问题,而基于WiFi的无线解决方案则能够很好地解决这些痛点。本文将详细介绍一种基于STM32微控制器和ESP8266 WiFi模块的无线排队叫号系统实现方案。
这个系统采用主从架构设计,主机作为中央服务器负责管理排队队列,多个从机作为终端设备实现取号和叫号功能。系统核心特点包括:
- 无线通信:通过ESP8266模块实现主从机之间的WiFi TCP/IP通信
- 队列管理:采用FIFO(先进先出)算法管理排队顺序
- 实时显示:通过OLED屏幕显示当前叫号、排队总数等信息
- 声光提醒:蜂鸣器和LED提供叫号提醒功能
- 可扩展性:支持多个从机终端同时工作
2. 硬件设计与选型
2.1 核心硬件组件
系统硬件主要由以下几部分组成:
-
主控制器:STM32F103C8T6最小系统板
- 72MHz主频的Cortex-M3内核
- 64KB Flash,20KB RAM
- 丰富的外设接口(USART、I2C、GPIO等)
-
WiFi通信模块:ESP8266-01S
- 支持802.11 b/g/n协议
- 内置TCP/IP协议栈
- 支持AT指令控制
-
显示模块:0.96寸I2C OLED
- 128×64分辨率
- 低功耗
- 可视角度大
-
输入输出设备:
- 轻触按键(取号、叫号功能)
- 有源蜂鸣器(叫号提醒)
- LED指示灯(状态显示)
2.2 硬件连接详解
2.2.1 STM32与ESP8266连接
ESP8266模块通过UART接口与STM32通信:
code复制STM32引脚 ESP8266引脚 说明
PA9 TX STM32 USART1_RX(接收ESP数据)
PA10 RX STM32 USART1_TX(发送数据至ESP)
3.3V VCC ESP供电(必须使用3.3V)
GND GND 共地
CH_PD 3.3V 使能引脚(接高电平)
RST 悬空 复位引脚(正常工作时悬空)
注意:ESP8266的供电电压必须为3.3V,直接连接5V会损坏模块。
2.2.2 STM32与OLED连接
OLED显示屏通过I2C接口连接:
code复制STM32引脚 OLED引脚 说明
PB6 SCL I2C1时钟线
PB7 SDA I2C1数据线
3.3V VCC OLED供电
GND GND 共地
建议在SCL和SDA线上各加一个4.7kΩ的上拉电阻,确保通信稳定。
2.2.3 按键与指示设备连接
code复制STM32引脚 外设引脚 说明
PB0 取号按键 上拉输入(按下接地)
PB1 叫号按键 上拉输入(仅从机/主机叫号终端)
PB2 清队列按键 上拉输入(仅主机)
PB3 蜂鸣器正极 推挽输出(低电平触发)
PB4 LED正极 推挽输出(高电平点亮)
GND 蜂鸣器/LED负极 共地
3. 软件架构设计
3.1 系统工作流程
系统工作流程可以分为以下几个主要阶段:
-
初始化阶段:
- 主机启动TCP服务器
- 从机连接主机WiFi热点
- 建立TCP连接
-
取号流程:
- 用户按下从机取号按键
- 从机发送取号请求给主机
- 主机生成新排队号并加入队列
- 主机返回新排队号给从机
- 从机显示分配的排队号
-
叫号流程:
- 服务人员按下叫号按键
- 从机发送叫号请求给主机
- 主机从队列中取出下一个号码
- 主机广播当前叫号给所有从机
- 对应号码的从机触发声光提醒
3.2 通信协议设计
系统使用自定义的简单协议进行数据交互,协议格式如下:
从机→主机指令:
- 取号请求:
0x01 - 叫号请求:
0x02,XXX(XXX为叫号)
主机→从机指令:
- 队列状态:
0x03,XXX,YYY(XXX=当前叫号,YYY=排队总数) - 新取号:
0x04,XXX(XXX=分配的排队号) - 错误指令:
0xFF
协议设计考虑因素:
- 简洁性:减少数据传输量
- 可扩展性:预留指令空间
- 可靠性:包含校验机制(实际项目中可添加CRC校验)
4. 关键代码实现
4.1 队列管理实现
队列是系统的核心数据结构,采用循环队列实现:
c复制#define QUEUE_MAX_LEN 50 // 最大排队数
typedef struct {
uint16_t queue[QUEUE_MAX_LEN]; // 排队号数组
uint16_t front; // 队首指针
uint16_t rear; // 队尾指针
uint16_t current_num; // 当前叫号
uint16_t total_num; // 排队总数
uint16_t next_num; // 下一个分配的号
} Queue_TypeDef;
// 初始化队列
void Queue_Init(Queue_TypeDef *queue) {
memset(queue->queue, 0, sizeof(queue->queue));
queue->front = 0;
queue->rear = 0;
queue->current_num = 0;
queue->total_num = 0;
queue->next_num = START_NUM; // 起始排队号
}
// 入队操作
uint8_t Queue_Push(Queue_TypeDef *queue, uint16_t num) {
if(((queue->rear + 1) % QUEUE_MAX_LEN) == queue->front) {
return 1; // 队列满
}
queue->queue[queue->rear] = num;
queue->rear = (queue->rear + 1) % QUEUE_MAX_LEN;
queue->total_num = (queue->rear - queue->front + QUEUE_MAX_LEN) % QUEUE_MAX_LEN;
return 0;
}
// 出队操作
uint8_t Queue_Pop(Queue_TypeDef *queue, uint16_t *num) {
if(queue->front == queue->rear) {
return 1; // 队列空
}
*num = queue->queue[queue->front];
queue->front = (queue->front + 1) % QUEUE_MAX_LEN;
queue->current_num = *num;
queue->total_num = (queue->rear - queue->front + QUEUE_MAX_LEN) % QUEUE_MAX_LEN;
return 0;
}
4.2 ESP8266通信实现
ESP8266通过AT指令进行配置和控制:
c复制// 发送AT指令并等待响应
void ESP8266_Send_AT_CMD(uint8_t *cmd, uint8_t *ack, uint16_t timeout) {
memset(g_uart_rx_buf, 0, 256);
g_uart_rx_len = 0;
HAL_UART_Transmit(&huart1, cmd, strlen((char*)cmd), 1000);
HAL_Delay(timeout);
if(strstr((char*)g_uart_rx_buf, (char*)ack) == NULL) {
g_esp_state = ESP_DISCONNECTED;
} else {
g_esp_state = ESP_CONNECTED;
}
}
// 主机TCP服务器初始化
void ESP8266_Host_Init(void) {
HAL_Delay(1000);
// 1. 重启ESP8266
ESP8266_Send_AT_CMD((uint8_t*)"AT+RST\r\n", (uint8_t*)"OK", 2000);
HAL_Delay(2000);
// 2. 设置AP模式
ESP8266_Send_AT_CMD((uint8_t*)"AT+CWMODE=2\r\n", (uint8_t*)"OK", 1000);
// 3. 配置AP参数
uint8_t ap_cmd[64];
sprintf((char*)ap_cmd, "AT+CWSAP=\"Queue_Host\",\"12345678\",11,0\r\n");
ESP8266_Send_AT_CMD(ap_cmd, (uint8_t*)"OK", 2000);
// 4. 启用多连接
ESP8266_Send_AT_CMD((uint8_t*)"AT+CIPMUX=1\r\n", (uint8_t*)"OK", 1000);
// 5. 建立TCP服务器
uint8_t tcp_cmd[32];
sprintf((char*)tcp_cmd, "AT+CIPSERVER=1,%s\r\n", HOST_PORT);
ESP8266_Send_AT_CMD(tcp_cmd, (uint8_t*)"OK", 2000);
g_esp_state = ESP_CONNECTED;
}
4.3 主机主循环逻辑
c复制while (1) {
// 1. 检查串口接收数据(从机指令)
if(g_uart_rx_len > 0) {
Comm_DataTypeDef recv_data, send_data;
ESP8266_Parse_Data(g_uart_rx_buf, g_uart_rx_len, &recv_data);
Host_Process_Cmd(&recv_data, &send_data);
Comm_Send_Data(&send_data);
Host_Broadcast_State();
memset(g_uart_rx_buf, 0, 256);
g_uart_rx_len = 0;
}
// 2. 检查清队列按键
if(Key_Scan(KEY_CLEAR_PORT, KEY_CLEAR_PIN) == 1) {
Queue_Clear(&g_queue);
OLED_ShowString(0, 7, (uint8_t*)"Queue Cleared");
HAL_Delay(2000);
OLED_Clear();
}
// 3. 更新OLED显示
OLED_ShowNum(64, 2, g_queue.current_num, 4);
OLED_ShowNum(64, 4, g_queue.total_num, 2);
OLED_ShowNum(64, 6, g_queue.next_num, 4);
// 4. 检查ESP8266连接状态
if(g_esp_state == ESP_DISCONNECTED) {
ESP8266_Host_Init();
LED_Flash(2);
}
HAL_Delay(100);
}
5. 系统调试与优化
5.1 分步调试流程
-
硬件连接检查:
- 确认所有电源连接正确
- 检查各模块供电电压
- 验证通信线序是否正确
-
ESP8266通信测试:
- 使用串口调试助手直接与ESP8266通信
- 测试AT指令响应
- 验证WiFi连接和TCP服务器建立
-
功能模块测试:
- 单独测试OLED显示功能
- 测试按键响应和消抖效果
- 验证蜂鸣器和LED控制
-
系统集成测试:
- 主机从机通信测试
- 队列管理功能验证
- 多从机并发测试
5.2 常见问题及解决方案
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| ESP8266不响应 | 供电不足/接线错误 | 检查3.3V供电,确认接线正确 |
| 从机无法连接主机 | WiFi配置错误 | 核对SSID和密码,确认主机IP |
| 通信数据错误 | 波特率不匹配 | 确保主从机UART波特率一致(115200) |
| OLED不显示 | I2C地址错误 | 确认OLED的I2C地址(通常0x78或0x7A) |
| 队列管理异常 | 队列指针越界 | 检查队列操作逻辑,确保边界条件处理正确 |
5.3 性能优化建议
-
通信可靠性优化:
- 增加数据校验(如CRC校验)
- 实现重传机制
- 添加心跳包检测连接状态
-
队列管理增强:
- 支持优先级队列
- 实现队列持久化(掉电保存)
- 增加队列统计分析功能
-
用户体验改进:
- 添加语音叫号功能
- 支持多语言显示
- 实现远程管理接口
6. 应用场景与扩展
6.1 典型应用场景
- 银行/政务大厅:替代传统排队机,减少客户等待时间
- 医院分诊系统:实现患者有序就诊,提高医疗效率
- 餐饮行业:优化就餐排队流程,提升顾客体验
- 教育机构:用于咨询、报名等需要排队的场景
6.2 系统扩展方向
-
云端对接:
- 将排队数据上传至云端
- 实现微信小程序取号
- 支持远程监控和管理
-
多模式显示:
- 增加大屏幕显示终端
- 支持多种显示布局
- 实现多媒体叫号
-
智能调度:
- 基于历史数据的智能排队
- 动态调整服务窗口
- 预测等待时间
-
数据分析:
- 排队数据统计与分析
- 服务效率评估
- 高峰时段预测
7. 开发经验分享
在实际开发过程中,我们总结了以下几点重要经验:
-
电源管理:
- ESP8266在工作时峰值电流可达200mA,需要确保电源能够提供足够的电流
- 建议使用独立的LDO为ESP8266供电
- 添加适当的去耦电容(推荐100μF+0.1μF组合)
-
通信稳定性:
- WiFi信号强度对系统稳定性影响很大
- 在复杂环境中建议进行信号测试
- 可以考虑使用外置天线增强信号
-
防静电措施:
- 所有IO口建议添加保护电路
- 按键输入线添加TVS二极管
- 确保良好的接地
-
代码优化技巧:
- 使用DMA传输减少CPU负载
- 合理规划中断优先级
- 关键代码段使用寄存器级操作
-
调试建议:
- 使用逻辑分析仪抓取通信波形
- 添加详细的调试日志
- 分模块逐步验证功能
这套排队叫号系统方案经过实际验证,在多个场所稳定运行,证明了其可靠性和实用性。开发者可以根据具体需求进行功能扩展和性能优化,打造更适合自己应用场景的排队系统。