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

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

May 30, 2021·
ToddZ

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

简介

  没错,我又要写一次这个玩意了,为什么呢?因为计算机学院并不认电信学院的微机,所以我不得不再学了一次 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
  • 程序有些细节与上面讨论的略有出入

常量与变量

详细代码
;====================================================================
; 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)理论值显示值误差
16.5886.51.34%
319.76419.70.32%
532.9432.90.12%
746.11646.10.035%
959.29259.30.013%
1172.46872.50.044%
1385.64485.80.18%
1598.82990.18%
17111.996111.90.085%
19125.172124.90.22%

注意到最大的误差仅为 1.34%,在接受范围内。


选取不同里程,计算价格的理论值,并与实际显示的值相比较:

路程理论值显示值误差
2.388.988.990.11%
5.5917.33417.340.034%
9.9228.59228.570.077%
13.3337.45837.450.021%
19.9554.6754.630.055%
28.5577.0376.960.091%
33.2189.14689.090.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 上做数学运算的痛苦。

最后更新于