微机课程设计:用汇编写一个计费器

再次享受操纵所有寄存器的快感~

简介

  没错,我又要写一次这个玩意了,为什么呢?因为计算机学院并不认电信学院的微机,所以我不得不再学了一次 8051。不得不说,计算机学院讲得清楚易懂多了,因为电信学院是 8086+8051 一起教的(电信的课程设置得真不合理🙃)

  来说说要求吧!要求如下:

  1. 用Proteus设计原理图,要求显示出 里程速度总价
  2. 使用一个按钮,按下按钮开始计费,松开按钮停止计费
  3. 用信号发生器来产生出租车的模拟信号(轮子转一圈产生一个脉冲)
  4. 出租车轮胎周长按 1.83 米计算。2 公里以内按 8 元计算,超过 2 公里每公里按 2.6 元计算。不考虑其他费用

  这不是很简单嘛,弄一个外部中断来统计脉冲的次数,从而算出里程和价格,然后再弄一个计时器来计算两次脉冲的时间,从而算出速度,搞定!有两点要注意:

  • 计算价格时要判断是否大于 0,再判断是否大于 2
  • 计算速度时,要注意当太久没脉冲时,要降低速度

  这个程序的难点在于数学计算。

程序设计

这个程序分为四部分:

  • 主程序:
    • 判断按键是否按下
    • 计算里程、速度、总价
  • 定时器0:
    • 计时
  • 定时器1:
    • 显示
  • 外部中断0:
    • 脉冲

主要需要如下常量和变量:

  • 常量:
    • n 公里 MILE_THRESHOLD
    • 起步价 START_PRICE
    • n 公里后每公里价格 MILE_PRICE_INTMILE_PRICE_DEC
    • 轮胎周长 WHEEL_C_INTWHEEL_C_DEC
    • 数码管显示码 LED_DIGIT_CODE
  • 变量
    • 上个脉冲的时间 last_pulsetast_pulse_tl0tast_pulse_th0
    • 当前脉冲的时间 this_pulse
    • 总脉冲次数 total_pulse_htotal_pulse_l
    • 速度 speed_intspeed_dec
    • 价格 price_intprice_dec
    • 里程 mile_intmile_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.

  至于计时嘛,这就要讨论一下了。计时主要用于计算两个脉冲之间间隔的时间,怎么计呢?步骤如下:

  1. 当有脉冲来时,读取定时器的数值,然后清零,开始对下一个脉冲计时。
  2. 如果定时器到时间后还没有脉冲,就给变量 this_pulse 加一(相当于扩展了定时器的位)。
  3. 最终计算的时间包括定时器的时间(低2bytes)和 this_pulse(高tyte)。
  4. 下一个脉冲来时重复 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. 1 - 11 不够减,所以结果最高位为 0
    2. 10 - 11 不够减,所以结果第二位为 0
    3. 101 - 11 = 10,所以结果第三位为 1
    4. 101 - 11 = 10,所以结果第四位为 1
    5. 如果不需要算小数,那么结果就是 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
  • 程序有些细节与上面讨论的略有出入

常量与变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
;====================================================================
; 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

初始化与主函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
;====================================================================
; 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 ;当前值不变,直接循环

计算

除法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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

  注意这个函数的结果会覆盖被除数!

计算价格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
;====================================================================
; 计算价格
;====================================================================
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

计算里程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
;====================================================================
; 计算里程
;====================================================================
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

计算速度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
;====================================================================
; 计算速度
;====================================================================
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
;====================================================================
; 二进制转压缩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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
;====================================================================
; 计时(定时器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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
;====================================================================
; 显示(定时器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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
;====================================================================
; 脉冲(外部中断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%|

总代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
;====================================================================
; 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 ;价格 2BCD 
digit_price_h EQU 5BH ;价格 2BCD
digit_mile_l EQU 5CH ;里程 2BCD 
digit_mile_h EQU 5DH ;里程 2BCD
digit_speed_l EQU 5EH ;速度 2BCD 
digit_speed_h EQU 5FH ;速度 2BCD

; 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 上做数学运算的痛苦。