停车计费题如何用状态机拿下蓝桥杯单片机省赛 📅 发布时间:2026/9/3 3:05:48 👁 浏览次数: 简介2021年12届蓝桥杯第一场停车计费赛题要求设计一套停车场自动收费系统覆盖车辆进出管理、时长记录与动态计费核心考察数据结构选型、时间戳差值算法、异常输入处理并融合STM32下的嵌入式开发。蓝桥杯作为全国性软件与信息技术竞赛本题对备赛者、单片机学习者和职场技能提升者均有较强参考价值。资源包共341个文件、13.81MB主体为C语言源码.c/.h内含HAL库驱动和应用层逻辑另有.o/.axf/.hex等编译连接产物、.uvprojx工程配置与.sct链接脚本可直接导入Keil MDK在STM32G4上编译运行也可用于分析编译过程与内存布局。目前已有472人学习下载目录结构清晰便于按模块查阅源码尤其适合赛后复盘BSP板级驱动与业务功能实现。除停车计费主逻辑外资源内还有定时器、串口、ADC等外设驱动示例能帮助读者掌握中断服务、状态机与低耦合代码组织方式完整理解从硬件底层到上层业务输出的工程化流程。1. 停车计费题为什么成了历届选手的“分水岭”2021年第12届蓝桥杯单片机省赛第一场“停车计费”这四个字一亮出来现场很多人是松了口气的——没有复杂的温度曲线、没有PWM占空比调光、没有一串串看不懂的报文协议说白了就是一辆车进来、一辆车出去、算个钱。但我当时考完就有一个直觉这道题拿高分的人不会太多。因为它不考你“会不会某个外设”它考的是你“能不能在一个看似简单的业务场景里把状态、时间、存储这些东西掰扯清楚”。先说清楚这道题面向的是单片机设计开发组用的是国信长天那套CT107D开发板主控是IAP15F2K61S2。题目模拟的是一个停车场管理系统有车辆进场、有车辆出场、要显示车位剩余数量、要记录停车时长、要按规则计算停车费。核心外设覆盖了独立按键、LED指示灯、数码管动态扫描、DS1302实时时钟、AT24C02掉电存储。这几样东西单拆开看练习册上哪道题都有但是组合在一起塞进一个“停车计费”的业务壳里考察的就不再是外设驱动而是业务逻辑的条理性。为什么我喊它“分水岭”因为这道题对两类人特别不友好。第一类是那种外设驱动背得滚瓜烂熟、但一遇到多状态交互就大脑空白的选手。DS1302他会读AT24C02他会写数码管他会扫但“进场按键按下去之后系统要做什么、显示什么、时间存到哪儿、再次上电之后怎么恢复”这一连串问题他一下子串不起来。第二类是那种平时练习只跑官方示例工程、从来没自己从头搭建过逻辑框架的选手。示例工程里只有“按下按键—现象出现”的直线逻辑而停车计费要求的是“事件驱动状态流转掉电恢复”的完整闭环这已经无限接近真实产品开发了。反过来对平时习惯画画流程图、写着写着就把main函数分成“初始化-按键处理-显示刷新-业务逻辑”几个模块的人来说这道题就是个送分题。同样4个小时的考试时间有人光调DS1302的时间写入就耗掉一个多小时有人四十分钟就把框架搭完了剩下的时间全在扣边界条件。差距不是手速是思维习惯。所以我在给备赛的人做辅导时从来不会只让他们对着题目敲代码。我会先问一句如果这个停车场是你家楼下那个你觉得它应该怎么工作想明白这个问题代码怎么写都是顺理成章的事。下一节我就把这道题的功能需求逐条掰开看看题目背后到底想让你实现什么。2. 功能需求逐条拆解每一行字背后都有坑2.1 停车业务里的三个核心对象车辆、车位、账单停车计费的业务模型说穿了就是三类数据在转车辆信息、车位状态、计费账单。蓝桥杯这道题不会让你真的去识别车牌它用按键来模拟车辆的进出用数码管来展示当前的业务数据用LED来指示系统状态。你要做的事就是把这套“模拟系统”做得跟真的一样。先说车辆进场。按一下“进场”按键系统要判断当前有没有空余车位如果有车位剩余数量减一记录当前时间作为这辆车的进场时间同时亮起对应的指示灯表示“有车进来了”。如果车位已经满了你得拒绝进场最好能有个提示。再说车辆出场。按“出场”按键系统读取当前时间减去那辆车的进场时间算出停车时长再套用题目给的计费规则算出费用。费用怎么显示、要不要累加到总营收里、累计数据掉电之后还在不在这些都是考点。这里就出现第一个容易让新手翻车的点停车场不止一个车位但蓝桥杯题目的硬件资源有限它不会让你真的管理几十个车位。通常的做法是用数码管显示“剩余车位数”用LED的点亮数量模拟“当前占用状态”。比如8个LED对应8个车位亮表示占用。这样一来你既要维护一个“剩余数量”的整型变量又要维护一个“车位占用状态”的数组或者位变量。两者必须同步更新这就是第一个逻辑链。第二个坑藏在“进出记录”里。真实停车场要记录每辆车的进出时间但单片机资源有限题目不会让你写一个链表去存几十辆车的记录。它通常只模拟“当前有一辆车在场内”这种情况或者最多用一个结构体保存最近一条进场记录。换句话说你不需要做车队管理但必须保证“进场的时刻”被可靠地保存下来并且出场的时候能准确算出差值。这个“保存”动作就是DS1302和AT24C02登场的地方DS1302负责提供实时时间AT24C02负责掉电之后不让数据丢失。2.2 计费规则看似是小学数学实则是状态机考题停车费怎么算是整道题的灵魂。蓝桥杯的计费题通常不会让你做一个静态价格表它会让费用跟“当前时刻”挂钩。比如工作日白天一个价、夜间一个价、周末又是一个价或者更常见的套路是按小时计费前若干小时一个单价超过之后另一个单价甚至封顶价格。这样一来“当前时间属于哪个计费段”这件事就变成了一个必须实时判断的逻辑。我当时看到这题第一反应是“这得用分段函数”第二反应是“分段函数的边界条件才是真正的坑”。比如跨时段停车怎么算晚上10点进场、第二天早上7点出场按哪个费率题目要是规定了“按进场时刻费率计费”那还好说取进场时间判断一次就行要是要求“按实际占用时段分段累加”那逻辑复杂度直接上一个台阶。考场上我见过有人在这里反复改条件判断改到最后自己都晕了这就是典型的“没在动手前把规则画成图”的后果。我的建议是考场上拿到题目先别碰单片机拿张草稿纸把计费规则写成分段表达式把所有边界时间点标出来。比如“前2小时5元/小时之后3元/小时24小时封顶20元”——你就得问自己第2小时整到底按哪个算23小时59分进场跟00点01分进场有什么区别这些边界想清楚了再写代码就是一次过不想清楚就是无尽的if嵌套地狱。2.3 外设清单背后的真实分配方案再捋一遍这道题用到的东西逐一对应它的职责数码管显示剩余车位、停车时长、费用金额或者滚动显示进出记录。LED显示车位占用状态、系统运行状态、满位报警。独立按键模拟进场、出场、参数设置比如切换显示模式、调整费率。DS1302提供实时时钟记录进场和出场的时刻。AT24C02保存累计营收、剩余车位、当前费率等数据掉电不丢。蜂鸣器/继电器出场或者满位的时候给出声光提示。你会发现每个外设的音量都不大但合在一起它们必须被一套清晰的主逻辑串起来。这个主逻辑就是下一节要讲的“状态机”思路。还有个细节值得提一下CT107D开发板上数码管和LED经常共用IO口平时练习的时候你没觉得有什么真到写代码的时候动态扫描和LED刷新一旦共用延时就会出现显示闪烁、亮度不均的问题。这个问题我在第四部分会专门讲赛场上无数人栽在它手里。3. 程序框架设计从“能跑”到“不乱”的状态机思路3.1 为什么我劝你别用一堆if硬怼业务逻辑我见过太多备赛的人写代码main函数里先初始化然后一个while(1)套三层if——if(按键1按下)、if(按键2按下)、if(系统处于某种状态)。功能单的时候没问题一旦像停车计费这样既要计时又要算钱又要存数据就乱了按键扫描和数码管刷新挤在一起延时一长DS1302的时序就错位业务变量到处可见一个地方的赋值改了另一个地方不知道bug查半天。这套东西在工程上有个正式的名字叫“状态机”。听起来高大上其实本质很简单把系统的运行过程拆成若干个互斥的状态每个状态只做自己该做的事事件来了才切换状态。拿停车计费来说至少有这么几个状态空闲待机、进场处理、出场处理、计费显示、满位报警。每个状态下按键按下去的含义不一样空闲时按“出场”键可能无效处理中再按“进场”键可能不应该响应。用状态机管理这些约束逻辑一下子就干净了。具体到代码我不主张上多复杂的框架考场上也没时间写什么操作系统。我通常的做法是定义几个枚举常量表示状态在while循环里用一个switch把状态分支打开typedef enum { STANDBY, VEHICLE_IN, VEHICLE_OUT, BILLING } SysState; SysState currentState STANDBY; while (1) { KeyScan(); DisplayRefresh(); switch (currentState) { case STANDBY: standbyHandler(); break; case VEHICLE_IN: vehicleInHandler(); break; case VEHICLE_OUT: vehicleOutHandler(); break; case BILLING: billingHandler(); break; default: break; } }这个框架看着简单但它的价值在于把“按键处理”和“业务逻辑”解耦了。按键只管记录“有没有事件发生”状态机决定“事件在当前状态下要不要响应、怎么响应”。这样每条代码路径都清晰可查调试的时候也容易复现问题。3.2 显示刷新与按键扫描的分时设计把按键扫描和数码管刷新放进while主循环是大多数人的第一直觉。但这里有个隐患单片机是单线程的数码管动态扫描需要周期性地刷新才能稳定显示按键需要消抖延时DS1302读写又有自己的时序。如果它们串在一起互相等待整个系统就会表现出“按键按下没反应”“数码管闪烁”这类令人抓狂的现场。我的做法是给每个模块分配明确的执行节奏。最土但最实用的方案是“标志位主循环轮询”用一个定时器产生1ms的中断在中断里对按键进行扫描、对数码管进行刷新同时维护一个系统时间戳主循环只处理业务逻辑判断时间戳是否到了该干什么的时间点。这样按键消抖、显示刷新这些对时序敏感的工作被牢牢锁在中断里业务逻辑再复杂也不会拖垮它们。这里插一句避坑心得考试和平时练习不一样赛场上我见过有人在中断里写DS1302读写结果因为时序太长导致主循环里的数据显示卡住。教训就是中断里只做最轻量的操作——清标志、计数器累加、IO翻转——把DS1302这种带时序的外设读写全部放到主循环里去做。你可以用“时间片轮询”的思路每隔100ms去读一次DS1302每隔200ms去把新的数据写入AT24C02完全够用而且稳定得多。3.3 掉电存储与上电恢复的完整闭环停车计费这种题AT24C02的存在意义就在于“断电了账不能乱”。你想一个停车场要是停个电累计营收清零了车位数也乱了那还怎么营业所以考试必考一个动作把关键数据写进EEPROM然后在系统上电的时候读回来。写什么数据通常包括当前剩余车位数、累计营收金额、费率参数如果系统要支持“断电解锁”还得记录一个状态标记位。这里有个关键点不是任何时候都该写EEPROM。你想想每次按键都要写一次AT24C02的擦写寿命虽然一般标称10万次但考试时反复按、反复写万一执行到一半断电数据就毁了。更合理的方案是业务变化的时候先在内存里改等某个特定时机比如出场结算完成、满位状态改变再落盘。我在做这道题的时候习惯把AT24C02的写入封装成一个函数在“数据发生变化且系统空闲”的时候调用而不是在按键中断里直接写。上电恢复的代码放到初始化阶段一开机先把关键参数读出来再根据这些参数把LED状态、数码管显示、当前状态机所处状态全部恢复。有些同学忘了恢复“当前状态”导致断电前停车场是满位的重启之后指示灯全部熄灭车位数也变成初始值这不是功能缺失这是逻辑没闭环。4. 实测踩坑记录我在这道题上交过的“学费”4.1 数码管亮度不均真相是刷新节奏没对齐这道题最让我印象深刻的坑是数码管和LED抢IO。CT107D开发板为了省引脚把数码管的段选和LED的位选放在同一个IO口上。平时做一个LED流水灯、做一个数码管计数器大家都觉得理所当然。但停车计费要同时显示很多内容剩余车位、计费金额、时间你还得让8个LED保持“某一辆车占用”的状态。搞不好就是数码管正常显示LED那边亮度不均匀甚至几个LED偷偷灭掉。排查下来问题出在刷新时序上。数码管动态扫描要求每一位点亮一小段时间再切到下一位整个周期人眼看起来才是连续显示的。LED如果在你扫描数码管的时候被顺带改写了位选它就会闪。解决思路很简单把LED的锁存操作和数码管扫描放在同一个时间片里或者干脆先把数码管的某一位置显示完再统一刷新LED状态不让两者交错。说完这个后续大家在训练的时候见到“显示闪烁”可以先从锁存器操作入手别一上来就怀疑硬件坏了。4.2 按键“按一下变成两下”消抖和防重触发要一起做停车计费里按键是个高频操作进场、出场、切换显示、费率设置全得靠它。但按键在高频使用下有个老毛病机械抖动。按下一次电平在几个毫秒内会反复跳变如果你只读了一次电平就当成一次有效按下那就会出现“按一次算两次”的现象。你明明想让一辆车进场结果系统进了两辆直接满位。基础的消抖大家都会延时10ms再读一次。但考场上还有更隐蔽的坑“重触发”。就算电平稳定了你的手指还存在一个按住不放的过程。如果代码里没做“释放检测”那按键一直被按住主循环每次跑到按键判断都会触发一次业务逻辑结果就是车辆一辆接一辆进场。我的处理方法是加状态标记只有检测到“从按下到释放”的完整过程才记一次有效按键。相当于把边沿触发当成需要同时确认低电平变高电平变化的过程。4.3 DS1302读写失败十有八九是时序缝隙没留够题库里DS1302不是难题很多人调通一次就再也没管过。但停车计费要求你频繁读取时间做计费运算这就把DS1302的读写时序问题暴露出来了。尤其是把DS1302的操作放进中断之后随时可能被打断读回来的时间就会出现一秒跳两秒、甚至读到负数的情况。解决思路有两个方向一是把DS1302的读写全部放到主循环用时间片保证读操作完整执行二是读取的时候连读两次两次结果一致才采用。这个方案虽然土但在比赛场景下非常实用稳定性提升巨大。另外DS1302在掉电之后是靠电池维持走时的如果你在调试过程中发现时间总是不对先检查开发板上有没有装电池、跳动开关有没有拨对位置。别笑考场上有太多人因为电池没装花了一个小时在调代码。4.4 AT24C02写入时机不对累计营收悄悄归零AT24C02写数据本身不难难的是“什么时候写、什么时候不写”。我见过一个同学的代码每次出场结算完直接写EEPROM测了三次都正常。结果一到测试“断电再上电”的环节数据就乱套了。查到最后发现他是每次启动都把默认值写进EEPROM把之前存好的数据覆盖了。这还不如不写。这就是典型的“上电恢复逻辑”和“默认参数初始化”顺序搞反了。你应该先读EEPROM读到有效标志位就用EEPROM里的数据读不到有效标志位才说明是第一次使用再把默认值写进去。我在代码注释里特意写了三行先读、判断标志、再决定是否初始化。顺序反了就会像我那个同学一样每次上电都“重置系统”数据永远存不住。5. 从停车计费到整场备赛最值钱的其实是思维习惯5.1 一道省赛题的考点清单比想象中全把停车计费这道题吃透你会发现它几乎覆盖了单片机开发的全部基础考点GPIO控制、动态数码管扫描、按键消抖与状态检测、定时器中断、DS1302实时时钟、AT24C02 I2C存储、状态机业务逻辑、掉电恢复机制。这些名词单独拿出来每一个都是省赛的常客。所以你要是能把这道题从头到尾独立写出来省赛拿个二等奖以上的概率就很高了。我建议备赛的同学不要只盯着“我要把题做完”而是做完之后做三件事重新画一遍状态图把边界条件标出来把硬件资源分配表写出来看看有没有更优方案把代码里所有“魔法数字”改成宏定义或者枚举提升可读性。这三件事做完这道题才算真正属于你。5.2 主观上觉得“差不多”比赛时直接露馅备赛的时候有个现象很典型看例题代码觉得自己都懂照着敲了一遍能编译能下载就觉得拿下了。等你合上课本自己写或者我让你换个计费规则、改一下停车位数量你试试能不能一次跑通。停车计费这道题网上能找到的源码版本不下十种但看懂别人的代码和自己在考场上从零写出来是两码事。我比较推荐的做法是“限时复现”找一道往年真题给自己定4小时全程不翻参考代码只允许看芯片手册和开发板原理图模拟考试状态。第一次复现大概率翻车没关系记录下卡住的位置隔三天再复现一次这个过程比做十套练习题都管用。考场上拼的就是这种“肌肉记忆”。5.3 考场上稳住的三条心法最后分享三条考场经验这三条是我带了很多届备赛的人之后总结出来的。第一条拿到题先做需求拆解和状态图别急着写代码。磨刀不误砍柴工有草稿纸上的流程图你写代码的速度会快一倍而且不会走到一半迷路。第二条每个功能模块写完立刻验证不要等全部写完再下载试。单片机这玩意儿是“硬件软件”联合体堆了太多代码再调试出了bug都不知道是逻辑错了还是外设时序错了。分段验证能让你永远知道自己在哪一步出了问题。第三条留出至少一小时做“断电测试”。蓝桥杯的评分是功能测试测试员不会只在你正常操作的情况下打分他们一定会断电重启、乱按按键、甚至在你没启动的时候直接看显示。把你上电恢复、按键防抖、边界条件全部用“刁难模式”跑一遍比赛分数就没那么大悬念了。说到底停车计费这道题能教给我们的不只是怎么调一个DS1302或者怎么刷一个数码管而是一套“从需求到代码再到可靠性”的完整思考方式。这玩意儿才是比分数本身值钱得多的东西。本文还有配套的精品资源点击获取