51单片机出租车计价器(汇编语言)

51单片机出租车计价器(汇编语言)

要求

  1. 使用信号发生器作为模拟出租车轮胎转速
  2. 使用数码管可显示费用,里程,速度
  3. 按钮按下开始计费
  4. 按钮抬起后停止计费
  5. 按钮再次按下后清零
  6. 里程达到2km前费用均为8元
  7. 里程达到2km后每千米2.6元
  8. 代码部分使用汇编语言完成,不可使用c语言编译

效果图

  1. 里程达到2km前费用均为8元
    在这里插入图片描述
  2. 里程达到2km后每公里2.6元
    在这里插入图片描述
  3. 按钮抬起后里程不再增加
    在这里插入图片描述
  4. 按钮再次按下后清零
    在这里插入图片描述

理论基础

指令系统
数据传送: 寄存器寻址,寄存器间接寻址等
中断: 外部中断
89C51单片机定时器的4种模式及其应用

实现思路

数码管显示

要实现数码管的显示,需要同时输出段码和位码才能让数码管显示

位码

控制数码管的显示位

段码

控制数码管的具体显示
共阳极段码表

DB	0C0H, 0F9H, 0A4H, 0B0H, 99H, 92H, 82H, 0F8H, 80H, 90H  //[0~9]
DB	88H, 83H, 0C6H, 0A1H, 86H, 8EH  //[A~F]
DB	89H, 0C7H, 8CH, 0C1H, 91H, 0FFH  //[H L P U Y 灭]

这里建议在程序调试初期使用0-F的16进制段码表
实例:

MOV P2,#00H		;位码
MOV P1,#0C0H		;段码

此时数码管的第1位会显示为数字0

定时刷新

本程序中需要12位数码管全部显示,所以需要定时刷新所有数码管
而刷新程序周期为5ms (下面会解释)
如果一次只刷新1位会导致显示不稳定
(即每次自增显示位,然后与某数ANL)

Display:
    LCALL Delay
    MOV    A, cDisplayBit
    MOV    P2, A
    MOV    DPTR, #DisplayTable1
    MOV    A, #cDisplayBuffer
    ADD    A, cDisplayBit
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
    INC cDisplayBit
    ANL cDisplayBit,#0FH

以上程序调用一次只会刷新一个数码管,刷新范围0-15位(0FH)
所以需要一次全部刷新所有数码管

Display:
    MOV R5,#0CH		;需要扫描12个位
D1: 
    LCALL Delay
    MOV    A, cDisplayBit
    MOV    P2, A
-------------------------------------;译码
    MOV    DPTR, #DisplayTable1
    MOV    A, #cDisplayBuffer
    ADD    A, cDisplayBit
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
--------------------------------------
    INC cDisplayBit
    DJNZ R5,D1
    MOV cDisplayBit,#00H		;自增,清零
    RET

在本程序中设置了两个显示缓冲区

cDisplayBuffer 存储单字节BCD码 如 08H 07H 有12位
BCD 存储双字节BCD码 如 12H 60H 有6位

BCD送入cDisplayBuffer的程序

LOOSE:        
   MOV R5,#06H	    ;总共5个字节,需要循环5次
   MOV R0,#BCD    ;6位双字节首地址
   MOV R1,#cDisplayBuffer	    ;12位单字节缓冲区地址
   
LOOP1:
   MOV A,@R0
   ANL A,#0F0H	    ;取高位
   SWAP A
   MOV @R1,A
   INC R1
   MOV A,@R0	    ;存高位
   ANL A,#0FH	    ;取低位
   MOV @R1,A	    ;存低位
   INC R0
   INC R1
   DJNZ R5,LOOP1
   RET

小数点显示

带小数点的共阳极段码表

DB 0BFH, 86H, 0DBH, 0CFH, 0E6H, 0EDH, 0FDH, 87H, 0FFH, 0EFH

只需要在显示程序后追加一个程序,指定段码,使用同样的BCD译码进行叠加显示即可看到小数点

    LCALL Delay
    MOV    P2, #01H
    MOV    DPTR, #DisplayTable2
    MOV    A, #cDisplayBuffer
    ADD    A, #01H
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
    LCALL Delay
    MOV    P2, #05H
    MOV    DPTR, #DisplayTable2
    MOV    A, #cDisplayBuffer
    ADD    A, #05H
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
    LCALL Delay
    MOV    P2, #0AH
    MOV    DPTR, #DisplayTable2
    MOV    A, #cDisplayBuffer
    ADD    A, #0AH
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
    RET

