简介:基于51单片机的出租车计价器课程设计资料包,面向电子工程、嵌入式方向的初学者及高校学生,解决课程设计与实训中从硬件搭建到计费逻辑实现的完整流程问题。资源共46个文件,压缩包约1.62MB,以C语言源码、Keil工程文件、Proteus仿真工程、原理图及hex烧录文件为主,另有obj、lst等编译中间文件与调试记录,覆盖开发、仿真、烧录各环节。程序模块包含按键输入、LCD显示、EEPROM参数存储、DS1302时钟驱动等,计费规则与中断逻辑清晰可读;原理图详细展示了单片机、传感器、显示模块之间的连接,便于对照PCB布局或实物调试。已有1505人学习下载。通过该案例可掌握51单片机外设驱动、定时器/计数器应用及Proteus虚拟仿真排错技巧,适合作为课程设计参考或嵌入式入门实践项目。
1. 出租车计价器课程设计:51单片机必做项目的完整资源与落地思路
51单片机的课程设计选题里,出租车计价器几乎和电子钟、蓝牙小车并列前三,它把外中断计数、定时器时基、按键消抖、状态机切换和液晶显示全部压进一个巴掌大的系统,做完这一套,MCU入门阶段的核心外设基本就通了大半。这份基于51单片机的出租车计价器设计资源,打包了Keil源码工程、Proteus仿真图、电路原理图和设计报告,适合正在赶课程设计的学生,也适合想完整复现一遍“脉冲计数→里程计算→费用显示”这条链路、顺带补一补原理细节的自学者。它要回答的问题很具体:里程怎么数得准,费用怎么算得清,按键和显示怎么配合不打架。
2. 硬件方案选型与引脚规划:计价器骨架怎么搭才不返工
2.1 功能需求拆解:计价器到底要算哪几笔账
出租车计价器看着简单,真把需求拆开,至少要处理六个面板:起步价、超起步里程单价、等待费、单程往返加价、单价调整和复位。课程设计里最常见的计价规则是“起步价8元含3公里,超出后每公里2.4元,等待每5分钟折算1公里”,具体参数如下表。
| 计费项目 | 常见设定 | 说明 |
|---|---|---|
| 起步价 | 8.00元 | 含前3公里 |
| 超起步里程 | 2.40元/公里 | 超过3公里后按里程累加 |
| 等待费 | 每5分钟折算1公里 | 车速过低或按下暂停键时累计 |
| 单程/往返 | 可选 | 超出某里程后单价上浮,课程设计可不做 |
| 单价调整 | 长按设置键进入 | 通过按键步进修改,存储到EEPROM |
这一步别急着写代码。把计费规则先画成“里程在哪个区间、费用按什么公式涨、等待状态是否叠加”,后面写状态机才不至于东补一块西补一块。课程设计评分通常盯着三处:里程累计准不准、金额计算对不对、按键操作稳不稳,规则表就是这三条评分的依据。
2.2 核心器件选型:用STC89C52加霍尔传感器的理由
主控选STC89C52或AT89S52就够了,理由很直白:8KB Flash能装下完整的计价程序还剩富余,两个外部中断(INT0/INT1)一个数脉冲、一个留作备用,三个定时器里拿一个做50ms时基、一个做脉冲测速,资源完全不紧张。这类资源包里默认就是这个级别的芯片,换更强的单片机反而要改引脚和初始化,得不偿失。
里程传感器常见三种方案:霍尔开关、干簧管和光电对管。霍尔元件(典型型号A3144)是无触点输出,抗震动、响应快,磁钢贴车轮每转一圈输出一个脉冲,这是课程设计里最稳的做法;干簧管便宜但颠簸路段容易误触发,光电对管在户外强光下需要遮光处理。这套资源按霍尔传感器来写程序,仿真里用脉冲发生器或手动开关模拟霍尔输出,实物移植时直接对号入座。
显示部分的选择会影响整个程序结构。LCD1602能直接显示“3.25KM 8.60RMB”这种带单位的字符,演示效果好、报告截图直观,缺点是占用的I/O多;数码管动态扫描更贴近真车计价器,但消隐处理和位选时序会多出一堆坑。这套资源用LCD1602做主显示,数码管版本留作扩展,后面我会讲两者的切换思路。
2.3 电路连接与引脚分配:每根线都要有明确用途
引脚分配直接决定程序里寄存器操作怎么写,下面这张表是这套设计里最常用的接线方式,和资源包里的原理图能对上。
| 信号 | 接MCU引脚 | 电平逻辑 | 备注 |
|---|---|---|---|
| 霍尔脉冲输出 | P3.2 / INT0 | 下降沿触发 | 需要10k上拉电阻 |
| 启动/停止键 | P1.0 | 按下为低 | 同一个键,按状态切换 |
| 暂停/等待键 | P1.1 | 按下为低 | 和启动键互斥处理 |
| 功能/设置键 | P1.2 | 按下为低 | 长按进入单价调整 |
| LCD1602 RS | P2.5 | 高=数据,低=命令 | 接单片机普通I/O |
| LCD1602 RW | P2.6 | 接地只写即可 | 省一个控制位 |
| LCD1602 EN | P2.7 | 下降沿锁存 | 使能信号 |
| LCD1602 D0-D7 | P0.0-P0.7 | 数据总线 | 必须接上拉排阻 |
两个容易翻车的细节在这里提前说:P0口是开漏输出,不加上拉电阻LCD数据线电平就会飘,仿真里可能看不出问题,实物一定花屏;霍尔传感器的输出端也要接10k上拉到VCC,否则脉冲高电平不确定,INT0下降沿触发会漏记或者乱记。复位电路用经典的10uF电容加10k电阻,上电延时约100ms,足够让单片机稳定启动。
2.4 脉冲当量计算:计价准不准全看这一处
里程计费精度的源头是“一个脉冲对应多少米”,这个参数算错,后面所有显示和金额都是白搭。常见做法是:设车轮半径0.31米,周长约1.95米,每圈一个磁钢对应一个脉冲,那么一个脉冲就是约1.95米。课程设计演示时为了好算,经常简化成1脉冲等于0.1米或0.125米,仿真里点几下开关就能看到里程明显变化,也方便核对。
// 脉冲当量换算:轮胎周长除以每圈脉冲数 #define TIRE_RADIUS_M 0.31f // 车轮半径,单位米 #define TIRE_PERIMETER_M (6.28318f * TIRE_RADIUS_M) // 周长 #define PULSE_PER_TURN 1 // 每圈脉冲数(磁钢个数) #define PULSE_TO_METER (TIRE_PERIMETER_M / PULSE_PER_TURN)参数说明:TIRE_RADIUS_M按实际轮胎标注修改,PULSE_PER_TURN是车轮转一圈霍尔输出的脉冲个数,磁钢贴得多就填几。实物标定更准的办法是推车走一段已知距离,数出总脉冲数,再反推单个脉冲当量,这比查轮胎规格更可靠,因为胎压和负重都会改变实际周长。整个程序中只有这一处宏定义控制里程精度,后期标定只改它,别在多个文件里散着写硬编码。
3. 软件核心实现:里程计数、计价算法与显示刷新
3.1 用INT0外部中断做里程计数:为什么不用查询方式
里程脉冲是异步事件,行驶中霍尔输出的频率可能到几百赫兹甚至更高,如果用主循环轮询查询电平变化,程序在显示刷新或按键扫描时就会丢脉冲。外部中断方案把计数放到硬件级触发里,每个下降沿自动进中断,丢失概率大幅下降,这是所有计价器程序的共识。
// INT0外部中断初始化:下降沿触发 void int0_init(void) { IT0 = 1; // 1=下降沿触发,0=低电平触发 EX0 = 1; // 使能INT0中断 EA = 1; // 开总中断 } volatile unsigned long pulse_count = 0; // 里程脉冲累计 // 中断服务函数:只做计数,不做其他运算 void int0_isr(void) interrupt 0 { pulse_count++; }逻辑说明:IT0=1把INT0配置为下降沿触发,霍尔输出从高到低的那一瞬间进入中断;ISR里只做pulse_count++,不计算里程不刷新显示,这样中断服务时间极短,主循环不会被拖累。参数说明:pulse_count用volatile unsigned long,因为它在中断和主循环之间共享,不加volatile的话编译器可能把它优化进寄存器,导致主循环读到的不是最新值。32位无符号数可以累计到几十亿个脉冲,对课程设计场景完全不会溢出。
3.2 定时器0搭建50ms时基:时间基准怎么算才不偏
计价器里所有跟时间相关的功能——等待费计时、开机时间、显示刷新频率——都靠一个稳定的时基。最常见做法是让定时器0工作在模式1(16位计数),每50ms中断一次,中断里累计tick_50ms,再分频出秒和分钟。
void timer0_init(void) { TMOD = 0x01; // 定时器0,模式1(16位定时) TH0 = 0x3C; // 12MHz晶振下50ms初值的高8位 TL0 = 0xB0; // 低8位 ET0 = 1; // 使能定时器0中断 TR0 = 1; // 启动定时器 } void timer0_isr(void) interrupt 1 { TH0 = 0x3C; // 重装初值,保持50ms周期 TL0 = 0xB0; tick_50ms++; if (tick_50ms >= 20) { tick_50ms = 0; second_count++; } }参数说明:12MHz晶振的机器周期是1us(12分频),50ms需要计数50000次,定时器初值等于65536减去50000即15536,换算成十六进制是0x3CB0,所以TH0=0x3C、TL0=0xB0。如果换成11.0592MHz晶振,机器周期约1.085us,同一初值走出来的时间会偏短,必须重算。这也是很多模板宁可用12MHz的原因——串口通信用不到的时候,12MHz的定时器初值最好算。
3.3 计价状态机:待机、运行、等待、结束四态怎么切
计价器不能是“一个主循环从头跑到底”的写法,必须用状态机把不同工况隔开:待机时只显示单价,运行时里程和金额联动,等待时只累计等待时间并把每5分钟折算成1公里费用,结束时冻结金额等复位。每个状态下按键的含义也不同,这就需要一个清晰的状态转移表。
typedef enum { STATE_IDLE, // 待机:显示单价,等待启动 STATE_RUN, // 运行:里程累计+超起步价计算 STATE_WAIT, // 等待:等待时间累计,每5分钟按1公里计 STATE_STOP // 结束:金额冻结,等待复位 } MeterState; MeterState meter_state = STATE_IDLE; void meter_proc(unsigned char key) { switch (meter_state) { case STATE_IDLE: if (key == KEY_START) meter_state = STATE_RUN; break; case STATE_RUN: if (key == KEY_WAIT) meter_state = STATE_WAIT; else if (key == KEY_STOP) meter_state = STATE_STOP; break; case STATE_WAIT: if (key == KEY_WAIT) meter_state = STATE_RUN; break; case STATE_STOP: if (key == KEY_RESET) { pulse_count = 0; // 清零里程脉冲 distance_m = 0; // 清零累计里程 total_fee_c = 0; // 清零金额 meter_state = STATE_IDLE; } break; } }逻辑说明:状态机的好处是每个按键在每个状态下只有唯一含义,比如KEY_START在IDLE态是启动,在RUN态就会被忽略或定义为别的功能,不会出现“按一下复位结果金额还在涨”的混乱现场。参数说明:复位操作在STOP态内完成,清掉三个累计量再回到IDLE,而不是直接跳转,这样确保金额冻结期间乘客看到的数字不会被意外改动。
3.4 费用计算:用整数“分”运算,避开浮点的坑
Keil C51的默认int是16位,最大值32767,如果用“元”做单位,8.00和2.40这种小数直接算浮点会引入库函数开销,还容易在乘除过程中溢出。常见做法是全部用“分”做最小单位,8.00元存成800,2.4元存成240,显示的时候再除以100拆出元和角分。
#define BASE_DIST_M 3000 // 起步里程3公里,单位米 #define BASE_FEE 800 // 起步价8.00元,单位分 #define PRICE_PER_KM 240 // 每公里2.4元,单位分 unsigned long distance_m = 0; unsigned int total_fee_c = 0; void fee_update(void) { if (distance_m <= BASE_DIST_M) { total_fee_c = BASE_FEE; // 起步价内不额外收费 } else { // 超出部分按每公里单价累加,结果仍以分为单位 total_fee_c = BASE_FEE + (unsigned long)(distance_m - BASE_DIST_M) * PRICE_PER_KM / 1000; } }逻辑说明:先判断里程是否在起步区间内,是就直接给起步价;否则用超出的米数乘以单价再除以1000,把米换算成公里,结果加到起步价上。参数说明:(distance_m - BASE_DIST_M) * PRICE_PER_KM这一句先乘后除,中间结果可能超过65535,所以前面加了(unsigned long)强转,防止16位溢出;最终结果total_fee_c用unsigned int能存到65535分即655元,一趟出租车行程绝对够用。
3.5 LCD1602显示刷新:主循环里分配好刷新节奏
显示刷新不能放在中断里,否则会拖慢脉冲计数和定时精度。常规做法是在主循环里用tick_50ms做个简单的分频,大约每100ms刷新一次液晶,既能看到里程和金额的变化,又不会因为刷新太快让液晶模块反应不过来。
void lcd_display_update(void) { char buf[17]; unsigned int km_int = distance_m / 1000; // 取整公里 unsigned int km_dec = (distance_m % 1000) / 10; // 取小数后两位 sprintf(buf, "DIS:%d.%02dKM", km_int, km_dec); lcd_write_string(0, 0, buf); sprintf(buf, "FEE:%d.%02dRMB", total_fee_c / 100, total_fee_c % 100); lcd_write_string(1, 0, buf); } void main_loop(void) { unsigned char tick_flag = 0; while (1) { key = key_scan(); meter_proc(key); fee_update(); // 100ms刷新一次显示,避免液晶忙不过来 if (tick_50ms >= 2) { tick_50ms = 0; lcd_display_update(); } } }参数说明:sprintf是偷懒但可靠的做法,会把浮点和字符串拼接都处理好,代价是引入printf类库导致代码体积增加几百字节,8KB Flash完全扛得住。如果Flash紧张,就改成自己拆数字加字符表:定义一个seg[]数组,用除法和取模逐位取数字,再查表拼字符串,代码量更小但可读性差一些。两种方式资源包里一般都有注释,建议先跑通sprintf版本再考虑优化。
4. Proteus仿真与Keil联调:把实物风险前置到电脑里
4.1 Proteus仿真电路搭建要点
拿到资源包先别急着看代码,第一步是在Proteus里把电路图完整搭出来。要点有四个:单片机选AT89C52,晶振12MHz配两个20pF到30pF的电容;P0口接LCD1602数据线,同时接一组上拉排阻;P3.2接霍尔脉冲源,仿真里用开关或者脉冲发生器模拟;复位电路用10uF电容加10k电阻,上电时保证RST脚有一个足够宽的高电平脉冲。
搭电路时最容易漏的是LCD1602的对比度引脚V0,仿真里不接电位器可能会导致显示乱码。做法是取一个10k电位器,中间抽头接V0,两端分别接VCC和GND,调节到屏幕上出现清晰的黑块字符。霍尔脉冲模拟部分,我一般习惯用Proteus信号发生器里的方波模式,频率设到10Hz到50Hz,就能模拟车轮低速和快速两种状态,比手动点开关更能看出计数稳定性。
4.2 Keil工程配置与编译
代码工程用Keil打开,文件结构通常是main.c放主循环和状态机,lcd1602.c放显示驱动,i2c.c放24C02读写,头文件里定义引脚和参数宏。新建工程时芯片型号选Atmel AT89C52,然后在Options for Target的Output选项卡里勾选Create HEX File,编译后才会在工程目录生成要烧进Proteus的.hex文件。
编译时如果报error C141: syntax error,先看是不是少写了分号或者花括号不匹配;报error C267: identifier expected,多半是变量名打错了或者头文件没包含。这类课程设计工程最常见的问题是main.c里重复定义了pulse_count——它在int0_isr和自己写的初始化函数里各定义了一次,链接时报L104: MULTIPLE PUBLIC DEFINITIONS。遇到这个错误,把变量声明放进头文件用extern导出,只在main.c里定义一次。
4.3 加载HEX文件并验证完整功能流程
双击Proteus里的单片机,在弹出的编辑窗口中Program File一栏选择刚编译出的hex文件,然后点运行。验证顺序建议按下面的表走,每一条都对应一个功能模块,哪个环节不对就回看那个模块的代码。
| 操作步骤 | 预期结果 |
|---|---|
| 按下启动键 | 液晶进入运行状态,里程显示开始可变化 |
| 给P3.2施加10个脉冲 | 里程增加10×脉冲当量 |
| 里程低于3公里 | 金额始终显示8.00 |
| 里程超过3公里 | 金额按超出里程×单价累加 |
| 按下暂停键 | 进入等待状态,里程不涨,等待时间累计 |
| 等待满5分钟 | 金额额外增加1公里费用 |
| 按下停止键 | 金额冻结不变 |
| 按下复位键 | 里程、金额全部清零回到待机 |
这条验证流程就是课程设计答辩时的演示脚本。如果某一步不通过,优先检查两个地方:一是脉冲当量宏定义是否和你的仿真脉冲频率匹配,二是meter_proc里按键状态编码是否和key_scan返回值一致。资源包里的代码通常按这个流程调好,换到自己的工程时先跑一遍确认,再改参数。
4.4 仿真和实物的差异清单
仿真跑通不等于实物一定能跑,这个坑几乎所有做过课设的人都踩过。仿真里没有按键抖动、没有电源毛刺、没有电磁干扰,霍尔信号干净得像教科书,而实物这三样全都有。
| 差异项 | Proteus仿真 | 实物电路 |
|---|---|---|
| 按键输入 | 无抖动,按下即稳定电平 | 金属触点抖动明显,必须软件消抖 |
| 晶振起振 | 无需负载电容也能跑 | 必须配12-30pF负载电容 |
| 上电复位 | 电平干净,很少异常 | 电源上冲可能引起误复位,要加复位电路 |
| 霍尔信号 | 方波理想边沿 | 有毛刺和缓慢的边沿变化,需要施密特整形 |
| 显示模块 | 接上就亮 | 对比度电位器、上拉电阻少一个都不行 |
我一般会在仿真通过后,再花十分钟挨个检查这几个差异点:按键扫描有没有消抖、霍尔输入端有没有上拉和滤波电容、LCD的V0有没有可调电阻。把这四条在原理图画板之前落实,实物焊接翻车的概率能少一半。
5. 计价器避坑指南:五个高频翻车点与排查方法
5.1 按键抖动导致状态机乱跳
现象:按下启动键一次,状态在IDLE和RUN之间来回切换,按复位键时金额有时清零有时不清零。
原因:机械按键的金属触点按下瞬间会产生几毫秒到十几毫秒的抖动,电平在高低之间反复跳变,单片机在极短时间内识别到多次边沿,把它当成多次按键输入。
解决:按键扫描函数里加10ms延时消抖,并且等待松手后再返回键值,避免一次长按被重复触发。
unsigned char key_scan(void) { unsigned char key_val = KEY_NONE; if ((P1 & 0x07) == 0x07) return KEY_NONE; // 无按键按下 delay_ms(10); // 10ms软件消抖 if ((P1 & 0x01) == 0) key_val = KEY_START; else if ((P1 & 0x02) == 0) key_val = KEY_WAIT; else if ((P1 & 0x04) == 0) key_val = KEY_RESET; while ((P1 & 0x07) != 0x07); // 等待松手 return key_val; }逻辑说明:先读一次P1判断有没有键按下,有就延时10ms再读一次,两次都确认是低电平才算有效;最后的while循环把按键按住期间的所有重复触发全部屏蔽掉,只有松手后函数才返回。参数说明:10ms是经验值,机械按键的抖动通常不超过10ms,调太短消不掉抖动,调太长响应变肉,实际调试时可以用示波器看按键波形再定。
5.2 定时器计时和真实时间对不上
现象:程序里等了60秒,秒表实测只有57秒或者多了5秒,等待费的5分钟折算明显偏短或偏长。
原因:定时器0中断服务函数里除了重装初值还做了其他操作,中断响应本身需要几个机器周期,累加多了时间就跑快;仿真时在中断里打断点停在ISR中,时间失真更严重。
解决:先确认晶振频率和初值匹配,再用示波器量一个I/O口的翻转周期做实际校准。
void timer0_isr(void) interrupt 1 { TH0 = 0x3C; // 重装初值 TL0 = 0xB0; tick_50ms++; if (tick_50ms >= 20) { tick_50ms = 0; second_count++; P2_0 = ~P2_0; // 翻转一个测试引脚,用示波器量周期 } }参数说明:把P2.0在每秒处翻转一次,示波器量出来的周期应该是精确的2秒,如果偏了,就微调TH0和TL0。比如实测周期偏长,说明定时器走得慢,可以适当减小初值让每次中断提前一点;反之则增大初值。这个校准方法是排查时间类问题的通用手段,做电子钟、交通灯课设一样能用。
5.3 里程多计数:脉冲信号抖动造成虚增
现象:手在霍尔信号线附近晃一下,里程就增加了好几米;仿真里手动点一次开关,计数加了两次。
原因:霍尔输出端没有滤波,信号边沿有毛刺;仿真里用普通开关模拟脉冲时,开关本身也有抖动,INT0下降沿触发会把抖动沿也算进去。
解决:在软件里加最短间隔滤波,记录上一次脉冲进入中断的时间片,间隔不足一定值的脉冲直接丢弃。
unsigned char last_pulse_tick = 0; void int0_isr(void) interrupt 0 { unsigned char now = (unsigned char)(second_count * 20 + tick_50ms); // 相邻两个有效脉冲间隔必须大于2个时间片(约100ms) if ((unsigned char)(now - last_pulse_tick) >= 2) { pulse_count++; last_pulse_tick = now; } }逻辑说明:用一个8位的时间片计数器记录上次有效脉冲的时刻,下次中断进来时算差值,小于2就认为是抖动直接丢弃。参数说明:2个时间片约等于100ms,按0.1米一个脉冲算,对应的最高可测车速约36km/h,课程设计演示够用;真车高速场景要把这个阈值调小,否则高速时会把真实脉冲误丢。
5.4 LCD1602不显示或花屏
现象:上电后液晶背光亮但一个字都没有,或者屏幕上全是黑色方块乱码。
原因:三个常见坑——P0口没加上拉电阻,数据线高电平驱动不足;对比度引脚V0悬空或电压不对,字符显示不出来;初始化函数里延时不够,液晶上电后还没完成内部复位就开始收命令。
解决:P0接上拉排阻;V0接10k电位器分压,仿真和实物都要调;初始化函数开头加至少50ms延时,让液晶模块稳定后再发第一组命令。按这个顺序排查,绝大多数花屏问题都能解决。
5.5 掉电后单价和累计里程全丢
现象:设置好2.4元/公里的单价,断电再上电,单价回到默认值1.0或者变成一个乱数,之前跑的里程也没了。
原因:参数和里程都只存在RAM里,51单片机掉电后RAM内容全部丢失。课程设计里最常见的忽略点,答辩时老师常问“司机调好的价格断电为什么没了”。
解决:用24C02这类I2C接口EEPROM保存参数,上电时读回并做范围校验,读到的值不在合理区间就回退默认值。
void param_load(void) { unsigned char val; i2c_read(0xA0, 0x00, &val); // 24C02器件地址0xA0,读地址0x00 if (val >= 100 && val <= 500) // 单价合法范围1.0元到5.0元 price_per_km = val; else price_per_km = 240; // 非法值回退默认2.4元 }逻辑说明:先读EEPROM保存的单价值,判断是否在100到500分之间,是就用,不是就回默认240分。参数说明:0xA0是24C02写操作的器件地址,读操作用0xA1;这个范围校验很重要,因为EEPROM第一次上电时读回的往往是0xFF,不校验的话255分会被当成2.55元单价,价格全乱套。
6. 从仿真到实用:三个进阶技巧与最终验证
6.1 用脉冲间隔测车速,自动切入等待计费
资源包里的基础版本通常靠手动按键进入等待状态,但真实出租车是车速低于某个阈值自动开始等待计费。进阶做法是记录相邻两个脉冲的时间间隔,间隔越长说明车速越慢,连续N秒低于阈值就自动把状态机切到WAIT。
// 在50ms时基里检查车速状态 void speed_check(void) { if (pulse_interval_tick > 30) // 两个脉冲间隔超过1.5秒,说明车速很低 { slow_count++; if (slow_count >= 20) // 持续低速约1秒 { meter_state = STATE_WAIT; } } else { slow_count = 0; } }参数说明:pulse_interval_tick需要在int0中断里更新,slow_count在定时器中断里累加,阈值按实际车速要求调整。这个逻辑补上之后,演示时只要停止给脉冲,计价器就能自动进入等待计费,比手动按键更像真车。
6.2 长按按键进入参数校准模式
单价调整如果每次按一下就进设置,行驶中容易误触。常见的稳妥做法是:在IDLE状态长按功能键超过2秒才进入校准模式,校准模式下短按加价、长按减价,再按功能键退出并把新单价写入24C02。用状态机扩展一个SETTING子状态,不会影响原有四态流程。
6.3 数码管版本注意动态扫描消隐
如果想把LCD1602换成数码管动态扫描,一个必须处理的细节是消隐:切换位选时先关闭所有位选,再送段码数据,最后打开目标位选,否则会产生拖影。扫描频率一般设在1kHz到2kHz之间,低于500Hz肉眼能看到闪烁,高于5kHz占用CPU太多。
最终验证建议把前面4.3节的流程完整走一遍,再加一条:连续跑10分钟,每5分钟记录一次里程和金额,看等待费折算是否符合规则。从那以后我每次拿到这种51单片机课程设计资源,都会强制按“脉冲当量→状态机→显示刷新”的顺序在Proteus里先走一遍再动烙铁,硬件反馈和软件逻辑分开验证,这是这套计价器设计教给我最实在的一课。希望帮到你。
本文还有配套的精品资源,点击获取