蓝桥杯单片机省赛真题:停车计费系统设计与实现全解析

发布时间:2026/9/3 19:24:23
蓝桥杯单片机省赛真题:停车计费系统设计与实现全解析 简介2021年12届蓝桥杯第一场“停车计费”项目完整工程资源面向嵌入式方向备赛考生及STM32开发者解决停车管理系统中车辆进出、计时计费与状态维护等问题。压缩包共341个文件、约13.81MB以143个H头文件、97个C源码文件为核心配套STM32G4系列HAL库驱动、BSP用户自定义模块及Keil工程配置文件并包含axf/hex等编译输出便于查看或烧录验证。已有472人学习下载。工程内覆盖数据结构设计、时间差计算、计费规则、输入输出处理等关键模块同时涉及GPIO、定时器、串口等常用外设配置帮助读者掌握蓝桥杯嵌入式竞赛的完整开发流程和排错思路。适合备赛复盘、项目参考及嵌入式系统综合实训可有效提升竞赛实战与工程落地能力。 提到蓝桥杯单片机省赛真题2021年第十二届第一场的“停车计费”绝对算得上分水岭级别的题目。它不像单纯点灯、数码管那样只考一个外设而是把DS1302实时时钟、AT24C02掉电存储、独立按键、LCD1602显示、蜂鸣器提示全串在一起模拟一个真实的停车场管理系统。我当时做完这道题最大的感受就是代码本身不难难的是把模块之间的逻辑理顺。很多同学平时单个外设都会用一旦组合到一起就卡壳。这篇文章就围绕这道题从需求拆解、方案选型、代码实现到调试踩坑完整梳理一遍。1. 题目解读与考点分析1.1 停车计费到底要求做什么题目要求在单片机上模拟停车场计费系统具体行为大致是这样车辆入场时按下“入场”按键系统记录当前时间并进入“车在场内”状态车辆出场时按下“出场”按键系统基于当前时间和入场时间计算停车时长再按题目给出的费率结算费用最终通过LCD1602显示车辆状态、当前时间、停车时长和费用同时蜂鸣器给出提示。如果中间发生了断电重新上电后系统还要能恢复断电前的车辆状态和入场时间这就把AT24C02带进来了。从功能清单看这题跟现实中的停车场系统非常贴近核心就三件事记录时间、计算时长、按时长计费。难点并不在算法或者某个外设本身而在“状态管理”——系统里随时要区分现在是“空闲”还是“有车”在不同的状态下按键动作对应完全不同的业务逻辑而所有显示内容也要跟着状态切换。1.2 考点拆解与难度分析我习惯把这类题拆成三层来看基础操作层按键消抖、LCD1602驱动、蜂鸣器控制这属于每个选手必须形成肌肉记忆的内容。进阶模块层DS1302实时时钟的读写、BCD码与十进制互转、AT24C02的I2C通信协议这是拉开差距的地方。综合逻辑层跨天计费如何处理、掉电后如何恢复现场、按键事件如何在不同状态间切换这层才是整道题的灵魂。蓝桥杯单片机比赛用的开发平台是IAP15F2K61S2比传统的STC89C52快不少。很多同学还按老51的思维写一堆长延时结果反而把DS1302的时序搞得不稳定。这题所有模块加在一起本质考察的是你对一个完整单片机系统的整体把控能力而不是某一个外设的熟练度。2. 整体方案设计与外设规划2.1 外设选型与引脚规划以蓝桥杯官方CT107D开发板为基准停车计费用到的外设清单如下外设型号/接口主要作用关键引脚主控IAP15F2K61S2系统控制-实时时钟DS1302提供精准时间SCLK、IO、RST掉电存储AT24C02保存车辆入场状态和时间SCL、SDA显示LCD1602显示状态、时间、费用P0数据口、RS、RW、EN按键S4、S5独立按键模拟入场/出场按开发板原理图提示蜂鸣器操作成功提示音按开发板原理图DS1302的SCLK、IO、RST在CT107D上一般走P1口或开发板指定的跳线位置AT24C02的SCL和SDA通常接P2.0和P2.1。我不建议死记硬背最稳妥的做法是考前花十分钟把原理图上的引脚关系抄成一张自己的速查表比赛时真能省下不少翻原理图的时间。2.2 模块化分层协作逻辑我拿到题目后的第一件事不是写代码而是先画模块关系图思路图不用画出来交卷。整个系统的数据流大致是DS1302作为全局时间源定时读取并刷新显示按键产生事件事件驱动状态机状态机在“空闲”和“有车”之间切换AT24C02只在状态切换的关键节点写入或清除数据。为什么一定要把“时间源”“事件”“状态”分开因为如果写成一锅粥比如在按键中断里直接读DS1302又去写EEPROM很容易出现外设操作时间过长导致按键抖动没消干净、主循环里显示刷新卡顿的问题。模块化之后每个模块只干一件事调试时哪里不对直接到对应模块里找效率会高很多。我当时在考场上就是这么设计的整个代码结构写下来非常顺。3. 核心功能实现与代码要点3.1 DS1302计时与时间读取DS1302是串行实时时钟芯片读出来的时间默认是BCD码格式。比如秒寄存器里存的是0x45实际代表45秒。我习惯直接在工程里定义两个宏#define BCD2DEC(bcd) (((bcd) 4) * 10 ((bcd) 0x0F)) #define DEC2BCD(dec) ((((dec) / 10) 4) | ((dec) % 10))读取流程并不复杂先写地址字节再读数据字节。如果一次要读时分秒可以用连续读模式先读秒、再读分、最后读时DS1302在读操作时会自动把地址递增。注意一点每次读数据前要做一个“虚读”动作也就是先读一个字节丢掉不然连续读取时首字节可能不稳定。DS1302上电后不一定立刻走时必须检查秒寄存器最高位CH如果CH1说明时钟停了。我用的时候会先初始化一次把CH清零顺便把时分秒设成正确时间。如果忽略这一步很可能出现“时间死了”的现象。3.2 跨天计费与向上取整计费的核心是计算停车时长。我的做法是先把入场和出场时间都换算成当天零点起的秒数unsigned long inSeconds inHour * 3600UL inMin * 60 inSeconds; unsigned long outSeconds outHour * 3600UL outMin * 60 outSeconds; if (outSeconds inSeconds) { outSeconds 24 * 3600UL; // 跨天处理 } unsigned long diffSeconds outSeconds - inSeconds;跨天的本质其实是“把出场时间当作第二天时间”只要出场秒数小于入场秒数就加上一整天问题就解决了。这比用日期逐项相减简单可靠得多。接下来是费用结算。题目一般会给出明确费率比如“每分钟1元”有些还附带“不足一分钟按一分钟计费”。如果直接diffSeconds / 60会把不足一分钟的部分丢掉。我推荐用向上取整的方式unsigned int minutes (diffSeconds 59) / 60; unsigned int fee minutes * unitFee;(diffSeconds 59) / 60这个写法在碰到整分钟时不加费非整分钟时多算一分钟正好对应停车场常见计费规则。这类细节看似小却是考场里最容易丢分的地方。3.3 基于状态机的按键控制我建议把系统状态定义成枚举两个状态就够了typedef enum { PARKING_IDLE 0, // 无车 PARKING_OCCUPIED // 有车 } ParkingState;按键S4模拟入场S5模拟出场。核心逻辑if (keyS4Pressed state PARKING_IDLE) { // 入场读当前时间写入AT24C02状态置为有车 ParkingInfo info; info.state 1; getCurrentTime(info.hour, info.min, info.sec); saveParkingInfo(info); state PARKING_OCCUPIED; beep(); } if (keyS5Pressed state PARKING_OCCUPIED) { // 出场读取存储的入场信息和当前时间计算费用 ParkingInfo info loadParkingInfo(); Time now; getCurrentTime(now.hour, now.min, now.sec); calcAndShowFee(info, now); clearParkingInfo(); state PARKING_IDLE; beep(); }用状态机的好处是我永远知道系统当前在什么状态按键按下后该走哪条分支也一目了然。就算连续按了两次入场键因为第一个if条件是state PARKING_IDLE第二次按下也不会触发避免了逻辑混乱。这块是很多人写成一堆if嵌套后把自己绕晕的地方。3.4 AT24C02掉电保存设计车辆入场后如果中途断电重新上电时必须能恢复“车内已有车”的状态。我是这样设计的定义一个结构体把入场状态和入场时间一起打包写入AT24C02。typedef struct { unsigned char state; // 0无车 1有车 unsigned char hour; unsigned char min; unsigned char sec; } ParkingInfo;写入时用I2C连续写读取时按相同结构读回。这里有几个实操点必须注意AT24C02写入后有一个内部写周期大概5ms写完不能马上读否则读出来是旧数据需要在代码里加延时。写入时要把数据地址分成高地址和低地址两部分I2C协议里写设备地址、字地址、数据三步缺一不可。不要在主循环里频繁写EEPROM会拖慢系统还会损耗Flash寿命。正确做法是只在入场和出场这两个状态变更节点操作。掉电恢复的另一个方案是用IAP15F2K61S2内部EEPROM但内部EEPROM按扇区擦写还有磨损问题而且跨扇区操作更麻烦所以我更推荐AT24C02。这个外设通用性强代码也简单练习一次就能牢牢记住。4. 调试过程中的常见问题与排查记录4.1 DS1302时间不走或数据全是0xFF这是我在备考时碰到最多的一个问题。现象要么是LCD上的时间一直不变要么读出来的数据全是0xFF或者0x00。排查思路按序来先看接线SCLK、IO、RST有没有接错或者虚焊这一步最基础却最致命。再检查初始化是否把秒寄存器的CH位清零。DS1302出厂时CH位可能为1此时时钟不走必须先写0。检查读时序是否做了虚读操作。最后确认主频和延时参数IAP15F2K61S2跑得比老51快很多某些地方延时太短会导致时序不满足。实测下来大部分人是倒在了第一步或者第二步。4.2 费用总是比预期少或者多计费异常最常见的原因是向上取整没做。我见过有人用diffSeconds / 60算分钟数实际停车61秒理论按2分钟计费结果算出1分钟费用直接对不上。另一个坑就是费率的单位。题目里可能写“每30分钟收费2元”也可能写“每小时5元”如果没换算成标准单位代码算出来的费用就全乱套。我的做法是在写代码前先把费率写成函数开头的宏定义再在纸上用两组数据手动算一遍确认无误后再往下写。再提醒一个隐藏细节单位不要用int存秒数。停车时间可能超过10小时10小时等于36000秒int在16位单片机上是放不下的一旦溢出就会得到错误结果。我全部用unsigned long来存秒数稳得很。4.3 掉电后车辆信息丢失停车计费这道题里AT24C02的坑主要在写周期。我第一次调试时写完数据立刻去读读出来的还是0xFF一开始还以为是I2C驱动写错了调试了一晚上最后发现是漏了5ms延时。写EEPROM时单片机发完停止信号后EEPROM内部开始真正的写入这段时间内芯片不响应外部指令。如果紧接着发起读操作读出来的自然是旧数据。解决办法就是在stop信号后加DelayXms(10)而不是5ms留足余量。另外还要检查I2C总线的上拉电阻。在CT107D开发板上I2C上拉电阻是板载的但如果你自己搭电路SDA和SCL必须接上拉不然通信必然失败。尽量用示波器或逻辑分析仪抓波形没有的话用软件轮询读ACK状态也能判断。4.4 LCD显示异常与按键误触发LCD1602显示乱码十有八九是初始化时序不对。很多人的驱动把“功能设置”“显示开关”“清屏”这些指令间隔压得太短LCD还没处理完就发了下一条指令直接花屏。我的习惯是在每条LCD命令之间至少留1ms延时必要时在写完后读取忙标志位。按键误触发的本质是抖动。我用的消抖方法是检测到按下后延时10ms再读一次电平确认确认后再等待按键释放。这样做虽然损失了一点响应速度但胜在稳定。如果按下和释放都触发逻辑还容易导致入场后立刻又出场整个状态机就乱套了所以释放检测一定不能省。5. 备赛建议与延伸思考5.1 从停车计费看省赛出题趋势最近几年的省赛题越来越综合越来越喜欢贴近真实应用场景。停车计费这类题目实际就是“时钟芯片存储芯片人机交互”的黄金组合。把这三个模块吃透蓝桥杯单片机省赛的大部分题目都能应付。备考时不要只盯着单个外设去做实验而要主动把多个外设组合起来。比如自己设计一个“电子密码锁”用矩阵按键输入密码、EEPROM存密码、LCD显示状态——这套组合跟停车计费在代码结构上是完全相通的。我备赛期间做过五六个这种综合小项目考试的时候基本没在逻辑架构上费过心思。5.2 功能扩展与进阶方向如果这道题放到国赛大概率会在基础上做减法、加约束。比如把“入场”“出场”改成模拟传感器信号或者增加多地车位管理、空闲车位数量统计、费率可调等功能。做这些扩展时核心思想依然是保持状态清晰、数据独立、模块解耦。对于嵌入式STM32组的选手这套设计思路同样适用。STM32可以用内部RTC或者外部DS3231提供时间用HAL库的I2C驱动EEPROM用CubeMX生成初始化代码但状态机和业务逻辑都不变。蓝桥杯嵌入式题目更强调操作系统思维和代码架构停车计费这类题用来练领域逻辑再合适不过。我个人做完这道题最大的体会是比赛真正考的不是“你会不会写某个驱动”而是“在有限时间内能不能把系统搭得清晰可靠”。停车计费这套代码量并不大但做得快的同学其实都是提前把状态机、时间转换、I2C读写这些基本功练到不需要靠思考就能写出来的人。备赛期间建议把这道题从头到尾手敲三遍用不同费率规则跑三遍测试数据。第一遍把驱动调通第二遍把状态理顺第三遍把代码精简到自己满意的程度。三遍下来下次看到任何“时间存储交互”的真题你都会觉得似曾相识。本文还有配套的精品资源点击获取