
简介本资源是一套基于AT89C52单片机的出租车计价器完整设计与实现方案面向电子类专业本科生、单片机初学者及课程设计实践者解决嵌入式系统中实时时钟管理、动态计费逻辑、掉电数据保存与人机交互等典型工程问题。压缩包共83个文件涵盖Keil源码含608行带注释C程序、Proteus仿真工程12个.pdsprj文件、原理图与PCB资料、开题报告与任务书等文档9个.docx8个.doc、功能演示MP4视频7个、焊接指导与元件清单等实操支撑材料整体大小为80.22MB。已有107人学习下载资源突出实用性与教学完整性提供白天/夜晚双模式计价、DS1302实时时钟校准、AT24C02掉电存储起价参数、LCD实时显示与8按键交互逻辑并配套仿真运行指南、常见报错解决方案及免费PPT模板显著降低入门门槛与调试成本。 做51实训那会儿很多人第一个念头就是“做个出租车计价器”。东西听起来实用功能也直观跑多少里程、该收多少钱、单价怎么调全是现实中能对上的逻辑。但也正因为贴近真实场景这题目比跑马灯、电子钟要磨人不少——它得同时处理测速、计时、计费、按键、显示和掉电保存等于一个小型嵌入式系统该有的模块全都占了。如果你正在准备课程设计或者实验室验收想拿个像样的作品这篇文章能帮你在AT89C52上把这套计价器从方案一路做到实测。我先说下这个项目的基本构成核心是AT89C52单片机用霍尔传感器或者光电码盘采集车轮转速通过计数器换算成里程再用定时器算时间和等待计费最终在LCD1602上显示金额、里程、单价这些信息。老规矩关键数据靠AT24C02存下来断电不丢。这个系统能做什么简单说就是模拟真实计价器的工作流上电待机、行车计价、暂停等待、到达结算。适合谁电子类专业做单片机课设、准备电子设计竞赛练手或者纯粹想弄明白“单片机怎么处理真实世界信号”的朋友都值得搞一遍。整个项目做下来你的收获不只是跑通了几个模块而是能把“传感器输入 实时计算 人机交互 数据存储”这条链路完整串起来。很多学生在这上面卡住不是某个模块单独不会而是不知道模块之间怎么配合——传感器信号进单片机后要消抖中断计数的结果要换算成里程里程数又要和定时器的时长一起决定金额这一串逻辑理不顺代码就是一锅粥。这篇文章我就从方案选型、硬件搭建、软件逻辑、调试排障四个方向把AT89C52出租车计价器的完整设计思路和实操喂给你。中间有可复用的代码思路也有我实测踩过的坑。1. 系统整体设计与方案选型1.1 功能需求拆解先别急着画电路我们把计价器要做的事一条条列出来后面每一步都对着需求来。一个典型的出租车计价器功能其实就四块测速测距实时检测车轮转了多少圈换算成行驶里程顺带算出当前车速。计费逻辑起步价 超出部分按每公里单价累加堵车或者临时停车时要按等待时间额外计费行驶状态和停止状态要能准确切换。数据显示总金额、总里程、单价、等待时间这些信息得实时刷新到屏幕上司机和乘客都要能看懂。参数设置与存储起步价、每公里单价不是写死就完事的得能按键调整而且调完断电重开不能丢。为了做“掉电不丢数据”我加了一片AT24C02做EEPROM存储。至于车速计算本质上是“单位时间内采集到的脉冲个数”这个逻辑放到后面软件部分细说。有些同学做设计时会往里面塞一堆花哨功能什么超速报警、夜间自动切换单价、打印小票不是不能做但要分清主次。课程设计最忌讳的是功能表拉得很长结果核心计费逻辑都没跑稳。我建议第一版先把上面四项做扎实扩展功能等整机联调通过后再考虑。1.2 为什么选AT89C52做主控现在单片机市场早就是ARM和RISC-V的天下了STC、STM32、ESP32随便挑一个性能都比AT89C52强。但在这个项目里AT89C52恰恰是最合适的。原因很直接计价器要的东西它全有。AT89C52属于MCS-51内核带8KB Flash程序存储、256B内部RAM、3个16位定时器/计数器实际用起来2个足够、1个全双工串口还有6个中断源。做计价器这种量级的应用资源刚好够用不会因为性能不足卡脖子。更重要的是上手门槛。51单片机的资料是全网上最多的随便搜一下就是一堆例程。对于课设周期只有几周的学生来说花大量时间啃STM32的时钟树和寄存器配置不如先专心调通业务逻辑。AT89C52的Keil开发环境配起来也简单烧录用STC-ISP一类工具就能解决不用专门的仿真器。另外提一句AT89C52和AT89C51、AT89S52这些型号引脚基本兼容程序改动量很小。如果你手头有的是STC89C52也不是问题后续代码稍微改一下下载配置就行核心逻辑一字不用动。1.3 测速方案对比霍尔、光电还是干簧管计价器的里程精度靠轮速采集打底传感器选型不能含糊。市面上常见的几种方案我对比过方案原理优点缺点是否推荐霍尔传感器 磁钢磁钢随车轮转动霍尔IC检测磁场变化输出脉冲抗振动、成本低、安装方便磁钢安装要稳固距离要调推荐最常用光电码盘码盘转动遮挡红外光输出脉冲精度高、无接触怕脏污、安装对位要求高可用但环境要求高干簧管 磁钢磁钢靠近时干簧管触点闭合电路最简单触点有机械寿命、会抖动不推荐实际做的话我推荐霍尔IC比如A44E或者AH3144这类开关型霍尔元件。它在轮轴固定位置贴一个磁钢车轮每转一圈霍尔传感器就翻转一次输出正好对应一个脉冲。配合单片机的计数器或者外部中断脉冲数就能直接和里程对上。这里有个细节值得多嘴一句磁钢极性要选对霍尔元件的“感应面”要正对磁钢安装间隙控制在3~8mm之间比较稳。装太远了感应不到脉冲装太近了容易机械干涉。我见过好几个人做到后面发现里程数只有实际的一半排除下来就是磁钢松了导致偶发漏检。如果不想自己钻孔装磁钢也有现成的霍尔转速传感器模块卖三根线VCC、GND、OUT直接接单片机省去不少机械加工的麻烦。做课设拿来演示完全够用。2. 硬件电路搭建与模块设计2.1 最小系统晶振、复位、电源AT89C52要跑起来最小系统这块必须搭扎实别在基础上栽跟头。首先是晶振。我用的是12MHz无源晶振配合两个22pF~30pF的负载电容接到晶振两脚电容另一端接地。选12MHz的好处在于51单片机的机器周期是12个时钟周期12MHz晶振下一条机器周期正好是1μs定时器算时间特别方便。比如后面要做50ms定时中断初值算出来是整齐的数字调试时心算都不容易出错。如果你只有11.0592MHz的晶振也不是不能用只是它主要为了串口波特率精确这里用不上串口反而12MHz更方便。复位电路有两种常见画法我推荐最经典的上电自动复位10μF电解电容接到VCC和RST引脚之间RST再通过10kΩ电阻接地。上电瞬间电容充电RST引脚短暂维持高电平单片机完成复位后回到低电平正常运行。同时可以预留一个手动复位按键方便调试时随时重启程序。这类设计虽然简单但稳定性直接影响后续所有功能的调试。电源部分AT89C52工作电压是5V。想省事就直接用USB供电但最好在电源入口加一个100μF电解电容滤波同时在VCC和GND之间并联一个0.1μF陶瓷去耦电容而且这个电容要尽量靠近单片机引脚摆放。很多不明原因复位、显示花屏的问题根源就是电源纹波太大几个电容就能解决。2.2 轮速采集电路霍尔传感器与信号调理霍尔传感器模块的输出是集电极开路型这就意味着上拉电阻是少不了的。我一般接一个4.7kΩ~10kΩ的上拉电阻到VCC不接的话输出高电平状态不稳定单片机可能把噪声误判成脉冲。完整的接法是这样VCC接5VGND接GND。OUT引脚通过10kΩ电阻上拉到VCC同时直接连到单片机的P3.2或P3.3引脚这两个引脚对应外部中断INT0和INT1后面测量脉冲数要用。这里强烈建议在OUT和GND之间加一个0.1μF的电容做滤波。霍尔传感器本身反应快但车轮转动时磁钢靠近、离开的过程会产生边沿毛刺不加滤波的话单片机可能一次物理转动计出好几个脉冲直接导致里程虚高。即使加了硬件滤波软件里最好也做一次小的延时或者连续采样确认双保险。如果你手头的霍尔模块板上已经带了上拉电阻和滤波电容那直接三线接单片机就行省事不少。但正规做设计原理图里这些元器件得画出来别只靠模块。至于要不要加施密特触发器整形我觉得多数场景不需要。霍尔IC输出本身是数字信号不是那种缓慢变化的模拟波形STC89C52的IO口输入电平就可以直接识别。只有当你用普通转速电机产生正弦波或者导线拉得特别长超过30cm时才需要考虑加74HC14整形。2.3 显示与按键LCD1602的4线模式显示部分我选了LCD1602理由就是它便宜、成熟、资料多而且能显示数字和字母足够展示“金额、里程、单价、等待时间”这些信息。LCD1602的数据接口有两种模式8线和4线。我要重点推荐4线模式。这两种模式读起来各有优劣但4线模式只占单片机的6个IO口RS、RW、EN 4根数据线D4~D7能给其他模块按键、EEPROM留出空间。8线模式接线虽然更直观但在AT89C52这种IO资源本来就紧张的单片机上能省则省。4线模式的具体接线就按下面这组来RS接P1.0RW接P1.1EN接P1.2D4~D7接P1.4~P1.7RW引脚通常直接接地就行液晶只写不读这样又能省一个IO。屏幕第3脚是LED背光控制串一个10Ω~100Ω的限流电阻到VCC。最关键的是第1脚的对比度调节接一个10kΩ电位器中间抽头接到Vo引脚。很多人做LCD1602显示“全黑方块”或者“隐隐约约看不清字”十有八九就是对对比度没调好电压应该在1V左右最清楚。按键方面我用三个独立按键分别负责“功能切换”、“数值加”和“数值减”。三个按键一端接P3.0~P3.2对应的IO口另一端接地同时在IO口和VCC之间接上拉电阻。程序里检测到引脚为低电平就说明按下去了。考虑抗干扰我还会加上消抖处理一般延时10~20ms再判断一次。这种做法写起来简单实测也够稳。2.4 掉电存储与声音提示既然要模拟真实计价器断电不能丢数据这一点就得认真对待。AT24C02作为一块2Kbit的EEPROM容量是256字节存起步价、单价、累计金额这些参数绰绰有余。AT24C02走I2C协议SDA和SCL两根线分别接单片机的P2.0和P2.1两根线都要接上拉电阻到VCC典型值4.7kΩ。有人会问I2C上拉电阻选大选小有区别吗有电阻太小功耗大电阻太大信号边沿变缓影响通信稳定性4.7kΩ在5V系统里是比较保险的中间值。声音提示用一个蜂鸣器就够。无源蜂鸣器需要单片机输出一定频率的方波才会响有源蜂鸣器给高电平就会响。计价器这种场景我建议用有源蜂鸣器控制逻辑简单直接在P2.2引脚输出高电平就能驱动。不过蜂鸣器工作电流不小不能直接接单片机IO口得用一个NPN三极管比如S8050做开关驱动。三极管基极串一个1kΩ电阻接P2.2集电极接蜂鸣器负极蜂鸣器正极接VCC发射极接地这样IO口只需要提供很小的基极电流就能控制蜂鸣器通断。到这里整个硬件框架就齐了。你会发现这套电路并没有追求多复杂的技术但它覆盖了一个完整系统的所有功能模块每一块都有它存在的意义。3. 软件设计思路与核心逻辑实现3.1 主程序状态机框架硬件搭好之后真正的重头戏在软件。如果写成一坨大循环加各种if嵌套后面改功能和查bug都会让人崩溃。我用了一个简单的状态机来组织整个程序流程这是我认为整个代码框架中最值得分享的设计。计价器的工作状态我划分为四态待机态IDLE上电或者刚结算完屏幕显示当前单价和总金额等待司机按键开始。运行态RUN行驶或等待中实时累计里程和金额。暂停态PAUSE中途有人下车、或者司机临时停表停止计程计费。结算态SETTLE到达目的地显示最终金额此时可以按键调价或者清零。主程序就是一个while(1)循环不断扫描当前状态再根据按键事件和定时器标志位做状态跳转。写成状态机的最大好处是每个功能模块的入口和出口非常清晰新增需求比如加夜间模式只需要加一个状态分支不需要把旧代码推倒重来。伪代码结构大概是这样用C语言思路描述void main() { System_Init(); // 定时器、中断、LCD、I2C初始化 while (1) { Key_Scan(); // 按键扫描 if (system_state RUN) { Calc_Mileage(); // 里程累计 Calc_Fee(); // 金额计算 } Display_Update(); // 刷新LCD显示 } }配合定时器中断去做时间和速度相关的工作。状态机里的状态切换则是靠按键触发的每个状态下的显示内容也是独立刷新。3.2 定时器和计数器测速计时的核心计价器最核心的两个数据来源一是时间二是脉冲数这两个都靠单片机内部的定时器/计数器来搞定。AT89C52有3个定时器T0、T1和T2T2在52里也有但用起来比T0、T1麻烦一些。我习惯用T0做定时中断T1做计数器。T0我配置成模式116位定时器配合12MHz晶振做50ms中断。50ms的初值计算方法是每个机器周期1μs定时50ms需要50000个机器周期而16位定时器最大计数值是65536所以初值 65536 - 50000 15536换成十六进制就是0x3CB0所以TH0赋0x3C、TL0赋0xB0。每次进入中断我都对中断次数变量加1累计20次正好是1秒。这样秒数就有了等待计费、车速计算都从这个秒基准延伸。代码初始化的骨架void Timer0_Init() { TMOD | 0x01; // T0工作在定时模式1 TH0 0x3C; TL0 0xB0; ET0 1; // 开T0中断 EA 1; // 开总中断 TR0 1; // 启动T0 } void Timer0_ISR() interrupt 1 { TH0 0x3C; // 重新赋初值 TL0 0xB0; tick_count; if (tick_count 20) { tick_count 0; second_flag 1; // 每秒置一次标志供主循环处理 } }T1则作为计数器使用。霍尔传感器的脉冲输出接到P3.5引脚也就是T1的计数输入引脚。T1配置成计数模式自动对引脚上的下降沿或上升沿计数。计数器溢出时触发中断我在中断里把脉冲总数加到全局变量里。也有人图省事把脉冲接到外部中断INT0上然后在中断服务函数里自己计数这两种方案都行但用计数器有个好处计数过程由硬件完成不会因为中断服务程序执行时间长而丢失脉冲。实际代码里脉冲计数的关键是“读快照”。不要在需要的时候直接读TH1和TL1的值因为主循环可能正好碰上计数器进位高低字节不一致。稳妥做法是先读TH1再读TL1然后再读一次TH1如果两次TH1不一样就重新读。这点是我在调试中发现真正有用的经验否则里程计算会出现偶发的跳变。3.3 里程与金额计算轮径换算的细节脉冲数只是原始数据要变成里程还得做一道乘除法。车轮每转一圈霍尔传感器产生1个脉冲那么一路走的距离就是轮胎周长。轮胎周长 π × 轮径。出租车的轮径一般按0.6米算那周长就是3.14 × 0.6 1.884米。也就是说脉冲数 × 1.884米就等于脉冲对应的总路程。如果检测的是轮胎内侧传动轴上的码盘码盘一圈可能产生多个脉冲那就按“每个脉冲对应的距离 周长 / 每圈脉冲数”来算。这块的换算系数建议在实测时再校准一遍后面调试章节我会展开说。里程到金额的计算我采用分段计费起步价3公里以内收起步价假设7元。超起步里程每公里单价假设2.4元。等待计费车速为零但计价器仍处于运行状态时按等待时间累计费用比如每分钟加0.5元。需要注意的是出租车计价器里的里程单位换算不能直接用浮点数做累加因为单片机做浮点运算慢而且内存占用大。我建议用整数计算里程精确到0.1km费用精确到0.1元全部用整数存储和运算。比如把“元”乘以10存成“角”来算显示的时候再除以10。这样规避了浮点误差也让代码在8位单片机上跑起来更流畅。实际计算时有个容易踩的坑起步价判断要基于“累计里程”而不是每秒的瞬时里程。我用一个total_mile变量没跑满3公里就是起步价状态超过之后才按里程加钱。这个逻辑如果写错会出现乘客还没坐几百米就开始跳表那笑话就闹大了。3.4 掉电存储的写入时机AT24C02放这片就是为了断电不丢数据。关键问题是什么时候写写太频繁EEPROM的擦写寿命是个问题AT24C02的擦写寿命通常在10万次左右写太少断电时可能丢的就是最近的数据。我的做法是每次里程增加0.1km、或者金额变化超过0.1元时就把当前的总里程、当前金额、单价这几个核心参数写入AT24C02。因为这几个值的更新频率不高通常几百毫秒才变一次一天跑下来写入次数不会太多寿命完全不用担心。AT24C02的写入要严格按住I2C时序来。一个需要注意的细节是AT24C02写完一个字节后需要一个自定时周期大概5ms才能写下一个字节程序里必须加上这个延时或者查询ACK。很多人第一次调I2C写完字节没等待下一字节写进去全是乱的显示出来的数据像天书一样。3.5 LCD显示界面规划LCD1602只有两行每行16个字符显示内容得精心安排。我是这么规划的第一行显示“里程”和“金额”格式是“M:0012.3km F:008.0”第二行显示“单价”和“状态”格式是“P:2.4 RUN T:00:00”。“等待时间”不常看平时第二行就显示状态等待时再切出来。切换显示也靠按键。因为LCD1602没有字库要显示小数点就得自己定义字符或者在代码里把数字拆成整数部分和小数部分分别显示。我选择后者直接输出“%.1f”这种方式在51上是不可靠的因为Keil C51对浮点printf支持得很差占资源又多。正确的姿势是自己写数字转换函数把整数变量按照个十百拆位再一位一位显示。这样代码清晰显示也稳定。4. 调试心得与常见问题排查4.1 霍尔信号乱跳里程数虚高到底是谁的锅这是我调试时遇到最多的问题。轮胎没转几圈里程数却蹭蹭往上涨。排查方向有三个检查霍尔传感器供电和上拉是否正常。示波器或者万用表量一下OUT引脚看有没有正常的高低电平翻转。检查信号线上有没有滤波电容。没加的话用示波器看波形会在边沿处看到一堆毛刺这些毛刺全被单片机当成脉冲了。检查轮轴连接是否牢固。磁钢松了霍尔传感器可能一秒钟感应到好几次或者在临界位置反复触发。排除完硬件后还有可能是程序问题。如果用的是外部中断计数中断服务函数里消耗时间太长也会导致在脉冲边沿抖动时来不及响应多计或少计。用计数器模式后这个坑基本就没了。4.2 LCD1602不显示、乱码、满屏方块LCD1602的问题我自己就折腾了一个下午最常见的情况是满屏方块对比度没调好。把电位器从一端旋到另一端找到一个字迹最清晰的位置。只有第一行有字初始化时序没走对尤其4线模式下要先发功能设置命令然后延时再发后续命令。顺序反了就会出现只初始化一半的诡异现象。显示乱码RS、RW、EN的接线搞混了。用万用表逐个确认一下IO口和引脚对应关系。背光不亮检查背光限流电阻和背光供电引脚别把第15、16脚忘了。另外有一点LCD1602上电之后要等一段时间大概100ms以上再发送初始化指令这个延时不能省。很多同学代码是从网上抄的例程例程里可能用了delay(15)这种整数延时但实际单片机主频不同延时不达标LCM内部还没初始化好指令就丢了。4.3 掉电保存失效重新上电数据全没了这个问题通常出在I2C通信上。排查时先检查SDA和SCL的上拉电阻有没有接没上拉的话I2C总线根本拉不高通信必然失败。再检查写时序。写一个字节到AT24C02后要等待自定时周期结束程序里如果没有加延时或者等待ACK就会连续写入导致失败。我踩过最隐蔽的坑是单步调试时数据能存进去全速运行时存不进去。原因就是单步调试时每一步之间有足够延时全速运行时写完一个字节紧接着写下一个芯片还没准备好数据覆盖了。解决办法很简单在每次写字节之间加一个5~10ms的延时万事大吉。4.4 行车过程中按键误触、系统复位这个问题多出现在物理振动大的场合。按键没做消抖处理或者电源不稳导致单片机复位。按键消抖我前面已经提过延时10~20ms再确认一次。电源方面如果电机或者蜂鸣器工作时电流变化剧烈会给VCC引入很大的纹波单片机工作电压跌到4.5V以下就可能复位。解决办法是在电源入口加一个100μF以上的储能电容蜂鸣器和单片机分开供电或者至少共用电源时在蜂鸣器两端并联一个反向二极管吸收浪涌。4.5 里程校准实战用实测数据修正换算系数最后分享一个我实测出来很好用的校准方法。上理论班人人都知道轮径乘π但轮胎气压不同、轮胎磨损不同实际直径和理论值一定有偏差。要跑出高精度里程必须实测校准。具体方法找一段没有人的平直路面用卷尺量出100米推着车或者把轮子架空走完这段距离同时记录单片机测出来的里程数。如果实际走了100米单片机上显示只有95米那就说明换算系数偏小了用真实里程除以显示里程得到一个修正系数再乘到原来的换算系数里。反复测两三次精度很容易做到99%以上。计算公式修正后的每个脉冲对应距离 理论周长 × (实际距离 / 显示距离)这样做虽然朴素但比查轮胎参数表靠谱得多也是工程上常用的标定思路。调试到这一步整套系统的功能就基本完整了。最后我再补充两句个人体会这门课设做下来最值钱的不是代码能不能跑而是你学会了一种“拆问题”的方式——先拆功能再选硬件然后写软件最后用调试去修正。顺序对了进度就快。真卡住了也别硬憋拿万用表和示波器把信号量一遍问题通常就浮出水面了。本文还有配套的精品资源点击获取