1. 从“会说话的盒子”说起:系统组成与主控选型
第一次在公交车上留意到司机每到一站都要伸手去按一个键,我就在想,这个动作背后到底连着一套什么东西。后来自己动手做了这套基于STM32的语音报站器系统,才发现这事比想象中有意思。报站器听起来就是个“会说话的盒子”,但拆开看,它其实是四个独立模块捏在一起:主控负责逻辑,语音部分负责出声,按键或者GPS负责决定“什么时候说哪一句”,显示和状态灯负责让人知道当前状态。这套基于STM32F103C8T6的智能公交车地铁语音报站器系统,我前后整理出了完整程序、原理图、PCB和演示视频,写这篇博文就是想把这个项目从选型到踩坑的全过程复盘一遍,给正在做同类课程设计或者想自己捣鼓一套报站器的朋友一个可以直接参考的完整路径。
这个项目适合什么人?如果你是电子专业学生、嵌入式爱好者,或者正在找毕业设计方向,都可以拿它当模板。因为它的技术覆盖面相当典型——单片机最小系统、语音芯片控制、MOS管或三极管驱动、按键防抖、状态机编程、原理图绘制、PCB布局布线,一套下来几乎把入门嵌软要碰的东西都碰了个遍。而且它不像温湿度计、点灯这种项目那样“做完就忘”,报站器天然有完整逻辑闭环,尤其当你把自动触发加进去之后,状态管理、串口解析、距离计算全都来了,学习价值非常集中。
1.1 报站器的四个模块
先不聊电路,把系统切成几块来看。
- 主控模块:接收触发信号,维护当前站点索引,向语音模块下发播报指令,同时管理状态灯和可选显示屏。这是整个系统的大脑。
- 语音模块:存储报站音频,响应主控的指令播放指定语音。可以理解成一张“会说话的SD卡”,主控说播第几段,它就放第几段。
- 触发输入模块:公交车场景最常见的是一键切换(司机按一下、播一站),地铁场景则可能直接接收车门开关信号。进阶版会接GPS模块,通过定位自动判断进站出站。
- 人机交互模块:本站站名显示、上下行指示灯、音量调节旋钮。精简版可以不加屏幕,但状态灯要留。
在设计硬件之前,先把这四个模块画成一棵逻辑树,哪个接口接什么、信号怎么流,都定下来再开原理图,后面会省非常多事。我自己在这个项目第一次迭代时就是没想清楚触发逻辑,结果程序写完以后按键方案又改了,等于从头梳理了一遍IO分配,白白浪费两个晚上。
1.2 为什么选STM32而不是51或Arduino
这几乎是每一个拿到这个题目的人都会问的问题。听到“报站器”三个字,很多人第一反应是“51单片机够了吧”,确实够,如果只做按键触发加语音播报,51完全能跑。但我仍然建议用STM32,理由很实际:
第一,串口资源。语音芯片几乎都是串口控制,GPS模块也是串口输出NMEA语句,51的串口只有一个,接了语音模块就难再接GPS,加了GPS就得用软件模拟串口,稳定性又差一截。STM32F103C8T6有多个USART,语音、GPS、调试日志可以各用各的口,开发体验完全不一样。
第二,定时器数量。做按键防抖、播报超时看门狗、GPS距离计算的时间基准,这些东西都要挂定时器。51的定时器在做了PWM、做了计时之后,剩余资源很紧张;而F103C8T6有4个定时器,项目做下来也只用了一半不到。
第三,价格和生态。F103C8T6目前一片也就是几块钱,最小系统板甚至比有些51开发板还便宜。加上固件库和HAL库资料遍地都是,遇到问题随便搜都能找到参考。
第四,后续扩展余量。报站器做完之后,你大概率会想加个OLED显示站点名、加个蓝牙调站、做个LORA触发之类的,这些在STM32上都是往总线上一挂的事,在51上就要重新规划资源了。
所以我的建议是:除非你手头只剩51且只是为了交作业,否则直接STM32起步。我自己画原理图时用的是F103C8T6的LQFP48封装,成本低、引脚够用、手工焊接难度也不高,非常适合这个项目。
2. 报站器的“嘴”:语音播报芯片怎么选
语音方案是整个项目里最容易被低估的环节。很多人一开始想用STM32直接驱动喇叭——让单片机自己发声,比如用PWM输出不同频率做“哔哔”声,或者利用片内DAC播放一小段采样音频。实测下来这个路子有两个硬伤:一是STM32片内Flash和RAM装不了几秒钟的语音,二是PWM发声的音质基本是电子门铃水平,根本不适合播报中文站名。所以这个项目的正确姿势,是外接一颗专用的语音播报芯片。
2.1 三套语音方案的优缺点
市面上常用的语音方案大致可以分三派:音频播报芯片、MP3模块、中文TTS合成芯片。我一张表给你捋清楚:
| 方案类型 | 代表芯片 | 存储方式 | 音质 | 控制方式 | 适合场景 |
|---|---|---|---|---|---|
| 语音播报芯片 | JQ8900、WT588D | 外挂/内置SPI Flash | 中上,可录真人语音 | 串口或一线串口 | 报站这种固定内容场景 |
| MP3模块 | DFPlayer Mini | TF卡 | 高,直接放MP3 | 串口 | 需要大容量音频的场景 |
| TTS合成芯片 | SYN6288 | 无,现场合成 | 机械感较强 | 串口发送文本 | 站名无限变化、不能预录场景 |
对于公交地铁报站器,最优解是语音播报芯片。因为报站语音内容是完全固定的——每一站的“欢迎乘坐XX路”、“下一站是XXX”都是提前录好的,天然适合分段存储、按索引播放。JQ8900和WT588D这类芯片的逻辑就是:你给它一段音频文件,烧录进Flash,然后主控通过串口或者一线串口发一个索引号,它就播对应那一段。实现简单到像按遥控器。
DFPlayer这类MP3模块也可以做,但它更适合音频文件特别多、需要插TF卡随时换曲目的场景。报站语音一般就几十段,用SPI Flash存储足够了,而且TF卡的接触可靠性在车载震动环境下不如板载Flash。
SYN6288这种TTS方案我就不建议了,除非你想做的不是报站器而是“公交车走到哪、临时播哪站”的试验车。它确实能直接发送中文文本让芯片朗读,改站名不用重新录音,但音质明显有电子合成味,面向乘客的正式播报用这个效果不太行。
2.2 音频文件准备与播放时序
语音方案定了之后,最大的工作量反而在音频文件整理上。别小看这个步骤,我第一次做的时候一次性录了三十多段语音,命名混乱,烧录之后索引对不上,排查了半天。
几个实操经验:
- 采样率统一为16kHz/16bit单声道。这个参数是语音质量和芯片存储占用之间的平衡点。人声频段基本都在4k以内,16kHz采样绰绰有余。单声道WAV格式最通用,绝大多数语音芯片和烧录工具都能直接处理。
- 音频文件按“站名编号”命名。比如
001欢迎乘坐.wav、002下一站是人民广场.wav。这样在代码里维护索引时一目了然。 - 共用语句不要每站重复录。比如“下一站是”单独录成一段,“人民广场到了”录成另一段,播报时先播“下一站是”再播“人民广场”,这样能省一半Flash空间。这个技巧对站点数多的线路尤其划算。
- 播放时序上,务必等上一段播完再发下一段。语音芯片的串口指令可以连续发,但如果你在播到一半时发下一条,很多芯片会直接打断当前播报。报站器场景里偶尔打断还能接受,但如果你要做“先播报站、再播广告”这种串联播放,就必须利用语音芯片的BUSY脚——播放时拉低,播完拉高。主控查询BUSY状态,确认空闲后再发下一条指令。
2.3 实测中最容易翻车的一点
在所有语音相关的坑里,最常见的其实是串口波特率和指令帧格式不匹配。JQ8900、WT588D这类芯片出厂波特率可能是9600,也可能有4800的,而且指令往往带帧头和校验。如果你用STM32的串口直接发“播放第几段”的裸数据,大概率没反应。
正确做法是查清楚芯片手册里的指令协议。以JQ8900的串口控制为例,指令帧一般是:7E FF 06 03 00 01 00 FE EF这种带帧头帧尾的格式,中间的03是播放命令,00 00是播放索引。写个简单的发送函数,把要播的索引号填进去就行,但千万别看着网上老版本代码直接复制——同一系列芯片不同批次指令可能不完全一样,以你手上那块芯片说明书为准。
调试串口通信时我还有个土办法:用USB转TTL模块单独连语音芯片,在电脑串口助手里手动发指令。如果电脑能控制播放,那问题就出在STM32侧;如果电脑发指令都没反应,基本就是波特率、供电或者芯片没进入串口模式,先把芯片侧搞通,再回到单片机侧排错。
3. 报站触发机制:公交和地铁是两套思路
“什么时候报哪一站”这个问题看起来简单,其实不同场景差别特别大。公交车是走一条固定线路,报站时机取决于司机想什么时候报,和车辆位置并没有严格绑定;地铁虽然也是固定线路,但车门的开闭信号、司机确认按钮都是有逻辑顺序的。所以我把触发部分分成了两条线来讲。
3.1 手动按键和红外遥控(最稳的方案)
项目第一版建议做手动触发,这是最稳的方案,也是我实测调试时一直在用的方案。系统保持一个“当前站索引”,按“下一站”键,索引加一,同时触发语音播报“下一站是XXX”;按“上一站”键,索引减一,往回播报。上下行的处理不用搞两套站表,一个方向标志位就够:上行序号和下行序号之间往往是镜像关系。
按键处理有几个细节值得注意:
- 按键必须做防抖。机械按键按下和释放瞬间会有几毫秒到几十毫秒的抖动,不处理的话一次按压会被识别成多次。我是用定时器做个10ms周期的扫描,读到电平变化后再连续确认两次,确认变化持续了两个周期才认为是一次有效按键。
- 播放中按“下一站”要谨慎处理。如果你在语音播报还没结束时按了下一站,是直接打断、还是等播完再跳?报站器场景里我建议等当前播完再响应下一次按键,否则很容易出现司机连按两下、语音也连跳两站的情况,乘客完全反应不过来。
- 红外遥控触发是手动触发的一个变种。用红外接收头接STM32的外部中断,遥控器上的“上翻”“下翻”“播放”三个键对应三个功能。这个方案做演示效果极好,因为你可以远距离操作,也显得更“智能”。我自己在视频演示里就是用遥控器做上下行切换的。
3.2 车门开关量触发(地铁场景)
地铁场景可以做一套更自动化的触发方式:列车到站、开门时给一个干接点信号,报站器检测到这个信号就触发播报。这个方案在原理上很简单,但有一个非常重要的工程细节——信号隔离。
列车的开关门信号往往来自车辆控制系统,电压等级、共地关系和你板子完全不一样,直接拿一根线接到STM32的GPIO上,轻则误触发,重则烧IO口。我的做法是在触发输入引脚前加一个光耦隔离(比如PC817),列车侧的开关量驱动光耦内部的LED,光耦另一侧由板子上的3.3V供电,输出直接进MCU。这样两边电气隔离,安全系数高一大截。
处理这种开关量输入时,GPIO要配置成上拉输入,光耦导通时拉低、断开时恢复高电平。软件上我建议在触发判定里加一个“窗口确认”——检测到信号变化后再延迟20ms读一次,确认状态稳定了才认为有效。地铁里的人流震动、电磁干扰可不比教室里的面包板,逻辑条件不能太脆弱。
3.3 GPS自动报站(进阶)
再往上走一步,就是标题里这个“智能”的核心——GPS自动报站。我用的模块是ATGM336H,北斗和GPS双模,串口直接输出NMEA 0183协议的数据。STM32通过串口接收$GNRMC语句,从中解析出经纬度,然后和预先存储在站表里的站点经纬度做距离计算,当距离小于设定阈值(比如80米)时,认为车辆已经到站,自动触发播报。
距离计算用的是经典的Haversine公式,换算成代码其实就二十来行。这里我不建议用球面余弦公式,它在短距离时的精度不如Haversine稳定,这点差别在城市公交这种几百米站间距的场景下还是能感觉出来的。
GPS自动报站真正的坑不在计算,而在判断的稳定性。卫星信号在楼宇密集区域会漂移,车辆明明还没到站,定位点可能跳到站台半径内几十米,导致提前播报。我的处理办法是“连续两次判定”逻辑:设定一个状态标志,第一次进入半径范围内只是“候选”,等下一次解析出经纬度后再次计算,如果仍在半径内才真正触发;车辆驶出半径时再清掉候选状态。这个防抖思路和按键防抖本质完全一样,只是时间尺度从毫秒拉到了秒级。
另外,在没有真实GPS信号的环境调试自动报站逻辑时,可以用STM32的PWM输出模拟GPS模块产生脉冲信号,或者干脆用串口助手定时发送伪造的NMEA语句来模拟车辆移动。我给这个项目录演示视频时就是这么干的——把人放进车里跑一圈录真实数据不现实,但用模拟数据把逻辑链路完整跑通是完全可以做到的。
4. 原理图设计关键节点:电源、功放与最小系统
报站器这类的原理图,看着元件不多,实则有一堆容易埋雷的地方。我把它分成电源、功放电路、最小系统三块来设计,每一块单独画完再进行拼接,这样可以避免在集成阶段被一个地线问题搞得团团转。
4.1 电源树设计
车载场景的输入电压通常是12V,板子上却同时需要5V和3.3V两路低压。我用的方案是12V → LM2596-5.0 降压 → 5V,然后5V → AMS1117-3.3 → 3.3V。
选择LM2596做主降压而不是直接用AMS1117从12V降到5V,是因为12V到5V压差太大,线性稳压器会烧掉。LM2596是开关电源方案,效率高、发热小,输出5V给语音芯片和功放供电刚好够。AMS1117只负责再降一档给MCU供电,压差只有1.7V,芯片不烫。
电源这一块的电流预算要提前算清楚:功放PAM8403工作在5V时,输出功率按3W算,峰值电流可能到1A以上;语音芯片正常工作电流几十毫安;MCU最省只有几十毫安。所以5V这一路至少要能撑住1A以上,3.3V这一路几百毫安就够。
还有一个很多人忽略的细节:开关电源的输出纹波比线性电源大得多,LM2596输出的5V直接给D类功放供电时,噪音可能串进音频里。我的做法是在5V输出端加一个10uH的功率电感加1000uF电解电容的LC滤波,效果立竿见影。实测不加滤波时喇叭能听到明显的“滋滋”底噪,加了之后基本听不出来了。
4.2 功放电路与复位下载电路
语音芯片的DAC输出直接推喇叭是不现实的,带不动,声音也小。一定要加功放。我选的是PAM8403,一颗D类功放芯片,5V供电可以输出最高3W,对于报站器这种“广播”场景完全够用,而且便宜、外围电路极简。
功放部分接法:语音芯片音频输出用10uF钽电容耦合,送进PAM8403输入端。喇叭接到功放的SPK+和SPK-两个输出脚。PAM8403的供电要在芯片旁边就近放一个100uF电解和100nF瓷片电容,这是D类功放的开关噪声特性决定的,退耦电容离芯片脚越近越好。
最小系统部分,除了STM32F103C8T6本体,还包括8MHz晶振和两个20pF负载电容、复位电路(10K上拉电阻加100nF电容到地)、BOOT0和BOOT1跳线、3.3V每个电源引脚旁100nF退耦电容。SWD下载口四根线(SWDIO、SWCLK、GND、3.3V)必须引出来,ST-Link全靠它烧程序。别为了省事只画串口下载,串口ISP下载每次要手动拨BOOT0,做项目调试时会崩溃的。
5. PCB布局布线:音频电路和数字电路共存的纪律
原理图画完只是第一步。报站器里同时有MCU这种数字电路,也有功放和喇叭这种模拟大电流回路,布线时如果处理不好两者关系,最典型的现象就是:功能看着都对,喇叭里却总有背景噪声。这一节讲我在这块板子上是怎么排布和走线的。
5.1 布局规划与外设接口
板子做成双面,尺寸我控制在80mm×60mm左右,适合装进一个小盒子。布局上有一个核心原则:把数字部分和模拟大电流部分分开放置,中间留出明显分界。我习惯左侧放电源接口、LM2596和滤波电路,中间偏右放MCU、语音芯片,右上角放喇叭接口和PAM8403功放,按键排针放在板边方便接外接面板,SWD下载口放在板子边缘最好握持的位置。
外设接口的丝印一定要标注清楚,尤其是电源正极、喇叭两脚、串口TX/RX。我焊过一块板子,因为喇叭接口正负极标反,接上去声音发闷还容易烧功放,拆下来重新飞线才解决。另外在板上预留一个三针串口调试座(GND、TX、RX),后期排查问题时不用飞线到处找,直接杜邦线一插就行。
5.2 布线与地平面细节
布线规则按优先级排序:
- 电源线宽按电流算。1oz铜厚、1A电流的情况,经验上走10mil就能过,但我会留2到3倍余量——5V主电源线至少1mm宽,12V输入线用40mil到60mil,LM2596的开关节点走线还要更短更粗。功放到喇叭的输出走线也至少50mil,因为它实际走的是交流大电流。
- 晶振走线短而直。8MHz晶振的两根线尽量短,下方不要走任何信号线,否则高频时钟会耦合到音频通路里,噪声非常难查。
- 底层大面积铺地。整个PCB底层铺地铜,但功放和喇叭附近的地要单独处理。这里有个关键概念——模拟地与数字地。我在MCU的GND和功放的地之间用0欧电阻单点连接,模拟地部分和数字地部分只在这一点连通,能显著减少数字开关噪声串进功放。
- 音频信号输入线远离大电流开关节点。语音芯片输出到功放输入的两根线,不要贴着LM2596的电感走,也不要和喇叭线平行长距离走线,否则会感应到开关噪声。
还有一个小细节是固定孔。如果板子要装进外壳,用M2螺丝固定的话,孔的最终直径建议做到2.2mm到2.5mm,焊盘环宽留0.6mm以上;用M3螺丝就做到3.2mm孔、外圈5mm焊盘。小了装不上螺丝,太大固定又会松。
我画这板子用的是嘉立创EDA,学生党画完直接导出Gerber下单打样,工艺选双面喷锡就够了,不需要沉金。整板过完DCR和DRC检查之后,先不焊元件,用万用表量一下电源两端对地阻抗,确认没短路再开始焊。
6. 程序框架:状态机、站表和播放时序
硬件板子到手后,程序才是整个项目的重头戏。报站器程序的难点不在某一句代码,而在于如何把“当前是哪一站”“现在是否在播放”“用户刚按了什么”这些状态理清楚。我在写这套程序时最核心的思路,就是状态机加站表。
6.1 站表结构体与上下行设计
所有报站信息集中放到一个结构体数组里,每个元素代表一个站点。
typedef struct { const char *name; // 站点名,统一用UTF-8编码保存 uint16_t audio_up; // 上行播报音频索引 uint16_t audio_down; // 下行播报音频索引 float lat; // 站点纬度,GPS模式用 float lng; // 站点经度,GPS模式用 } StopInfo; const StopInfo stops[] = { {"始发站", 1, 32, 31.2304, 121.4737}, {"人民广场", 2, 31, 31.2400, 121.4690}, // ... };这样设计的妙处在于:一条线路不管有多少站,只要基础站表不变,上下行共用一张表,方向用当前方向标志位来选择音频索引即可。GPS自动报站时,遍历站表计算距离也只需要一次循环。
站点名编码有个很实际的问题,也是被问过很多次的“STM32 GBK转UTF8”:如果你用OLED/LCD显示站点名,而实际源的编码是GBK、屏库又是按UTF-8组织的,就会乱码。我在这套项目里统一全员UTF-8,从源文件保存编码、到串口日志输出、再到屏幕取模工具,都保持一致的编码环境,问题就自动消失了。如果你用的屏幕字库模块是固定GBK编码的,那就反过来统一GBK,总之不要混搭。
6.2 状态机与播放防抖
程序主循环里跑一个简单状态机,状态定义如下:
STATION_IDLE:空闲状态,等待触发STATION_PLAYING:正在播放当前站语音STATION_WAIT_RESET:播放完成,等待按键复位或下一轮触发
按键触发时,先查当前状态,如果不是空闲状态就忽略这次触发;进入播放状态后,语音芯片的BUSY脚作为播放完成信号,一旦检测到BUSY拉高(播放结束),状态迁回空闲。这套逻辑用代码写出来很清晰:
switch (station_state) { case STATION_IDLE: if (key_next_pressed) { current_stop_index++; play_audio(stops[current_stop_index].audio_up); station_state = STATION_PLAYING; } break; case STATION_PLAYING: if (voice_busy_pin == HIGH) { // 语音芯片播完 station_state = STATION_IDLE; } break; }这个状态机避免了“播放中连按跳站”的老大难问题。我第一次写的时候没加播放状态判断,司机按两下键,语音直接跳了两站,到了下一站乘客还在听上一站的报站,整个系统像喝醉了一样。
还有一个关键细节是语音芯片的BUSY脚不能只用轮询,最好同时作为外部中断输入。语音播放一般持续几秒钟,如果主循环里一直死等BUSY,按键扫描和串口日志就全卡住了。我实际的做法是轮询加节流:每10ms扫描一次BUSY脚,既不影响其他任务,判定播完的延迟又足够小。
6.3 中文编码与串口日志
调试这种带中文的项目,串口日志输出是效率翻倍的工具。我在每个状态切换、每次按键触发、GPS每次定位计算时,都输出一行结构化日志,格式类似[GPS] lat=31.2304 lng=121.4737 dist=42m trigger=NO,这样一看就知道当前逻辑走到了哪一步。
串口日志也有编码坑。STM32发出的中文字节流是UTF-8,如果你的串口助手里设置了GBK显示,日志里的中文就是一堆乱码。这属于调试环境配置问题,不是代码问题,但很多人在这上面浪费过时间。我一般直接让日志全部输出英文或者拼音,再不然就确保串口终端用UTF-8打开。
7. 实测排错:从“不出声”到“正常报站”的完整排查链路
项目做完之后,真正的学习才算开始——因为第一次上电几乎不可能一次通过。我总结了自己这轮调试里踩过的高频问题,按排查链路讲一遍。
7.1 上电没反应和下载失败
先量电源,这是铁律。用万用表测量3.3V和5V的对地电压,正常范围内再进行下一步。如果3.3V偏低或者几乎为零,先断开AMS1117的输出端,排查是不是MCU某引脚短路。焊接LQFP48这种引脚密的芯片时,最容易出现相邻引脚锡连,肉眼看不出来,但用万用表蜂鸣挡逐排量就能发现。
STM32程序下载失败是我见到的最高频问题之一。常见原因有这么几个:
- BOOT0被拉高,芯片进入了ISP模式——一般默认BOOT0接10K下拉到地,能正常烧录。
- ST-Link和板子接触不良,SWD四根线松动或者杜邦线太长。
- 目标板没有独立供电,ST-Link的3.3V输出能力带不动板子整体功耗。
- 芯片没有进入调试模式,可以试着按住复位键再点烧录,烧录瞬间松开复位。
排查顺序我建议是:先看ST-Link能否识别到芯片,再看供电是否正常,最后查BOOT配置。不要一上来就怀疑芯片坏了,STM32芯片被烧坏的概率远比你想象的低。
7.2 语音无声、杂音和声音小
语音无声的排查链路要按“信号流”从后往前查,从喇叭端逐步往前追:
- 喇叭接的是功放的SPK+和SPK-,先确认喇叭本身没坏——拿一个1.5V电池碰一下喇叭两个脚,听到“啪嗒”声说明喇叭是好的。
- 量功放芯片供电,5V是否正常送达芯片电源脚。
- 用示波器或万用表交流档量功放输入,看语音芯片播放时是否有音频信号输出。如果有信号、喇叭还是没声,问题大概率在功放或接线;如果没有信号,往前查语音芯片的输出。
- 语音芯片输出正常的前提是它能收到控制指令。用电脑的USB转TTL串口助手直接发播放指令给语音芯片,如果电脑能控制播放,说明语音芯片链路没问题,问题在STM32和语音芯片的通信上。
杂音和声音小的问题,绝大部分出在地线上。功放那部分地线如果和数字地混在一起,数字开关噪声会直接叠加到音频输出。还有一个常见情况是语音芯片的DAC输出没有加耦合电容直接接功放,导致直流偏置进了功放,声音变小且失真。经验之谈:语音信号链上的每一级之间都要用电容隔直流,电容容值选10uF左右,实测效果稳定。
7.3 跳站、漏站与开机自动播报
自动播报里最抽象的Bug是“一上电就自动播报”,第一次遇到时查了很久。后来发现是STM32上电瞬间GPIO引脚电平不稳定,语音芯片的BUSY脚或者触发IO被电平抖动误触发了一次。解决办法很直接:程序初始化时先把触发引脚的上拉和状态配置好,再延时几百毫秒,等各模块上电稳定后再使能外部中断或按键扫描。语音芯片本身也需要一个上电稳定时间,指令发太早芯片可能没准备好,直接丢了。
跳站漏站问题,在手动触发模式下基本都是防抖或播放状态判断没做好。我前面讲的状态机在这里能一次性解决,不需要额外补丁。GPS模式下跳站还要注意“方向判断”——车辆在站点附近倒车、绕圈时,如果不判断行驶方向,可能反复触发同一站。我的经验是在触发判定里加入“站点索引与上一站索引之差不为0”的条件,只有从上一站走到下一站这个过程才允许触发。
整套程序跑通之后,我把所有站点音频、上下行切换、GPS模拟触发都录进演示视频里。这样不仅是为了展示结果,更是为了固化一套“可复现的验收流程”:每次改动硬件或代码,都依照同样的步骤重新跑一遍,就能快速看出改动有没有引入新问题。
最后分享一点我自己的体会:报站器这个项目的难点真的不在单片机,而在状态管理和模块间的时序配合。做完这一套硬件加程序之后,你对状态机的理解、对模块化设计的感知,都会比单纯做那些“点灯、数码管”项目深刻得多。如果接下来想继续扩展,合理的路线是:先给板子加一块OLED显示当前站名,再接入GPS做全自动报站,之后可以尝试普通话、英语、方言分段切换,最后才是研究CAN接入车载系统——步子别迈太大,每加一个功能就把当前版本固化和留好日志,否则出问题你连从哪查起都不知道。