Delay为防抖程序
DisplayTable2为带小数点的共阳极段码表
如果不想写的这么冗杂也可以直接置小数点

    LCALL Delay
    MOV    P2, #01H
    MOV    P1,#80H
    LCALL Delay
    MOV    P2, #05H
    MOV    P1,#80H
    LCALL Delay
    MOV    P2, #0AH
    MOV    P1,#80H

80H为共阳极 “.” 的段码

计速

频率捕捉

速度需要显示为1s内平均速度,我们要先捕捉到信号发生器的频率再进行计算操作
所以我们需要使用到89C51单片机的两个计时器的两个功能
本程序中T0计时器使用边缘中断模式
T1计时器使用计时器模式,产生5ms的定时程序
在程序开头需要指明中断程序的入口地址

   ORG 0000H
   SJMP MAIN
   ORG 0003H
   LJMP 外部中断
   ORG 001BH
   LJMP 定时器

T0对应的中断子程序,每次对速度自增,1s后取值清零实现频率捕捉

   INC cache  ;速度所在地址单元
   RETI

T1对应的中断子程序
为了实现1s的计时需要使用到一个Temp标志位,
每次自增,在MAIN程序中判断是否达到200即可实现1s的定时功能
定时5ms的中断程序
该单片机晶振为22.1184MHz
计算过程略

   MOV TH1,#0DCH    
   MOV TL1,#00H
   INC cTemp
   RETI

部分主程序

M1:
   MOV	A,cTemp   ;5ms定时,计200次为1秒
   XRL	A,#200
   JNZ	M1		;不满200继续循环
   LCALL 速度计算
   MOV	cTemp,#0
   MOV	cache,#0	;速度置零
   SJMP	M1	;死循环
速度计算

轮胎每秒转1圈折合km/h为

1 r/s = 6.588 km/h

这里需要精确显示,所以需要使用到双字节乘法和双字节译码程序
51单片机的MUL指令是针对16进制数的,不要天真的以为是10进制数乘法
在这里插入图片描述
双字节乘法例子

12 34H * 56 78H = 06 26 00 60H

结果有4个16进制数,存放于4个地址中
双字节译码程序
R0是待译码双字节数高位地址
R1是译码结果的高字节地址
调用前先将R0和R1寄存器赋值

BinDec:	;双字节BCD译码
   CLR A
   MOV @R1,A
   INC	R1
   MOV @R1,A
   INC R1
   MOV @R1,A
   PUSH 7
   MOV R7,#16
BD1:
    CLR C
    INC R0
    MOV A,@R0
    RLC A
    MOV @R0,A
    DEC R0
    MOV A,@R0
    RLC A
    MOV @R0,A
    PUSH 1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    DEC R1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    DEC R1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    POP 1
    DJNZ R7,BD1
    POP 7
    RET

@Ri表示寄存器内地址位所储存的16进制数

译码前
@R0: 01H		@R0+1:01H
译码后R0会被清零所以需要和数据缓存位分开
译码后
@R0: 00H		@R0+1:00H
@R1: 00		@R1+1:02		@R1+2:57

所以速度计算的具体思路就是使用双字节乘法
每秒转过的圈数 * 6.588 即可
由于小数点的影响我们有以下几种方案
先假设频率是10,速度65.8km/h

6588 * 10 = 06 58 80

嗯,看起来很简单
看一下16进制

16 BCH * 0AH = 01 01 58

哦豁完蛋,溢出了,译码程序只能够译码2位16进制数
01 01H = 0257
01 58H = 0344
和原来的数完全不沾边
那么就需要一点骚操作了

10进制
65 * 10 = 06 50
16进制
41H * 0AH = 02 8A H

缺了后面的8但已经大差不差
那我们能不给强行补上呢
事实上是可以的,而且还挺准的

0E0H * 0AH = 08 C0H

然后取高位加到前面的低位上去

02 8AH
   08H
02 92H = 06 58 

锵锵,6588就出现了
你问我E0咋来的?
猜的(真切)
在调试的时候我已经把译码程序都加上去了,然后实在无奈了
对着低位一波操作结果发现数字对了,甚至还很准确
再把小数点加上就很nice了
效果图
在这里插入图片描述
(谁家出租车能跑到600多 23333~~)
在这里插入图片描述

计程

里程追加

如题所示,在多次使用乘法之后我放弃了
难点

183 * 1000 = 18 30 00

早就溢出了,这才转了1000圈,才1.83km
而且需要储存走过的圈数至少需要2位16进制位才够用
这样译码器负担更重
即使是

