资讯动态

单片机课程设计:排队叫号系统设计与实现解析

发布时间:2026/10/21 17:04:31 来源:尧图企业网站定制
简介这是一份围绕单片机课程设计课题「排队叫号系统设计」的完整报告PDF面向电子信息工程、自动化、计算机科学与技术等专业的学生与工程师可用于对照课程设计任务书完成从方案分析、电路设计到系统仿真的全流程参考。报告覆盖系统需求分析与整体设计、硬件设计、软件设计、Proteus仿真等模块包含取票、休息等待、按键叫号、前去办理四个核心功能并详细说明了单片机最小系统电路、液晶显示电路、按键与蜂鸣器电路以及元件参数选择依据。压缩包内共1个PDF文件大小2.83MB已吸引1302人学习。读者可从中获得任务书撰写、单元电路图绘制、主从机软件流程设计及系统集成仿真的完整样例便于快速建立单片机应用系统设计思路。1. 排队叫号系统设计课程设计的“压轴题”还是“劝退题”一提起“单片机课程设计排队叫号系统设计”做过的人第一反应往往不是“排队”而是“排队等调试”。银行网点、政务大厅、诊所分诊台都在用这类系统核心动作就三个取号、排队、叫号。看起来不复杂但真把 LCD 显示、按键输入、语音播报、窗口屏通信串在一起很多人的板子就卡在“数码管乱码”“按键一按跳两次号”这种小问题上。这门课程设计真正考验的不是单个外设而是怎么把“排队状态”这件抽象的事用单片机有限的内存和 IO 管明白。这篇文章适合三类人正在做课程设计、想拿一套能跑通的方案去改的学生带实验课、需要给学生划范围的导师以及想把排队叫号逻辑移植到自己的小项目、但又不想从零踩坑的开发者。我会按“硬件选型 → 队列与状态机 → 模块落地 → 翻车排查 → 进阶玩法”的顺序把整条路走一遍核心代码直接能抄参数和坑都写在明处。2. 硬件选型与系统架构51 内核为什么是课设的主流答案2.1 主控选型先看课程设计评分看什么课程设计的评分通常看三样原理图能不能讲清楚、代码是不是自己写的、演示的时候稳不稳定。这决定了主控选型不能只看性能还得看资料好不好找、下载器是不是人人都有。常见做法是用 STC89C52 这类 51 内核单片机原因很实际学校实验室的下载器、开发板、仿真器基本都是围绕 51 准备的Keil C51 的环境也熟出问题了随便找一个同学就能问。STM32 固然性能强、外设多但对课程设计来说属于“杀鸡用牛刀”而且库函数和 HAL 配置对初学者来说是个黑匣子万一配错一个时钟树整个演示就翻车了。我一般会建议如果题目里没有明确要求“必须用 STM32”就用 STC89C52 或者 STC15 系列如果要求带无线呼叫器、上位机统计这类偏应用的功能再考虑 STM32。选型判断标准就三条开发环境是否熟悉、例程是否够多、引脚是否够用。排队叫号系统需要的引脚不算多51 的 40 脚封装绰绰有余。还有一个容易被忽略的点51 是 5V 供电和大部分按键、数码管、LCD1602 直接兼容不用做电平转换这对课设焊接和调试都是省心的事。2.2 外设清单与引脚分配按键、显示、语音、通信怎么配以一套典型的“单取号机 双窗口屏”配置为例外设包括取号按键 1 个可扩展为“普通号/VIP 号”两个键、叫号按键每个窗口 1 个、LCD1602 显示屏 2 块取号机一块、窗口屏一块、语音播报模块 1 个、蜂鸣器 1 个。如果做无线版本再加一对 nRF24L01。整个系统的数据流是用户在取号机按下取号键主控生成排队号并显示同时通过有线串口或无线模块把“请 X 号到 X 窗口”发给窗口屏窗口屏显示并触发语音播报。窗口叫号时取号机上的队列更新过号数据也由主控统一维护。简单说取号端是大脑窗口端是显示和执行器。引脚分配的逻辑是先定“谁占并行总线、谁占串行口”。LCD1602 数据线占 P0 一组 8 根控制线 RS、RW、EN 占 P2.0~P2.2按键接 P1 口低四位用独立按键方式不用矩阵因为按键数量不超过 4 个语音模块如果用串口 TTS占 P3.0/RXD 和 P3.1/TXD这两根线同时也是 ISP 下载用的串口调试时要错开蜂鸣器占 P2.3nRF24L01 占 P1 口剩余引脚或单独一组 SPI 引脚。下面是我常用的分配表直接照着焊就行外设引脚说明LCD1602 数据 D0~D7P0.0~P0.7需接上拉电阻LCD1602 RS / RW / ENP2.0 / P2.1 / P2.2控制线取号按键 / 窗口1叫号 / 窗口2叫号P1.0 / P1.1 / P1.2独立按键低电平有效蜂鸣器P2.3叫号提示音语音 TTS 模块 TX / RXP3.1 / P3.0串口发送播报指令nRF24L01 CE / CSN / SCK / MOSI / MISOP1.4~P1.7无线方案时使用这块最容易忽略的是 P0 口必须接上拉电阻很多新手把 LCD 接上去发现屏幕不亮或者显示乱码原因就是 P0 是开漏输出不接上拉根本驱动不了。还有语音模块如果是 3.3V 供电的要注意和 51 的 5V 逻辑电平兼容问题通常模块自带电平转换就没问题否则要在 TX/RX 线上串电阻分压。3. 排队队列与叫号状态机系统的真正核心不是显示而是逻辑3.1 环形队列课设规模下最合适的数据结构排队叫号系统的本质是维护一个“先进先出”的队列。有人会想用链表但在 51 这种资源紧张的单片机上链表的内存开销和指针操作反而是负担。课设场景下排队人数一般不超过 20 人用数组实现的环形队列就够用了。环形队列的好处是头尾指针循环利用数组空间入队出队都是 O(1) 操作而且代码量小、容易讲清楚——答辩的时候老师问“你这个队列怎么实现的”你三句话就能说完。队列里存的是排队号比如 001~999。取号时队尾入队叫号时队头出队。需要注意边界条件队列满时不能继续取号队列空时不能叫号。下面这段是完整的队列模块我习惯把队列长度设为 20因为超过这个数说明系统已经拥堵到需要人工干预了。#define MAX_QUEUE 20 typedef struct { unsigned char num[MAX_QUEUE]; // 排队号只存低字节显示时格式化 unsigned char head; // 队头索引 unsigned char tail; // 队尾索引 unsigned char count; // 当前队列长度 } Queue_t; void queue_init(Queue_t *q) { q-head 0; q-tail 0; q-count 0; } // 入队成功返回1队列满返回0 unsigned char queue_enqueue(Queue_t *q, unsigned char no) { if (q-count MAX_QUEUE) return 0; q-num[q-tail] no; q-tail (q-tail 1) % MAX_QUEUE; // 尾指针循环 q-count; return 1; } // 出队成功返回队头元素队列空返回0 unsigned char queue_dequeue(Queue_t *q) { unsigned char no; if (q-count 0) return 0; no q-num[q-head]; q-head (q-head 1) % MAX_QUEUE; // 头指针循环 q-count--; return no; } // 查看队头但不取出用于显示“当前呼叫号” unsigned char queue_peek(Queue_t *q) { if (q-count 0) return 0; return q-num[q-head]; }代码里的环形逻辑用的是取模运算head 和 tail 到数组末尾会自动回到开头。逻辑说明入队时先判满再写数据出队时先判空再读数据count 这个字段是冗余的但用它判断空满最直观避免 head 和 tail 相等时无法区分“空”和“满”。参数说明MAX_QUEUE 可以根据实际场景改比如医院科室可以设 50但注意 51 的内存只有 128 字节内部 RAM数组太大要放到 xdata 区否则编译都过不了。这个坑我后面会细说。3.2 叫号状态机过号、重呼、完成怎么迁移光有队列还不够系统还需要一个状态机来管理“当前在干什么”。我见过很多人的代码是“按键一按就直接叫号”结果出现两个窗口同时叫同一个号、或者过号之后号还留在队列里。正确做法是定义几个状态空闲、等待、呼叫中、过号待处理。每个状态只允许特定事件触发迁移事件包括取号键按下、窗口叫号键按下、叫号超时、重呼键按下、确认完成。状态迁移规则是空闲状态下有人取号进入等待等待状态下窗口按叫号键从队头取号并进入呼叫中呼叫中如果 120 秒内没人确认标记为过号进入过号待处理过号后用户按重呼键重新进入呼叫中如果窗口按“完成”键当前号结束检查队列是否为空空了回空闲否则回等待。这个设计的好处是每个状态都清晰答辩时画个状态转移图就是加分项。下面是核心状态机代码。typedef enum { S_IDLE, // 空闲无排队 S_WAIT, // 有排队号等待叫号 S_CALLING, // 正在呼叫某号 S_OVERTIME // 当前号已过号等待处理 } State_t; typedef struct { State_t state; unsigned char current_no; // 当前正在叫的号 unsigned char window_id; // 当前叫号的窗口 unsigned int timer_cnt; // 超时计时 } CallSys_t; // 窗口叫号键触发的处理 void on_call_key(unsigned char win) { unsigned char no; if (sys.state ! S_WAIT sys.state ! S_IDLE) return; if (queue_peek(q) 0) return; // 队列空 no queue_peek(q); sys.current_no no; sys.window_id win; sys.timer_cnt 0; sys.state S_CALLING; lcd_show_call(no, win); voice_play(no, win); // 播报“请X号到X窗口” } // 定时器中断里每10ms调用一次处理超时 void on_timer_10ms(void) { if (sys.state S_CALLING) { if (sys.timer_cnt 12000) { // 120秒超时 sys.state S_OVERTIME; buzzer_beep(2); // 提示过号 } } } // 过号后按“重呼”键把当前号重新放回队头并再次呼叫 void on_recall_key(void) { if (sys.state S_OVERTIME) { sys.state S_CALLING; sys.timer_cnt 0; lcd_show_call(sys.current_no, sys.window_id); voice_play(sys.current_no, sys.window_id); } }参数说明超时时间 12000 个 10ms 就是 120 秒这个值可以按场景调整医院可以放宽到 180 秒快餐店可以压到 60 秒。逻辑说明过号后没有把号从队列里删除而是让它停留在“当前号”的位置等重呼或完成后再从队头出队。这个设计比直接把过号号踢出队列更人性化也是答辩时可以拿出来讲的一个细节。注意 on_call_key 里用了 queue_peek 而不是 queue_dequeue意思是叫号只是“预览”队头真正出队要等“完成”那一刻否则队列状态和实际状态对不上。3.3 取号与叫号联调一个完整的主循环框架把队列和状态机串起来主循环就很简单了扫描按键 → 按键产生事件 → 事件驱动状态机 → 状态机控制外设刷新。千万不要把业务逻辑写在按键扫描里否则按键抖动和逻辑耦合在一起调试起来非常痛苦。下面给出主循环的骨架。void main(void) { unsigned char key; sys_init(); // 初始化定时器、串口、LCD、队列 while (1) { key key_scan(); // 非阻塞按键扫描 if (key KEY_TAKE) // 取号键 on_take_key(); // 生成新号并入队 if (key KEY_CALL1) // 窗口1叫号 on_call_key(1); if (key KEY_CALL2) // 窗口2叫号 on_call_key(2); if (key KEY_RECALL) // 重呼键 on_recall_key(); lcd_refresh(); // 刷新取号机显示 } }这里的关键点是 key_scan 必须是非阻塞的不能在里面用长延时消抖否则主循环会被卡住按键响应就会变“肉”。也就是后面要重点讲的防抖问题。整个第 3 章解决的问题是排队号怎么存、叫号逻辑怎么走、事件怎么流转把这层想清楚硬件部分就是按图施工。4. 按键、显示、语音与通信的模块落地4.1 按键扫描与消抖把“一按出两号”的毛病扼杀在摇篮里机械按键按下和松开时触点会抖动几十毫秒如果不做处理一次按下会被单片机识别成好几次。很多人的排队叫号系统“翻车”就翻在这里取号键按一下取号机吐出来两个号。解决方式有两种一种是最简单的延时消抖检测到低电平后延时 15ms 再读一次确认还是低电平才算有效另一种是状态机消抖定期采样并把连续 N 次相同的电平视为稳定。课设场景下延时消抖足够但要注意延时不能放在主循环里阻塞业务我一般会用一个“按下标志 释放检测”的方式来替代。下面这段代码是改进后的按键扫描它比单纯的延时消抖多了一个“只有从释放变成按下才算一次”的边沿检测可以彻底解决重复触发。#define KEY_TAKE 0x01 // P1.0 #define KEY_CALL1 0x02 // P1.1 #define KEY_CALL2 0x04 // P1.2 #define KEY_RECALL 0x08 // P1.3 unsigned char key_scan(void) { static unsigned char key_prev 0xFF; // 上一次的完整按键状态 unsigned char key_cur, changed, ret 0; key_cur P1 0x0F; // 只读低四位 if (key_cur ! key_prev) { delay_ms(15); // 15ms消抖 key_cur P1 0x0F; // 消抖后再读一次 } changed key_cur ^ key_prev; // 异或变化的位为1 // 只处理“按下”沿之前是高电平现在是低电平 ret changed (~key_cur) 0x0F; key_prev key_cur; return ret; }逻辑说明key_prev 保存上一次的稳定状态本轮检测到变化后延时 15ms 再采样得到新的稳定状态。changed 是发生变化的位但“变化”既包含按下也包含释放所以用 (~key_cur) 过滤出“当前为低电平”的位也就是刚按下的键。参数说明延时消抖的 15ms 是基于常见机械按键的抖动时间约 5~10ms留的余量如果按键质量差可以把延时加到 20ms但不要超过 30ms否则会影响手感。这个函数还有一个好处如果用户一直按住不放它只会返回一次有效值因为第二次扫描时 key_cur 不再变化changed 为 0。4.2 LCD1602 与语音播报窗口屏的“显示 播报”组合窗口屏上要显示两行内容第一行是“请 XXX 号”第二行是“到 X 号窗口”。LCD1602 驱动是课设的标配这里不展开初始化时序直接给一个显示函数框架。需要注意的细节是LCD 显示是写一个字符就占一个地址数字转字符串时要自己处理位数对齐否则会出现“请 5 号”和“请 15 号”显示位置不一致的问题。// 在LCD指定位置显示格式化字符串 void lcd_show_call(unsigned char no, unsigned char win) { char buf[16]; sprintf(buf, Please %03d, no); // 排队号按3位显示不够补0 lcd_set_cursor(0, 0); lcd_write_str(buf); sprintf(buf, Window %d, win); lcd_set_cursor(1, 0); lcd_write_str(buf); }语音播报是另一个容易出问题的地方。如果用的是 ISD1820 这类录音播放芯片它只能播预先录好的固定语音适合播“请取号”“请到一号窗口”这种固定句子但没法动态播“请 125 号到 2 号窗口”因为号是不固定的。如果要动态播号得用支持 TTS 的模块比如 SYN6288。这类模块通过串口接收 GB2312 编码的文本然后合成语音输出。常见的做法是拼一个播报指令帧把“请”“号”“到”“窗口”这些固定文本和动态数字拼在一起发给模块。// 以SYN6288为例发送动态播报指令 void voice_play(unsigned char no, unsigned char win) { unsigned char cmd[8]; unsigned char text[32]; unsigned char len; // 拼接播报文本请X号到X窗口 text[0] 0xC7; // 请 text[1] 0xEB; text[2] 0x30 no / 100; // 百位数字字符 text[3] 0x30 (no / 10) % 10; text[4] 0x30 no % 10; text[5] 0xBA; // 号 text[6] 0xC5; text[7] 0xB5; // 到 text[8] 0xBE; text[9] 0x30 win; // 窗口号 text[10] 0xB4; // 窗 text[11] 0xB4; text[12] 0xBF; // 口 text[13] 0xDA; len 14; // 指令帧帧头 数据长度 命令字 文本 cmd[0] 0xFD; cmd[1] 0x00; cmd[2] len 3; cmd[3] 0x01; // 合成播放命令 cmd[4] 0x00; cmd[5] 0x00; uart_send_bytes(cmd, 6); uart_send_bytes(text, len); }这里的核心坑是字符编码。单片机里写中文字符串Keil 默认可能不是 GB2312 编码直接写请经常变成乱码发给 TTS 模块所以我在代码里直接写了 GB2312 的十六进制字节。注意不同厂商的 TTS 模块指令格式不一样用之前一定查数据手册确认命令字和帧结构不要照抄这一段就能通用。调试时先用串口助手把发送的字节打印出来再用 PC 上的串口助手直接发给语音模块两步都通过再接到单片机上能省一半的调试时间。4.3 取号机与窗口屏的通信有线串口还是 nRF24L01取号机和窗口屏之间要通信。最简单的方案是用一根串口线直连取号机通过 UART 把“显示内容”发给窗口屏窗口屏收到后就显示并播报。这个方案成本低、稳定但有个问题窗口屏离取号机远的话线不好拉而且两个窗口就要两根线。所以很多课设会选择 nRF24L01 无线模块做成无线窗口屏。这个模块 2.4GHz 频段、SPI 接口、最远空旷能到几十米课设演示已经够用。nRF24L01 的配置有几个“一票否决”级的参数收发双方的 RF_CH射频频道必须一致、收发地址必须一致、数据速率和 CRC 设置必须一致。任何一个不一致现象就是“收不到数据”或者“收到乱码”。以及这个模块是 3.3V 供电、IO 不能直接接 5V否则容易烧模块这又是课设常见的“血泪经验”。初始化配置我通常写成这样// nRF24L01 初始化要点收发双方必须一致 void rf24_init(void) { RF_CE 0; RF_CSN 1; // 使能自动应答、使能接收、8位CRC、上电 rf24_write_reg(CONFIG, 0x0E); // 设置频道40对应2.480GHz rf24_write_reg(RF_CH, 40); // 地址宽度5字节 rf24_write_reg(SETUP_AW, 0x03); // 数据速率1Mbps、发射功率0dBm rf24_write_reg(RF_SETUP, 0x06); // 使能数据通道0自动应答 rf24_write_reg(EN_AA, 0x01); // 写接收地址和发送地址同一组地址 rf24_write_reg(RX_ADDR_P0, TX_ADDR, 5); rf24_write_reg(TX_ADDR, TX_ADDR, 5); }参数的逻辑说明RF_CH 决定工作在哪个频点两边差一个数字都收不到RX_ADDR_P0 和 TX_ADDR 要一样因为点对点通信是“你发到我的地址、我发到你的地址”课设里最简单就是双方共用同一个地址。调试 nRF24L01 时先不要接单片机用两个 USB 转 SPI 的调试小板对发测试确认模块本身没问题再往工程里集成。还有一个经验供电要用纹波小的 3.3V最好加一个 100uF 的电解电容靠近模块电源脚很多无线模块“距离近、偶尔丢包”都是电源纹波造成的玄学问题。5. 排队叫号系统的常见翻车点与排查清单5.1 显示乱码和按键连击先查硬件还是先查代码现象一LCD1602 第一行正常、第二行全是方块或者完全没显示。原因大概率是这两类P0 口没接上拉电阻导致高电平驱动能力不足或者对比度调节电阻没调好屏幕“有显示但看不见”。解决先用万用表量 P0 口电压悬空时应该是高电平不是的话补 4.7k~10k 上拉排阻然后调 LCD 的 VL 脚电位器让背光下能看到淡淡的黑块再调程序里的初始化时序。注意 LCD1602 的初始化延时要求很严格上电后要等 15ms 以上再发第一条指令有些模块等 5ms 就够有些必须等 40ms这个时序在数据手册里有翻车时优先检查。现象二取号键按一次系统给出两个号。原因就是 4.1 里说的按键抖动或者代码里没有做边沿检测主循环每轮扫描都判断“按键为低电平就取号”结果按住的一瞬间被识别了几十次。解决把 4.1 节的 key_scan 整个替换掉原来的判断逻辑核心是“变化沿触发”而不是“电平触发”。还有一个隐藏坑如果取号逻辑里调用了 LCD 写入而 LCD 写入本身耗时几毫秒等于在消抖期间又做了一次长任务也会加剧误触发。解决方法是取号之后加一个 200ms 的“按键锁定”标志期间忽略取号键。5.2 语音播报和 LCD 显示不同步异步播报的时序问题现象LCD 已经显示“请 003 号到 1 号窗口”语音还在播上一个号或者干脆不播。原因常见是两个一是语音模块播报是异步的单片机发完串口指令就继续往下跑如果紧接着又发了一条新播报指令模块还在播上一条就会丢弃新指令二是 TTS 模块上电初始化需要时间单片机复位后立刻发指令模块还没准备好指令被吞了。解决给语音模块加一个 BUSY 引脚检测模块播音时为高电平播完变低单片机在发新指令前先查 BUSY忙就等。如果模块没有 BUSY 引脚就干脆在每次播报之间加一个固定延时比如 3 秒虽然丑但稳定。// 等待语音模块空闲再发送新指令 void voice_play_safe(unsigned char no, unsigned char win) { unsigned char timeout 0; while (VOICE_BUSY_PIN 1) { // 忙则等待 delay_ms(10); if (timeout 300) break; // 最长等3秒避免死等 } voice_play(no, win); }这个 BUSY 检测也有坑有些模块的 BUSY 引脚是开漏输出需要外部上拉到 VCC否则一直读到低电平永远认为“不忙”结果还是没有解决不同步的问题。所以排查时先用万用表测 BUSY 引脚在播报时是否有电平变化先确认信号有效再写代码。除了模块本身还要注意语音模块和单片机不要共用一个电源TTS 模块播报时电流能到几百毫安会把 51 的电源拉低导致 LCD 闪屏甚至复位。独立供电是课设演示稳定性的一个隐形保障。5.3 无线收不到数据和掉电丢号两个区位不同但都致命的坑现象一两个 nRF24L01 明明都配置了发送方显示发送成功接收方就是没反应。排查顺序要按“硬件到软件”走先量供电3.3V 必须稳定再查 CE/CSN/SCK 四根线有没有接反SPI 引脚接反是最常见的低级错误然后检查 RF_CH 和地址是否完全一致注意代码里写的地址数组多写一个字节都会导致匹配失败。最后一个容易忽略的是模块的 CE 脚电平时序进入接收模式前要 CE 拉高至少 10us有些代码在初始化最后忘了拉高 CE模块一直处于待机状态。发送方显示“发送成功”其实是发送 FIFO 清空了并不代表对方真的收到这个和 TCP 的 ACK 机制不一样调无线时记住这个区别能少走很多弯路。现象二系统断电重启后排队号又从 001 开始。很多人演示时被问“排队到一半断电了怎么办”答不上来。原因很简单排队号没有做掉电保存存在 RAM 里断电就没了。解决用 STC 单片机自带的内部 EEPROM实际是 Data Flash在取号成功后把当前最大排队号写进去上电初始化时读出来接着用。写 EEPROM 要注意磨损均衡不能每次取号都写否则寿命撑不住。常见做法是每隔 10 个号写一次或者只在“当前号被叫走并完成”时写一次掉电时最多丢失最近几个号演示场景完全能接受。下面给出内部 EEPROM 的读写示意。// STC89C52 内部EEPROM读写示例 void eeprom_write_byte(unsigned int addr, unsigned char dat) { IAP_CONTR 0x80; // 允许IAP操作 IAP_CMD 0x02; // 字节写命令 IAP_ADDRL addr 0xFF; IAP_ADDRH (addr 8) 0xFF; IAP_DATA dat; IAP_TRIG 0x5A; // 触发时序 IAP_TRIG 0xA5; _nop_(); IAP_CONTR 0x00; // 关闭IAP功能 }注意STC 的 IAP 操作有固定时序必须连续写 0x5A 和 0xA5 两个触发值中间不能被打断最好在关中断的环境下执行。不同型号的 IAP 地址映射不一样STC89C52 是 2K 字节 EEPROM、地址从 0x2000 开始STC15 系列又不一样用之前查对应型号的数据手册。还有一个细节写 EEPROM 前要先擦除扇区否则写不进去擦除会把整个扇区通常 512 字节清零所以不能把不同用途的数据放在同一个扇区。6. 进阶玩法让课程设计从“能跑”到“能答辩”基础功能跑通之后真正拉开分差的是几个锦上添花的设计。第一个是优先级排队比如 VIP 患者可以插队。实现思路不复杂队列里加一个“优先级”字段指向一个单独的 VIP 队列叫号时优先从 VIP 队列出队空的再叫普通队列。第二个是掉电恢复后的场景还原也就是 5.3 节说的 EEPROM 保存除了排队号还可以把窗口号、当前状态一起存进去上电后自动恢复到掉电前的状态而不是重新从 001 开始。这个细节答辩时一说出来老师会觉得你考虑问题比较完整。第三个值得做的方向是上位机数据统计。用串口把每次“取号时间、叫号时间、窗口号、排队时长”发给 PC上位机用串口助手或者简单的 Python 脚本接收画一个排队时长的折线图。这一块不占单片机资源只是增加一个串口发送函数而已但演示效果非常直观。我一般会建议在主循环里维护一个“排队计数器”每取一个号加一每完成一个号减一把当前队列长度实时发给上位机老师要是问“你这个系统怎么评价效率”你就可以指着曲线说哪个时段排队最长。最后一个进阶方向是多窗口动态负载均衡两个窗口谁先按叫号键谁拿队头的号而不是固定窗口 1 叫单号、窗口 2 叫双号。动态分配的代码量很少但和固定分配到差别很大也更接近真实场景。// 动态负载均衡先按叫号键的窗口拿到队头 void on_call_key_dynamic(unsigned char win) { if (sys.state S_CALLING) return; // 已有窗口在呼叫 if (queue_peek(q) 0) return; sys.current_no queue_peek(q); sys.window_id win; sys.state S_CALLING; }我个人做这类系统时有个习惯所有状态字段统一放在一个结构体里并且把状态机的每个迁移都打印到串口日志里比如“TAKE_NO:003”“CALL_WIN:1”“DONE_NO:003”。调试的时候打开串口助手看日志比盯着 LCD 猜状态要高效得多。这一行日志的习惯帮我排除过至少一半的“玄学 bug”——不是硬件玄学而是状态在某个分支里悄悄错了。把日志加上、把状态机画清楚这个课程设计离优秀就不远了。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