微机课程设计:用汇编写一个计费器
再次享受操纵所有寄存器的快感~
简介
没错,我又要写一次这个玩意了,为什么呢?因为计算机学院并不认电信学院的微机,所以我不得不再学了一次 8051。不得不说,计算机学院讲得清楚易懂多了,因为电信学院是 8086+8051 一起教的(电信的课程设置得真不合理🙃)
来说说要求吧!要求如下:
- 用Proteus设计原理图,要求显示出 里程,速度,总价
- 使用一个按钮,按下按钮开始计费,松开按钮停止计费
- 用信号发生器来产生出租车的模拟信号(轮子转一圈产生一个脉冲)
- 出租车轮胎周长按 1.83 米计算。2 公里以内按 8 元计算,超过 2 公里每公里按 2.6 元计算。不考虑其他费用
这不是很简单嘛,弄一个外部中断来统计脉冲的次数,从而算出里程和价格,然后再弄一个计时器来计算两次脉冲的时间,从而算出速度,搞定!有两点要注意:
- 计算价格时要判断是否大于 0,再判断是否大于 2
- 计算速度时,要注意当太久没脉冲时,要降低速度
这个程序的难点在于数学计算。
程序设计
这个程序分为四部分:
- 主程序:
- 判断按键是否按下
- 计算里程、速度、总价
- 定时器0:
- 计时
- 定时器1:
- 显示
- 外部中断0:
- 脉冲
主要需要如下常量和变量:
- 常量:
- n 公里
MILE_THRESHOLD
- 起步价
START_PRICE
- n 公里后每公里价格
MILE_PRICE_INT
和MILE_PRICE_DEC
- 轮胎周长
WHEEL_C_INT
和WHEEL_C_DEC
- 数码管显示码
LED_DIGIT_CODE
- n 公里
- 变量
- 上个脉冲的时间
last_pulse
、tast_pulse_tl0
、tast_pulse_th0
- 当前脉冲的时间
this_pulse
- 总脉冲次数
total_pulse_h
和total_pulse_l
- 速度
speed_int
和speed_dec
- 价格
price_int
和price_dec
- 里程
mile_int
和mile_dec
- 每个数码管的数字
digit0~11
- 上个脉冲的时间
数学相关
此处我们将讨论小数的表示、变量的范围以及精确度、定时时间、定点小数的计算与转换。
小数的表示
小数有两种表示方法:定点与浮点,定点的好处就是方便算,但费空间;浮点的好处就是省空间,但难算。作为一个懒人,我决定使用定点,后续我将“2bytes 整数+1bytes 小数”简写为“2+1”,1+1、2+2 也是类似的。
变量的表示与计算
变量的范围与精确度。
- 从硬件上来分析:每个数码管都是 4 位,所以按道理每个变量都至少需要 2+1,甚至 2+2. 一些中间变量,如脉冲总数需要更多
- 从常识上分析:高速的速度不会超 200 km/h,所以 1+1 够用;市区出租车路程很少超 100km,同样只需要 1+1;价格最大可能为 $100\times 5=500$(上海市夜间超过 15km 为 4.7¥/km),所以需要 2+1
- 从老师给的示例分析:示例中,受小数点的限制,价格和里程均不过百,1+1 够用。速度倒是有去到 250 的,需要 2+1.
作为一个懒人,我觉得够用就行,因此使用第三种,即价格、里程为 1+1,速度为 2+1。(注:实际代码中,为了方便除法运算,速度、里程均使用 3+1)
哦,对了,1 Byte 小数的最小精确到 $0.00390625$,足够了,毕竟现在也没有人给钱会精确到厘。
定时时间。数码管显示的定时采用自动装入,定时时间取 8 位定时器所能达到的最大值(反正这刷新率已经够用了),初值为 0.
至于计时嘛,这就要讨论一下了。计时主要用于计算两个脉冲之间间隔的时间,怎么计呢?步骤如下:
- 当有脉冲来时,读取定时器的数值,然后清零,开始对下一个脉冲计时。
- 如果定时器到时间后还没有脉冲,就给变量
this_pulse
加一(相当于扩展了定时器的位)。 - 最终计算的时间包括定时器的时间(低2bytes)和
this_pulse
(高tyte)。 - 下一个脉冲来时重复 1~3
这种方法的好处是:精确,并且触发中断的次数也少很多。缺点就是:我们最后得出的时间是 3 bytes,计算可能会比较复杂。
3 bytes 够不够用呢?假设速度为 $v$,轮胎周长为 $C$,机器周期为 $t_0$,那么定时器的数值为(考虑上扩展位):$t=\dfrac{C}{vt_0}$。取 $t_0=1{\rm \mu s}$,$C=1.83 {\rm m}$。那么 $v=100 {\rm km/h}$ 时,$t=65880$;$v=1 {\rm km/h}$ 时,$t=6588000$;这两个数转为二进制后均不超出 3 个字节(24位),如果依然溢出,那么我们可以认为速度为 0.
DEC 6588000
BIN 0110 0100 1000 0110 0110 0000
速度计算。但此处又有一个问题,这么长的数怎么计算啊。首先先确定公式:$v=\dfrac{C}{t}$。考虑单位转换,${\rm \dfrac{m}{\mu s}}\cdot k={\rm km/h}$,算出 $k=3.6 \times 10^{6}$,即我们最后要乘上这个数。这个数太大了,我们将它分解为 $28125 \times 2^7$,这么一来,公式就变为:
$$ \begin{aligned} v&=\dfrac{C}{t}\cdot 28125 \times 2^7\\ &=\dfrac{C\gg 1}{t}\cdot 28125 \times 2^8\\ &=\dfrac{C\gg 1}{t \gg 8} \cdot 28125\\ &=\dfrac{(C\cdot 28125)\gg 1}{t \gg 8} \end{aligned}\\ v:速度 \; C:周长 \; t:时间间隔 $$$C$ 是一个 1+1 的数,$t$ 移位(注:这并不是真的移位,而是把低 8 位移作小数)后是一个 2+1 的数,28125 是一个 2+0 的数。我们甚至可以再进一步简化,比如 28125 可以换为 28160(即 6E00H),这仅会带来 0.124% 的误差,但替换后,低 8 位就变为了 0,我们就无需计算低 8 位相乘。
我们先来计算 C 与 28125 相乘。这是个固定的数,所以只需要计算一次。
$$ (C_H+C_L\times 2^{-8})\times {\rm 6E00H}\\ =(C_H \times {\rm 6EH})\ll8+(C_L\times {\rm 6EH}) $$上面的结果是一个 3+1 的数,然后我们再算除法,此处我们忽略 $t$ 的小数部分,这样的话在 100 km/h 时仅带来 0.3% 的误差,低速时误差会更小(因为低速时 $t$ 会更大)。这么一来,我们只需要算一个 3+1 字节除以 2 字节。多字节除法的具体计算过程我们后面一起讨论。
里程计算 轮胎周长 1.83m,那么走 1km 就会有 546 个脉冲,100km 就会有 54645 个脉冲,因此我们用 2 个字节来存储脉冲数。那么,总里程等于脉冲数×周长÷1000. 脉冲数×周长得到得是一个 3+1 的数,1000 是 2 字节的数,也就是说,此处需要计算一个 3+1 字节除以 2 字节。
价格计算 先利用相减来比较大小,低于一定价格就按起步价来算,高于一定价格,就直接用相减的结果乘上每公里的价格,然后再加上起步价即可。多字节乘法一点难度都没有。
3+1 字节除以 2 字节 参考 51单片机多字节除法,多字节除法有两种:移位相减和循环相减。
- 循环相减:循环相减的原理就是用被除数减去除数,判断是不是够减,如果够减,将被除数数更新为差值,商加1;如果不够减,则结束,此时的被除数即为余数。其实质就是,求出被除数中有多少个除数。(这种方法很蠢)
- 移位相减:就和人手算的原理类似,比如算 1011 除以 11
- 1 - 11 不够减,所以结果最高位为 0
- 10 - 11 不够减,所以结果第二位为 0
- 101 - 11 = 10,所以结果第三位为 1
- 101 - 11 = 10,所以结果第四位为 1
- 如果不需要算小数,那么结果就是 0011 余 10;如果要算小数,那么重复上面步骤继续算下去
具体程序在后面部分。
程序逻辑
这里用“伪”伪代码描述一下程序逻辑:
主程序:
计算速度
判断按钮
if 未按下:
停止计算路程和价格(保持显示的数字不变)
else:
if 正在计时:
计算路程和价格
转化为BCD
else:
清零
计算路程和价格
转化为BCD
计时(定时器0):
开始下次计时
this_pulse++ (本次间隔的溢出次数加一)
if this_pulse>last_pulse:
last_pulse = this_pulse + 10 //10 为速度的刷新间隔,可变
显示(定时器1):
根据BCD来显示
脉冲(外部中断0):
暂停定时器0
保存定时器0
保存 this_pulse 到 last_pulse
清零定时器0
开启定时器0
total_pulse++
里程、价格都没什么问题,但是速度可能会有 Bug. 下面来分析一下。
假如脉冲(外部中断0)的优先级最高,显示和计时的优先级较低。那么,脉冲中断就有可能打断计时中断,比如:
计时:
开始下次计时
//此时有脉冲
//this_pulse 还没 +1 就被保存
//定时器0被清零
//开始下次计时
this_pulse++ //定时器0清零后多加了 1
...
那么要如何修复这个 bug 呢?唯一的方法是在外部中断 0 中判断定时器 0 有无触发中断(即判断有无打断计时中断),但 TF0 已经被硬件自动清 0 了,所以无法实现(或者说我想不到方法实现)。我们只能将外部中断 0 和定时器 0 的优先级均设为最高,这样虽然不能及时响应外部中断,但这样带来的误差只有几个机器周期而已,可以接受。
程序编写
注:
- 凡是与计算相关的,都放在主循环中做,所以这部分函数不需要 push Acc 和 push PSW。而中断则有可能打断计算过程,所以需要 push Acc 和 PSW
- 程序有些细节与上面讨论的略有出入
常量与变量
详细代码
;====================================================================
; DEFINITIONS
;====================================================================
MILE_THRESHOLD EQU 2H
START_PRICE EQU 8H
MILE_PRICE_INT EQU 2H
MILE_PRICE_DEC EQU 99H
WHEEL_C_INT EQU 1H
WHEEL_C_DEC EQU 0D4H
;====================================================================
; VARIABLES
;====================================================================
; Pulse Interval
last_pulse_tl0 EQU 30H
last_pulse_th0 EQU 31H
last_pulse EQU 32H
this_pulse EQU 33H
; Total Number of Pulses
total_pulse_l EQU 34H
total_pulse_h EQU 35H
; Speed
speed_dec EQU 36H
speed_int_l EQU 37H
speed_int_m EQU 38H
speed_int_h EQU 39H
; Price
price_dec EQU 40H
price_int EQU 41H
; Mile
mile_dec EQU 42H
mile_int_l EQU 43H
mile_int_m EQU 44H
mile_int_h EQU 45H
; km
mile_km_l EQU 46H
mile_km_h EQU 47H
; Divide Calculation
c_bit EQU 06H
c_stack_bit EQU 07H
remainder_l EQU 58H
remainder_h EQU 59H
; Display
digit_dot EQU 20H
digit_price_dot_0 EQU 00H
digit_price_dot_1 EQU 01H
digit_mile_dot_0 EQU 02H
digit_mile_dot_1 EQU 03H
digit_speed_dot_0 EQU 04H
digit_speed_dot_1 EQU 05H
;c_bit EQU 06H
zero_speed_bit EQU 07H
digit_price_l EQU 5AH
digit_price_h EQU 5BH
digit_mile_l EQU 5CH
digit_mile_h EQU 5DH
digit_speed_l EQU 5EH
digit_speed_h EQU 5FH
; Run Bit
run_bit EQU 08H
初始化与主函数
详细代码
;====================================================================
; RESET and INTERRUPT VECTORS
;====================================================================
; Reset Vector
org 0000H
jmp Start
org 0003H
clr TR0 ;为提高准确率,先暂停再跳转 ;1MC
ljmp Pulse ;2MC
org 000BH
setb TR0
ljmp Timer
org 001Bh
ljmp Display
;====================================================================
; CODE SEGMENT
;====================================================================
org 0100h
LED_SEGMENT_CODE:
db 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH, 6FH ;数码管0~9
Start:
mov SP, #5FH
mov IP, #00000011B
setb ET0
setb EX0
setb IT0
lcall Display_Test
setb TR0
Loop:
lcall Calc_Speed ;无论有无按下按键,均显示速度
lcall Bin2BCD ;转化为BCD,用于显示
jnb P3.7, Button_Not_Pressed ;按键没有按下
jb run_bit, Button_Pressed ;按键按下,且上一时刻也按下
;按键从未按下到按下,清零里程,并开始计费
mov total_pulse_l, #0
mov total_pulse_h, #0
setb run_bit ;设置run_bit
Button_Pressed:
lcall Calc_Mile ;计算里程
lcall Calc_Price ;计算价格
jmp Loop
Button_Not_Pressed:
clr run_bit ;清除 run_bit
jmp Loop ;当前值不变,直接循环
计算
除法
详细代码
DIV_4_BY_2:
;R0 存 4 字节被除数的低位地址(小端)
;R1 存 2 字节除数的低位地址(小端)
;结果会保存到被除数的 3 个字节中
;需要给 remainder_l, remainder_h, c_bit 分配空间
;或者可以用 remainder_h EQU R3
mov B, #32 ;循环次数 ;4*8
mov remainder_h, #0
mov remainder_l, #0
DIV_RLC:
clr C ;清空 C,使得循环移位时最低位移入的是 0
;将被除数左移
mov A, @R0 ;取最低位
rlc A ;循环左移
mov @R0, A ;保存最低位
inc R0
mov A, @R0 ;取中间位
rlc A ;循环左移
mov @R0, A ;保存中间位
inc R0
mov A, @R0 ;取中间位
rlc A ;循环左移
mov @R0, A ;保存中间位
inc R0
mov A, @R0 ;取最高位
rlc A ;循环
mov @R0, A ;保存最高位
;恢复 R0 为最低位
mov c_bit, C
dec R0
dec R0
dec R0
mov C, c_bit
;把移出的最高位放入余数
mov A, remainder_l ;取余数低位
rlc A ;循环左移
mov remainder_l, A;保存低位
mov A, remainder_h ;取余数高位
rlc A ;循环左移
mov remainder_h, A;保存高位
mov c_bit, C ;保存余数移出的最高位
DIV_SUB:
;多字节减法
clr C
mov A, remainder_l
subb A, @R1 ;低位相减
mov R7, A
inc R1
mov A, remainder_h
subb A, @R1 ;高位相减
mov R6, A
jb c_bit, DIV_INC ;c_bit 为 1,肯定够减,保存相减后的余数
jc DIV_CONTINUE ;c_bit 为 0,且 C=1,不够减,不保存相减的结果
DIV_INC:
mov remainder_l, R7
mov remainder_h, R6
inc @R0
DIV_CONTINUE:
dec R1 ;恢复 R1 为最低位
djnz B, DIV_RLC
clr C
DIV_RETURN:
RET
注意这个函数的结果会覆盖被除数!
计算价格
详细代码
;====================================================================
; 计算价格
;====================================================================
Calc_Price:
push Acc
Calc_Price_If_Zero:
mov A, mile_int_l
cjne A, #0, Calc_Price_If
mov A, mile_dec
cjne A, #0, Calc_Price_If
mov price_dec, #0
mov price_int, #0
jmp Calc_Price_Return
Calc_Price_If:
mov A, mile_int_l
clr C
subb A, #MILE_THRESHOLD
jnc Calc_Price_Larger ;若里程的整数部分大于等于起步里程则跳转
Calc_Price_Less:
mov price_dec, #0
mov price_int, #START_PRICE
jmp Calc_Price_Return
Calc_Price_Larger:
push Acc ;保存相减后的结果
;多出来的整数x每公里价格的小数部分
mov B, #MILE_PRICE_DEC
mul AB
mov price_dec, A
mov price_int, B
;多出来的整数x每公里价格的整数部分
pop Acc
mov B, #MILE_PRICE_INT
mul AB
add A, price_int
mov price_int, A
; TODO: 处理 B!=0 的情况
mov A, mile_dec
mov B, #MILE_PRICE_INT
mul AB
add A, price_dec
mov price_dec, A
mov A, price_int
addc A, B
mov price_int, A
mov A, mile_dec
mov B, #MILE_PRICE_DEC
mul AB
xch A, B
add A, price_dec
mov price_dec, A
mov A, #START_PRICE
addc A, price_int
mov price_int, A
Calc_Price_Return:
pop Acc
ret
Calc_Price_Test:
; 若程序正确,应该会显示 11.9 元
lcall Display_Test
mov mile_int_l, #3
mov mile_dec, #80H
lcall Calc_Price
lcall Bin2BCD
ret
计算里程
详细代码
;====================================================================
; 计算里程
;====================================================================
Calc_Mile:
push Acc
Calc_Mile_Mul:
mov A, #WHEEL_C_DEC
mov B, total_pulse_l
mul AB
mov mile_dec, A
mov mile_int_l, B
mov A, #WHEEL_C_INT
mov B, total_pulse_l
mul AB
add A, mile_int_l
mov mile_int_l, A
mov A, #0
addc A, B
mov mile_int_m, A
mov A, #WHEEL_C_DEC
mov B, total_pulse_h
mul AB
add A, mile_int_l
mov mile_int_l, A
mov A, mile_int_m
addc A, B
mov mile_int_m, A
jnc $+4
inc mile_int_h
mov A, #WHEEL_C_INT
mov B, total_pulse_h
mul AB
add A, mile_int_m
mov mile_int_m, A
mov A, mile_int_h
addc A, B
mov mile_int_h, A
Calc_Mile_Div:
mov mile_km_l, #0E8H
mov mile_km_h, #03H
mov R0, #mile_dec
mov R1, #mile_km_l
acall DIV_4_BY_2
pop Acc
ret
Calc_Mile_Test:
; 如果程序正确,应该会显示 1 km
lcall Display_Test
mov total_pulse_l, #22H
mov total_pulse_h, #02H
lcall Calc_Mile
lcall Bin2BCD
ret
计算速度
详细代码
;====================================================================
; 计算速度
;====================================================================
Calc_Speed:
push Acc
Calc_Speed_Mul:
mov A, #WHEEL_C_DEC
mov B, #6EH
mul AB
mov speed_int_l, A
mov speed_int_m, B
mov A, #WHEEL_C_INT
mov B, #6EH
mul AB
add A, speed_int_m
mov speed_int_m, A
mov A, #0
addc A, B
Calc_Speed_RR:
clr C
rrc A
mov speed_int_h, A
mov A, speed_int_m
rrc A
mov speed_int_m, A
mov A, speed_int_l
rrc A
mov speed_int_l, A
mov A, speed_dec
rrc A
mov speed_dec, A
Calc_Speed_Div:
mov R0, #speed_dec
mov R1, #last_pulse_th0
acall DIV_4_BY_2
pop Acc
ret
Calc_Speed_Test:
lcall Display_Test
mov last_pulse_tl0, #60H
mov last_pulse_th0, #86H
mov last_pulse, #64H
lcall Calc_Speed
lcall Bin2BCD
ret
Calc_Speed_Test1:
mov IP, #00000011B
setb ET0
setb EX0
setb IT0
lcall Display_Test
setb TR0
lcall Calc_Mile
lcall Calc_Speed
lcall Bin2BCD
jmp $-9
ret
显示
二进制转压缩BCD
详细代码
;====================================================================
; 二进制转压缩BCD
;====================================================================
Bin2BCD:
;转换价格
mov A, price_int
acall Bin2BCD_Int
mov digit_price_h, A
mov A, price_dec
acall Bin2BCD_Dec
mov digit_price_l, A
setb digit_price_dot_1
clr digit_price_dot_0
;转换里程
mov A, mile_int_l
acall Bin2BCD_Int
mov digit_mile_h, A
mov A, mile_dec
acall Bin2BCD_Dec
mov digit_mile_l, A
setb digit_mile_dot_1
clr digit_mile_dot_0
;转换速度
mov A, speed_int_l
acall Bin2BCD_Int
swap A
push Acc
anl A, #0FH
mov R0, A
mov A, B
swap A
orl A, R0
mov digit_speed_h, A
mov A, speed_dec
acall Bin2BCD_Dec
swap A
anl A, #0FH
mov R0, A
pop Acc
anl A, #0F0H
orl A, R0
mov digit_speed_l, A
clr digit_speed_dot_1
setb digit_speed_dot_0
ret
Bin2BCD_Test:
; 如果程序正确,显示 10.50 10.50 010.5
lcall Display_Test
mov price_int, #0AH
mov price_dec, #80H
mov mile_int_l, #0AH
mov mile_dec, #80H
mov speed_int_l, #0AH
mov speed_dec, #80H
acall Bin2BCD
ret
;====================================================================
; 单字节二进制转压缩BCD
;====================================================================
Bin2BCD_Int:
; 单字节转BCD
; 参数:A 待转换的字节
; 返回:A 低四位和高四位分别存放个位和十位,B 低四位存放百位
mov B, #100 ;100作为除数送入B中
div AB ;16进制数除以100
mov R1, A ;百位数送R0,余数在B中
mov A, #10 ;分离十位数和个位数
xch A, B ;余数送入A中,除数10放在B中
div AB ;分离出十位放在A中,各位放在B中
swap A ;十位交换到A的高4位
add A, B ;将个位送入A的低4位
mov B, R1 ;将百位送入B
ret
Bin2BCD_Dec:
; 单字节转BCD
; 参数:A 待转换的字节
; 返回:A 高四位和低四位分别存放十分位和百分位
mov B, #10
mul AB ;16进制数乘以10
mov R1, B ;十分位送R0,剩余小数在A中
mov B, #10
mul AB
mov A, B
swap A ;百分位交换到A的高4位
orl A, R1
swap A ;将十分位放到高四位,百分位放到低四位
ret
定时器 0
详细代码
;====================================================================
; 计时(定时器0)
;====================================================================
Timer:
;注:setb TR0 在前面的 org 000BH 后面
push Acc
push PSW
inc this_pulse
mov A, last_pulse
clr C
subb A, this_pulse ;本次脉冲间隔超过上次的时间,说明速度变慢了
jnc Timer_Return
setb zero_speed_bit ;直接显示0
;下面是不直接显示0,而是逐渐减小到0
;在仿真时发现效果不好,便放弃
;mov A, #1
;add A, last_pulse_th0
;mov last_pulse_th0, A
;jnc Timer_Return
;inc last_pulse
;mov A, last_pulse
;cjne A, #090H, pulse_Return
;Timer_Clr_Speed:
; mov last_pulse, #090H
Timer_Return:
pop PSW
pop Acc
reti
定时器 1
详细代码
;====================================================================
; 显示(定时器1)
;====================================================================
Display:
; 根据 digit_speed, digit_price, digit_mile 中的压缩BCD来显示
; 根据 digit_speed_dot, digit_price_dot, digit_mile_dot 来显示小数点
push Acc
push PSW
mov DPTR, #LED_SEGMENT_CODE
Display_Price:
mov A, digit_price_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #0
mov P1, A
mov C, digit_price_dot_0
anl C, digit_price_dot_1
nop
mov P1.7, C
mov A, digit_price_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #1
mov P1, A
mov C, digit_price_dot_0
cpl C
anl C, digit_price_dot_1
mov P1.7, C
mov A, digit_price_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #2
mov P1, A
mov C, digit_price_dot_1
cpl C
anl C, digit_price_dot_0
mov P1.7, C
mov A, digit_price_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #3
mov P1, A
mov C, digit_price_dot_1
orl C, digit_price_dot_0
cpl C
mov P1.7, C
Display_Mile:
mov A, digit_mile_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #4
mov P1, A
mov C, digit_price_dot_0
anl C, digit_price_dot_1
nop
mov P1.7, C
mov A, digit_mile_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #5
mov P1, A
mov C, digit_mile_dot_0
cpl C
anl C, digit_mile_dot_1
mov P1.7, C
mov A, digit_mile_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #6
mov P1, A
mov C, digit_mile_dot_1
cpl C
anl C, digit_mile_dot_0
mov P1.7, C
mov A, digit_mile_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #7
mov P1, A
mov C, digit_mile_dot_1
orl C, digit_mile_dot_0
cpl C
mov P1.7, C
Display_Speed:
jnb zero_speed_bit, Display_Speed_Normal
mov digit_speed_h, #0
mov digit_speed_l, #0
Display_Speed_Normal:
mov A, digit_speed_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #8
mov P1, A
mov C, digit_speed_dot_0
anl C, digit_speed_dot_1
nop
mov P1.7, C
mov A, digit_speed_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #9
mov P1, A
mov C, digit_speed_dot_0
cpl C
anl C, digit_speed_dot_1
mov P1.7, C
mov A, digit_speed_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #10
mov P1, A
mov C, digit_speed_dot_1
cpl C
anl C, digit_speed_dot_0
mov P1.7, C
mov A, digit_speed_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #11
mov P1, A
mov C, digit_speed_dot_1
orl C, digit_speed_dot_0
cpl C
mov P1.7, C
Display_Return:
pop PSW
pop Acc
mov P1, #0
reti
;====================================================================
; 显示测试
;====================================================================
Display_Test:
; 如果程序正确,会显示 12.34 23.45 345.6
clr EA
mov TMOD, #00100001B
mov digit_price_h, #12H
mov digit_price_l, #34H
mov digit_mile_h, #23H
mov digit_mile_l, #45H
mov digit_speed_h, #34H
mov digit_speed_l, #56H
mov digit_dot, #00011010B
setb ET1
setb TR1
setb EA
ret
外部中断 0
详细代码
;====================================================================
; 脉冲(外部中断0)
;====================================================================
Pulse:
push Acc
push PSW ;2MC
mov last_pulse_tl0, tl0 ;2MC
mov last_pulse_th0, th0 ;2MC
mov last_pulse, this_pulse ;2MC
mov tl0, #16 ;需要加上执行指令占用的时间 ;2MC
mov th0, #0 ;2MC
setb TR0 ;1MC
mov this_pulse, #0
;时间的四舍五入
mov A, #80H
add A, last_pulse_tl0
mov A, last_pulse_th0
addc A, #0
mov last_pulse_th0, A
clr zero_speed_bit
;增加总脉冲数
inc total_pulse_l
mov A, total_pulse_l
cjne A, #0, Pulse_Return
inc total_pulse_h
; TODO: 处理总脉冲次数溢出
Pulse_Return:
pop PSW
pop Acc
reti
误差分析
选取不同的脉冲频率,计算速度的理论值,并与实际显示的值相比较:
脉冲频率(Hz) | 理论值 | 显示值 | 误差 |
---|---|---|---|
1 | 6.588 | 6.5 | 1.34% |
3 | 19.764 | 19.7 | 0.32% |
5 | 32.94 | 32.9 | 0.12% |
7 | 46.116 | 46.1 | 0.035% |
9 | 59.292 | 59.3 | 0.013% |
11 | 72.468 | 72.5 | 0.044% |
13 | 85.644 | 85.8 | 0.18% |
15 | 98.82 | 99 | 0.18% |
17 | 111.996 | 111.9 | 0.085% |
19 | 125.172 | 124.9 | 0.22% |
注意到最大的误差仅为 1.34%,在接受范围内。
选取不同里程,计算价格的理论值,并与实际显示的值相比较:
路程 | 理论值 | 显示值 | 误差 |
---|---|---|---|
2.38 | 8.98 | 8.99 | 0.11% |
5.59 | 17.334 | 17.34 | 0.034% |
9.92 | 28.592 | 28.57 | 0.077% |
13.33 | 37.458 | 37.45 | 0.021% |
19.95 | 54.67 | 54.63 | 0.055% |
28.55 | 77.03 | 76.96 | 0.091% |
33.21 | 89.146 | 89.09 | 0.063% |
总代码
详细代码
;====================================================================
; Main.asm file generated by New Project wizard
;
; Created: 周二 6月 8 2021
; Processor: AT89C51
; Compiler: ASEM-51 (Proteus)
;====================================================================
$NOMOD51
$INCLUDE (8051.MCU)
;====================================================================
; DEFINITIONS
;====================================================================
MILE_THRESHOLD EQU 2H ;起步价的公里
START_PRICE EQU 8H ;起步价
MILE_PRICE_INT EQU 2H ;每公里价格的整数
MILE_PRICE_DEC EQU 99H ;每公里价格的小数
WHEEL_C_INT EQU 1H ;轮胎周长的整数
WHEEL_C_DEC EQU 0D4H ;轮胎周长的小数
;====================================================================
; VARIABLES
;====================================================================
; Pulse Interval
last_pulse_tl0 EQU 30H ;上个脉冲的间隔 低字节(即计时器的tl0)
last_pulse_th0 EQU 31H ;上个脉冲的间隔 中字节(即计时器的th0)
last_pulse EQU 32H ;上个脉冲的间隔 高字节(即计时器的溢出次数)
this_pulse EQU 33H ;本次脉冲的间隔 高字节
; Total Number of Pulses
total_pulse_l EQU 34H ;总脉冲数 低
total_pulse_h EQU 35H ;总脉冲数 高
; Speed
speed_dec EQU 36H ;速度 小数
speed_int_l EQU 37H ;速度 整数 低
speed_int_m EQU 38H ;速度 整数 中
speed_int_h EQU 39H ;速度 整数 高
; Price
price_dec EQU 40H ;价格 小数
price_int EQU 41H ;价格 整数
; Mile
mile_dec EQU 42H ;里程 小数
mile_int_l EQU 43H ;里程 整数 低
mile_int_m EQU 44H ;里程 整数 中
mile_int_h EQU 45H ;里程 整数 高
; km
mile_km_l EQU 46H ;1000m 低字节
mile_km_h EQU 47H ;1000m 高字节
; Divide Calculation
c_bit EQU 06H ;用于保存 C 的状态
remainder_l EQU 58H ;余数 低
remainder_h EQU 59H ;余数 高
; Display
digit_dot EQU 20H ;小数点位置设置
digit_price_dot_0 EQU 00H ;价格的小数点位置 bit
digit_price_dot_1 EQU 01H
digit_mile_dot_0 EQU 02H ;里程的小数点位置 bit
digit_mile_dot_1 EQU 03H
digit_speed_dot_0 EQU 04H ;速度的小数点位置 bit
digit_speed_dot_1 EQU 05H
;c_bit EQU 06H
zero_speed_bit EQU 07H ;强制速度显示 0 的标志位
digit_price_l EQU 5AH ;价格 低2个BCD
digit_price_h EQU 5BH ;价格 高2个BCD
digit_mile_l EQU 5CH ;里程 低2个BCD
digit_mile_h EQU 5DH ;里程 高2个BCD
digit_speed_l EQU 5EH ;速度 低2个BCD
digit_speed_h EQU 5FH ;速度 高2个BCD
; Run Bit
run_bit EQU 08H ;运行标志位
;====================================================================
; RESET and INTERRUPT VECTORS
;====================================================================
; Reset Vector
org 0000H
jmp Start
org 0003H
clr TR0 ;为提高准确率,先暂停再跳转 ;1MC ;MC表示机器周期
ljmp Pulse ;2MC
org 000BH
setb TR0 ;为提高准确率,立即开始下次计时
ljmp Timer
org 001Bh
ljmp Display
;====================================================================
; CODE SEGMENT
;====================================================================
org 0100h
LED_SEGMENT_CODE:
db 3FH, 06H, 5BH, 4FH, 66H, 6DH, 7DH, 07H, 7FH, 6FH ;数码管0~9
Start:
mov SP, #5FH
mov IP, #00000011B
mov TMOD, #00100001B
mov digit_dot, #00011010B
setb zero_speed_bit
setb ET0
setb ET1
setb EX0
setb IT0
setb TR1
setb TR0
setb EA
Loop:
lcall Calc_Speed ;无论有无按下按键,均显示速度
lcall Bin2BCD ;转化为BCD,用于显示
jb P3.7, Button_Not_Pressed ;按键没有按下
jb run_bit, Button_Pressed ;按键按下,且上一时刻也按下
;按键从未按下到按下,清零里程,并开始计费
mov total_pulse_l, #0
mov total_pulse_h, #0
setb run_bit ;设置run_bit
Button_Pressed:
lcall Calc_Mile ;计算里程
lcall Calc_Price ;计算价格
jmp Loop
Button_Not_Pressed:
clr run_bit ;清除 run_bit
jmp Loop ;当前值不变,直接循环
;====================================================================
; 二进制转压缩BCD
;====================================================================
Bin2BCD:
;转换价格
mov A, price_int
acall Bin2BCD_Int
mov digit_price_h, A
mov A, price_dec
acall Bin2BCD_Dec
mov digit_price_l, A
setb digit_price_dot_1
clr digit_price_dot_0
;转换里程
mov A, mile_int_l
acall Bin2BCD_Int
mov digit_mile_h, A
mov A, mile_dec
acall Bin2BCD_Dec
mov digit_mile_l, A
setb digit_mile_dot_1
clr digit_mile_dot_0
;转换速度
mov A, speed_int_l
acall Bin2BCD_Int
swap A
push Acc
anl A, #0FH
mov R0, A
mov A, B
swap A
orl A, R0
mov digit_speed_h, A
mov A, speed_dec
acall Bin2BCD_Dec
swap A
anl A, #0FH
mov R0, A
pop Acc
anl A, #0F0H
orl A, R0
mov digit_speed_l, A
clr digit_speed_dot_1
setb digit_speed_dot_0
ret
;====================================================================
; 二进制转压缩BCD测试
;====================================================================
Bin2BCD_Test:
; 如果程序正确,显示 10.50 10.50 010.5
lcall Display_Test
mov price_int, #0AH
mov price_dec, #80H
mov mile_int_l, #0AH
mov mile_dec, #80H
mov speed_int_l, #0AH
mov speed_dec, #80H
acall Bin2BCD
ret
;====================================================================
; 单字节二进制转压缩BCD
;====================================================================
Bin2BCD_Int:
; 单字节转BCD
; 参数:A 待转换的字节
; 返回:A 低四位和高四位分别存放个位和十位,B 低四位存放百位
mov B, #100 ;100作为除数送入B中
div AB ;16进制数除以100
mov R1, A ;百位数送R0,余数在B中
mov A, #10 ;分离十位数和个位数
xch A, B ;余数送入A中,除数10放在B中
div AB ;分离出十位放在A中,各位放在B中
swap A ;十位交换到A的高4位
add A, B ;将个位送入A的低4位
mov B, R1 ;将百位送入B
ret
Bin2BCD_Dec:
; 单字节转BCD
; 参数:A 待转换的字节
; 返回:A 高四位和低四位分别存放十分位和百分位
mov B, #10
mul AB ;16进制数乘以10
mov R1, B ;十分位送R0,剩余小数在A中
mov B, #10
mul AB
mov A, B
swap A ;百分位交换到A的高4位
orl A, R1
swap A ;将十分位放到高四位,百分位放到低四位
ret
;====================================================================
; 计算价格
;====================================================================
Calc_Price:
push Acc
Calc_Price_If_Zero:
mov A, mile_int_l
cjne A, #0, Calc_Price_If
mov A, mile_dec
cjne A, #0, Calc_Price_If
mov price_dec, #0
mov price_int, #0
jmp Calc_Price_Return
Calc_Price_If:
mov A, mile_int_l
clr C
subb A, #MILE_THRESHOLD
jnc Calc_Price_Larger ;若里程的整数部分大于等于起步里程则跳转
Calc_Price_Less:
mov price_dec, #0
mov price_int, #START_PRICE
jmp Calc_Price_Return
Calc_Price_Larger:
push Acc ;保存相减后的结果
;多出来的整数x每公里价格的小数部分
mov B, #MILE_PRICE_DEC
mul AB
mov price_dec, A
mov price_int, B
;多出来的整数x每公里价格的整数部分
pop Acc
mov B, #MILE_PRICE_INT
mul AB
add A, price_int
mov price_int, A
; TODO: 处理 B!=0 的情况
mov A, mile_dec
mov B, #MILE_PRICE_INT
mul AB
add A, price_dec
mov price_dec, A
mov A, price_int
addc A, B
mov price_int, A
mov A, mile_dec
mov B, #MILE_PRICE_DEC
mul AB
xch A, B
add A, price_dec
mov price_dec, A
mov A, #START_PRICE
addc A, price_int
mov price_int, A
Calc_Price_Return:
pop Acc
ret
;====================================================================
; 计算价格测试
;====================================================================
Calc_Price_Test:
; 若程序正确,应该会显示 11.9 元
lcall Display_Test
mov mile_int_l, #3
mov mile_dec, #80H
lcall Calc_Price
lcall Bin2BCD
ret
;====================================================================
; 计算里程
;====================================================================
Calc_Mile:
push Acc
Calc_Mile_Mul:
mov A, #WHEEL_C_DEC
mov B, total_pulse_l
mul AB
mov mile_dec, A
mov mile_int_l, B
mov A, #WHEEL_C_INT
mov B, total_pulse_l
mul AB
add A, mile_int_l
mov mile_int_l, A
mov A, #0
addc A, B
mov mile_int_m, A
mov A, #WHEEL_C_DEC
mov B, total_pulse_h
mul AB
add A, mile_int_l
mov mile_int_l, A
mov A, mile_int_m
addc A, B
mov mile_int_m, A
jnc $+4
inc mile_int_h
mov A, #WHEEL_C_INT
mov B, total_pulse_h
mul AB
add A, mile_int_m
mov mile_int_m, A
mov A, mile_int_h
addc A, B
mov mile_int_h, A
Calc_Mile_Div:
mov mile_km_l, #0E8H
mov mile_km_h, #03H
mov R0, #mile_dec
mov R1, #mile_km_l
acall DIV_4_BY_2
pop Acc
ret
;====================================================================
; 计算里程测试
;====================================================================
Calc_Mile_Test:
; 如果程序正确,应该会显示 1 km
lcall Display_Test
mov total_pulse_l, #22H
mov total_pulse_h, #02H
lcall Calc_Mile
lcall Bin2BCD
ret
;====================================================================
; 计算速度
;====================================================================
Calc_Speed:
push Acc
Calc_Speed_Mul:
mov A, #WHEEL_C_DEC
mov B, #6EH
mul AB
mov speed_int_l, A
mov speed_int_m, B
mov A, #WHEEL_C_INT
mov B, #6EH
mul AB
add A, speed_int_m
mov speed_int_m, A
mov A, #0
addc A, B
Calc_Speed_RR:
clr C
rrc A
mov speed_int_h, A
mov A, speed_int_m
rrc A
mov speed_int_m, A
mov A, speed_int_l
rrc A
mov speed_int_l, A
mov A, speed_dec
rrc A
mov speed_dec, A
Calc_Speed_Div:
mov R0, #speed_dec
mov R1, #last_pulse_th0
acall DIV_4_BY_2
pop Acc
ret
;====================================================================
; 计算速度测试一
;====================================================================
Calc_Speed_Test:
lcall Display_Test
mov last_pulse_tl0, #60H
mov last_pulse_th0, #86H
mov last_pulse, #64H
lcall Calc_Speed
lcall Bin2BCD
ret
;====================================================================
; 计算速度测试二
;====================================================================
Calc_Speed_Test1:
mov IP, #00000011B
setb ET0
setb EX0
setb IT0
lcall Display_Test
setb TR0
lcall Calc_Mile
lcall Calc_Speed
lcall Bin2BCD
jmp $-9
ret
;====================================================================
; 除法运算(4位除以2位)
;====================================================================
DIV_4_BY_2:
;R0 存 4 字节被除数的低位地址(小端)
;R1 存 2 字节除数的低位地址(小端)
;结果会保存到被除数的 3 个字节中
;需要给 remainder_l, remainder_h, c_bit 分配空间
;或者可以用 remainder_h EQU R3
mov B, #32 ;循环次数 ;4*8
mov remainder_h, #0
mov remainder_l, #0
DIV_RLC:
clr C ;清空 C,使得循环移位时最低位移入的是 0
;将被除数左移
mov A, @R0 ;取最低位
rlc A ;循环左移
mov @R0, A ;保存最低位
inc R0
mov A, @R0 ;取中间位
rlc A ;循环左移
mov @R0, A ;保存中间位
inc R0
mov A, @R0 ;取中间位
rlc A ;循环左移
mov @R0, A ;保存中间位
inc R0
mov A, @R0 ;取最高位
rlc A ;循环
mov @R0, A ;保存最高位
;恢复 R0 为最低位
mov c_bit, C
dec R0
dec R0
dec R0
mov C, c_bit
;把移出的最高位放入余数
mov A, remainder_l ;取余数低位
rlc A ;循环左移
mov remainder_l, A;保存低位
mov A, remainder_h ;取余数高位
rlc A ;循环左移
mov remainder_h, A;保存高位
mov c_bit, C ;保存余数移出的最高位
DIV_SUB:
;多字节减法
clr C
mov A, remainder_l
subb A, @R1 ;低位相减
mov R7, A
inc R1
mov A, remainder_h
subb A, @R1 ;高位相减
mov R6, A
jb c_bit, DIV_INC ;c_bit 为 1,肯定够减,保存相减后的余数
jc DIV_CONTINUE ;c_bit 为 0,且 C=1,不够减,不保存相减的结果
DIV_INC:
mov remainder_l, R7
mov remainder_h, R6
inc @R0
DIV_CONTINUE:
dec R1 ;恢复 R1 为最低位
djnz B, DIV_RLC
clr C
DIV_RETURN:
RET
;====================================================================
; 除法运算测试
;====================================================================
DIV_4_BY_2_Test:
;如果程序正确,会显示 1.5
lcall Display_Test
mov mile_int_l, #1
mov mile_dec, #80H
mov mile_km_l, #1
mov R0, #mile_dec
mov R1, #mile_km_l
acall DIV_4_BY_2
lcall Bin2BCD
ret
;====================================================================
; 显示(定时器1)
;====================================================================
Display:
; 根据 digit_speed, digit_price, digit_mile 中的压缩BCD来显示
; 根据 digit_speed_dot, digit_price_dot, digit_mile_dot 来显示小数点
push Acc
push PSW
mov DPTR, #LED_SEGMENT_CODE
Display_Price: ;显示价格
mov A, digit_price_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #0
mov P1, A
mov C, digit_price_dot_0
anl C, digit_price_dot_1
nop
mov P1.7, C
mov A, digit_price_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #1
mov P1, A
mov C, digit_price_dot_0
cpl C
anl C, digit_price_dot_1
mov P1.7, C
mov A, digit_price_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #2
mov P1, A
mov C, digit_price_dot_1
cpl C
anl C, digit_price_dot_0
mov P1.7, C
mov A, digit_price_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #3
mov P1, A
mov C, digit_price_dot_1
orl C, digit_price_dot_0
cpl C
mov P1.7, C
Display_Mile: ;显示里程
mov A, digit_mile_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #4
mov P1, A
mov C, digit_price_dot_0
anl C, digit_price_dot_1
nop
mov P1.7, C
mov A, digit_mile_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #5
mov P1, A
mov C, digit_mile_dot_0
cpl C
anl C, digit_mile_dot_1
mov P1.7, C
mov A, digit_mile_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #6
mov P1, A
mov C, digit_mile_dot_1
cpl C
anl C, digit_mile_dot_0
mov P1.7, C
mov A, digit_mile_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #7
mov P1, A
mov C, digit_mile_dot_1
orl C, digit_mile_dot_0
cpl C
mov P1.7, C
Display_Speed: ;显示速度
jnb zero_speed_bit, Display_Speed_Normal
mov digit_speed_h, #0
mov digit_speed_l, #0
Display_Speed_Normal:
mov A, digit_speed_h
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #8
mov P1, A
mov C, digit_speed_dot_0
anl C, digit_speed_dot_1
nop
mov P1.7, C
mov A, digit_speed_h
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #9
mov P1, A
mov C, digit_speed_dot_0
cpl C
anl C, digit_speed_dot_1
mov P1.7, C
mov A, digit_speed_l
swap A
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #10
mov P1, A
mov C, digit_speed_dot_1
cpl C
anl C, digit_speed_dot_0
mov P1.7, C
mov A, digit_speed_l
anl A, #0FH
movc A, @A+DPTR
mov P1, #0
mov P2, #11
mov P1, A
mov C, digit_speed_dot_1
orl C, digit_speed_dot_0
cpl C
mov P1.7, C
Display_Return:
pop PSW
pop Acc
mov P1, #0
reti
;====================================================================
; 显示测试
;====================================================================
Display_Test:
; 如果程序正确,会显示 12.34 23.45 345.6
clr EA
mov TMOD, #00100001B
mov digit_price_h, #12H
mov digit_price_l, #34H
mov digit_mile_h, #23H
mov digit_mile_l, #45H
mov digit_speed_h, #34H
mov digit_speed_l, #56H
mov digit_dot, #00011010B
setb ET1
setb TR1
setb EA
ret
;====================================================================
; 脉冲(外部中断0)
;====================================================================
Pulse:
push Acc
push PSW ;2MC
mov last_pulse_tl0, tl0 ;2MC
mov last_pulse_th0, th0 ;2MC
mov last_pulse, this_pulse ;2MC
mov tl0, #16 ;需要加上执行指令占用的时间 ;2MC
mov th0, #0 ;2MC
setb TR0 ;1MC
mov this_pulse, #0
;时间的四舍五入
mov A, #80H
add A, last_pulse_tl0
mov A, last_pulse_th0
addc A, #0
mov last_pulse_th0, A
clr zero_speed_bit
;增加总脉冲数
inc total_pulse_l
mov A, total_pulse_l
cjne A, #0, Pulse_Return
inc total_pulse_h
; TODO: 处理总脉冲次数溢出
Pulse_Return:
pop PSW
pop Acc
reti
;====================================================================
; 计时(定时器0)
;====================================================================
Timer:
;注:setb TR0 在前面的 org 000BH 后面
push Acc
push PSW
inc this_pulse ;增加本次的溢出次数
mov A, last_pulse
clr C
subb A, this_pulse ;本次脉冲间隔超过上次的时间,说明速度变慢了
jnc Timer_Return
setb zero_speed_bit ;直接显示0
;下面是不直接显示0,而是逐渐减小到0
;在仿真时发现效果不好,便放弃
;mov A, #1
;add A, last_pulse_th0
;mov last_pulse_th0, A
;jnc Timer_Return
;inc last_pulse
;mov A, last_pulse
;cjne A, #090H, pulse_Return
;Timer_Clr_Speed:
; mov last_pulse, #090H
Timer_Return:
pop PSW
pop Acc
reti
;====================================================================
END
总结
答辩之后,老师认为既然速度的数码管可以显示 100,那也应该可以显示大于 255 的数,但我觉得实际中不可能出现大于 255km/h 的速度。这是小问题,只需要判断一下 last_pulse 是否为0,如果为0,那么就使用 last_pulse_tl0 和 last_pulse_th0 来作为被除数,然后最后结果左移 8 位即可。
另外,老师说可以计算 1s 内的脉冲数来计算速度,这样确实更简单,但是实时性不如我的实现方式。
总之,这次大作业着实让我体会到了在 51 上做数学运算的痛苦。