1 * 1000 
8 * 1000
3 * 1000

也还是存在问题
如果带上一个系数,比如说
每转10圈再对路程进行更新
然而该方法我尝试过同样也行不通

所以我最后采用的方案是进行追加操作
使用4个地址位来作为我的缓存位
前两位可以直接接到显示缓冲区

00 00 00 00
	  18 30

使用DA和ADDC命令便可轻松做到

DA 命令可以简单理解为 出现字母的数转化为10进制数
MOV A,#0AH
DA A
A的值为 10H
MOV A,#10H
DA A
A的值同样是10H
而当A大于100后使用DA命令会将C置1
对高位进行ADDC 高位,#00H操作即可实现进位

程序较为简单,这里不给出了

计价

里程判断

需要对里程进行判断,小于等于2km时不对价格进行追加
这里我选用的方案是
减2后判断是否出现进位
若没有出现则退出并置数码管为8

   MOV A,BCD+2
   CLR C
   SUBB A,#02H	;判断是否大于2
   JC PRET	;小于2直接置8然后退出
PRET:
	MOV BCD+2,#08H
	RET
价格追加

和里程追加同理
只是这里每一圈追加的数是

26 * 183 = 4758

同样也需要4个缓冲位
小数点自己处理一下

按钮功能

抬起后价格不增加

在外部中断程序中加入JB对按钮进行监听
速度显示还是得放外面,不然成0去了
只要信号发生器还开着速度就不会是0

   INC cache  ;speed
   JB P3.7,EXIT 
   LCALL 路程
   LCALL 价格
   RETI
再次按下后清零

这里需要涉及到
循环中只进行一次的操作
回想到高级程序语言,我们可以这样

mark = 1 标志位置1
while(1):
	if mark == 1:
		只运行一次的程序
		mark = 0 标志位置0
	循环程序

这样就实现了每次按下按钮之后都对地址进行一次清零
最终的外部中断程序修改如下

外部中断:
   INC cache  ;speed
   JB P3.7,EXIT ;按下放通,抬起就置mark为0
   LCALL 里程
   LCALL 价格
   MOV A,mark	;取mark
   XRL A,#1	;判断mark
   JZ 清零	;清零内存
   RETI
EXIT:
   MOV mark,#01H	;mark置1
   RETI

这里给个清零程序
(直接FF干光算了,其实到80H就可以了)

清零:
   MOV	mark,#00H	;mark清零
   MOV R1,#0FFH
   MOV R0,#20H	;地址清零
C1:
   MOV @R0,#00H
   INC R0
   DJNZ R1,C1
   RETI

汇编源代码

   ORG 0000H
   SJMP MAIN
   ORG 0003H
   LJMP TRIGGER		;外部中断
   ORG 001BH
   LJMP TIMER		;定时器

DisplayTable1: DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH,77H,7CH,39H,5EH,79H,71H,76H,38H,73H,3EH,6EH,00H
DisplayTable2: DB 0BFH, 86H, 0DBH, 0CFH, 0E6H, 0EDH, 0FDH, 87H, 0FFH, 0EFH	
cDisplayBuffer 	EQU 	20H  	;12位段码实际值的缓冲区 12位
cDisplayBit	EQU	2CH
cTemp    EQU 	2DH
path	EQU	2FH
mark	EQU	6AH
cache	EQU	36H
result	EQU	75H	;运算结果
BIN	EQU	46H	;译码器译码后的BCD
BCD	EQU	40H	;40-45 6个BCD位 

MAIN:
   MOV cache,#00H
   MOV cTemp,#00H
   MOV TMOD,#10H    
   MOV TH1,#0DCH	    
   MOV TL1,#00H	
   SETB ET1    
   SETB TR1
   SETB	IT0
   SETB	EX0
   SETB EA

M1:
   MOV	A,cTemp   ;5ms定时,计200次为1秒
   XRL	A,#200
   JNZ	M1
   LCALL SPEEDCOUNT
   MOV	cTemp,#0
   MOV	cache,#0	;速度置零
   SJMP	M1	;死循环

;0 按下,1抬起
TRIGGER:
   INC cache  ;speed
   JB P3.7,EXIT ;按下放通,抬起就置mark为0
   LCALL PATHINC
   LCALL PRICEINC
   MOV A,mark	;取mark
   XRL A,#1	;判断mark
   JZ CCLR	;清零内存
   RETI

TIMER:        ;5ms计时器
   MOV TH1,#0DCH    
   MOV TL1,#00H
   LCALL SHOW
   INC cTemp
   RETI

