赞
踩
指令系统
数据传送: 寄存器寻址,寄存器间接寻址等
中断: 外部中断
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单片机使用定时器编写时钟程序(汇编)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。