SPEEDCOUNT:
   MOV A,cache
   MOV B,#0E2H
   MUL AB
   MOV result+1,B
   MOV B,#41H
   MOV A,cache
   MUL AB
   ADD A,result+1
   MOV result+1,A
   MOV A,B
   ADDC A,#0
   MOV result,A
   MOV R0,#result
   MOV R1,#BIN
   LCALL BinDec
   MOV BCD+5,BIN+2	;L
   MOV BCD+4,BIN+1	;M 00 06 58
   RET
   
BinDec:	;双字节BCD译码
   CLR A
   MOV @R1,A
   INC	R1
   MOV @R1,A
   INC R1
   MOV @R1,A
   PUSH 7
   MOV R7,#16
BD1:
    CLR C
    INC R0
    MOV A,@R0
    RLC A
    MOV @R0,A
    DEC R0
    MOV A,@R0
    RLC A
    MOV @R0,A
    PUSH 1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    DEC R1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    DEC R1
    MOV A,@R1
    ADDC A,@R1
    DA  A
    MOV @R1,A
    POP 1
    DJNZ R7,BD1
    POP 7
    RET

PRET:
   MOV BCD,#08H	;2km内直接置8返回
   RET
   
TORET:
   RET
   
EXIT:
   MOV mark,#01H	;mark置1
   RETI
   
CCLR:
   MOV	mark,#00H	;mark清零
   MOV R1,#0FFH
   MOV R0,#20H	;地址清零
C1:
   MOV @R0,#00H
   INC R0
   DJNZ R1,C1
   RETI
   
PATHINC:
   CLR C
   MOV A,#30H	;L
   ADDC A,cache+2
   DA A
   MOV cache+2,A
   MOV A,#18H	;H
   ADDC A,cache+1
   DA A
   MOV cache+1,A
   MOV A,BCD+3
   ADDC A,#0
   DA A
   MOV BCD+3,A
   MOV A,BCD+2
   ADDC A,#0
   DA A
   MOV BCD+2,A
   RET
   
PRICEINC:
   JB P3.7,TORET ;检测按钮
   MOV A,BCD+2
   CLR C
   SUBB A,#02H	;判断是否大于2
   JC PRET	;小于2直接置8然后退出
   MOV A,#58H ;L
   ADD A,cache+4 ;L
   DA A
   MOV cache+4,A
   MOV A,#47H
   ADDC A,cache+3
   DA A
   MOV cache+3,A   
   MOV A,#00H
   ADDC A,BCD+1
   DA A
   MOV BCD+1,A
   MOV A,#00H
   ADDC A,BCD
   DA A
   MOV BCD,A
   RET
   
SHOW:
   LCALL LOOSE
   LCALL Display
   RET
   
LOOSE:        
   MOV R5,#06H	    ;总共6个字节,需要循环6次
   MOV R0,#BCD	    ;时间序列存储首地址
   MOV R1,#cDisplayBuffer	    ;12位缓冲区
   
LOOP1:
   MOV A,@R0
   ANL A,#0F0H	    ;取高位
   SWAP A
   MOV @R1,A
   INC R1
   MOV A,@R0	    ;存高位
   ANL A,#0FH	    ;取低位
   MOV @R1,A	    ;存低位
   INC R0
   INC R1
   DJNZ R5,LOOP1
   RET

Display:				;通用显示程序
   MOV R5,#0CH
D1: 
    LCALL Delay
    MOV    A, cDisplayBit
    MOV    P2, A
    MOV    DPTR, #DisplayTable1
    MOV    A, #cDisplayBuffer
    ADD    A, cDisplayBit
    MOV    R0, A
    MOV    A, @R0
    MOVC   A, @A+DPTR
    MOV    P1, A
    INC cDisplayBit
    DJNZ R5,D1
    MOV cDisplayBit,#00H
D2:
    LCALL Delay
    MOV    P2, #01H
    MOV    P1,#80H
    LCALL Delay
    MOV    P2, #05H
    MOV    P1,#80H
    LCALL Delay
    MOV    P2, #0AH
    MOV    P1,#80H
	RET

Delay:	;防抖
   MOV	R0,#10
   MOV	R1,#10
   DJNZ	R1,$
   DJNZ	R0,$-4
   RET

END

翻译比较生硬,结构不够优雅

仿真文件

传送门

后记

本计价器还有多种实现思路,这里就不作叙述了
希望大家能够有所收获

参考了这篇文章
基于MCS-51单片机使用定时器编写时钟程序(汇编)

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值