基于ATMega16的数码管时钟显示实例(汇编)

本例在ATMega16上,利用汇编程序通过8个七段数码实现具有时分秒的实时时钟显示,主要讨论定时器T2中异步时钟的使用方法及时钟信号的产生。

本例中的8位数码管采用两个4位的组合而成,段码端通过限流电阻及跳线帽接在PB端口,位选端通过PNP三极管扩流后接在PA端口,电路如下图所示。

完整的汇编代码如下。

.INCLUDE "M16DEF.INC"
.DEF    TMP = R16                ;定义R16寄存器的别名(注意小于16号的寄存器不能进行LDI操作)
.DEF    CNT = R17
.DEF    SHIFT = R18
.DSEG                            ;以下数据放置在Data区域,即RAM中
.ORG    $0060                    ;从$60地址开始存放,即RAM的起始地址
.EQU    SECOND = $60             ;定义一个存放秒的RAM空间别名
.EQU    MINUTE = $61             ;定义一个存放分的RAM空间别名
.EQU    HOUR = $62               ;定义一个存放时的RAM空间别名
.CSEG                            ;以下数据放置在Code区域,即Flash中
.ORG    $0000
    JMP        RESET             ;复位的向量入口,地址为$00
    NOP                          ;空指令
    RETI                         ;中断返回
    NOP
    RETI
    NOP
    RETI
    NOP
    RJMP        TIME2_OVF         ;定时器2的溢出中断向量入口,地址为$10
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RJMP        TIME0_OVF          ;定时器0的溢出中断向量入口,地址为$12
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
    NOP
    RETI
;前面最后一个中断向量入口地址是$28,双字后刚好是$2A
RESET:
    LDI     TMP, HIGH(RAMEND)    ;获取RAM空间的最大地址高字节(ATMega16为$04)
    OUT     SPH, TMP             ;高字节送SP高位
    LDI     TMP, LOW(RAMEND)     ;获取RAM空间的最大地址低字节(ATMega16为$5F)
    OUT     SPL, TMP             ;低字节送SP低位
    SER     TMP                  ;把R16全部置1
    OUT     DDRA, TMP            ;把端口A设置为输出方向
    OUT     DDRB, TMP            ;把端口B设置为输出方向
    LDI     SHIFT, $FE           ;从端口A的第0位开始扫描
    LDI     ZL, LOW(LED7 * 2)    ;获取字形码所在的首地址低字节
    LDI     ZH, HIGH(LED7 * 2)   ;获取字形码所在的首地址高字节,乘2的目的是让整个字地址左移1位,空出最低位来作为高/低字节选择
    CLR     TMP
    STS     SECOND, TMP          ;清0秒的值        
    LDI     TMP, $59
    STS     MINUTE, TMP
    LDI     TMP, $23
    STS     HOUR, TMP
;Timer0溢出中断配置
    LDI     TMP, $06
    OUT     TCNT0, TMP            ;以上为设置定时器0的初始值
    IN      TMP, TCCR0
    ORI     TMP, $03
    OUT     TCCR0, TMP            ;设置为64分频,根据上面的初始值,8MHz晶振对应2ms
    IN      TMP, TIMSK
    ORI     TMP, $01
    OUT     TIMSK, TMP            ;允许定时器0的溢出中断
;Timer2溢出中断配置(异步方式)
    LDI     TMP, $08
    OUT     ASSR, TMP             ;设置定时器2为异步时钟模式
W0: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W0                    ;等待ASSR寄存器的低三位为0
    LDI     TMP, $05
    OUT     TCCR2, TMP            ;设置为128分频
W1: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W1                    ;等待ASSR寄存器的低三位为0
    CLR     TMP
    OUT     TCNT2, TMP            ;计数值置0
W2: IN      TMP, ASSR    
    CPI     TMP, $08
    BRNE    W2                    ;等待ASSR寄存器的低三位为0    
    IN      TMP, TIFR
    ORI     TMP, $40
    OUT     TIFR, TMP             ;强制清除定时器2的中断标志位            
    IN      TMP, TIMSK
    ORI     TMP, $40
    OUT     TIMSK, TMP            ;允许定时器2的溢出中断
    SEI                           ;开启总中断
    SEC                           ;进位位置1
LOOP:
    RJMP    LOOP
;定时器2溢出中断服务程序(时钟)
TIME2_OVF:                        
    LDS     TMP, SECOND            ;获取当前秒的值
    INC     TMP                    ;秒加1
    CPI     TMP, $5A               ;秒是否到60
    BREQ    M                      ;秒到60则跳到M行执行
    CPI     TMP, 10                ;秒未60时,如果个位大于10(0x0A),则进行BCD调整
    BRHS    SOK                    ;若半进位位H为1,则跳到SOK行执行
    SUBI    TMP, $FA               ;减去$FA,进行BCD调整
SOK:
    STS     SECOND, TMP            ;回写秒的值
    RETI                           ;中断返回
M:                                 ;分部分
    CLR      TMP        
    STS      SECOND, TMP           ;秒清0
    LDS      TMP, MINUTE           ;获取当前分的值
    INC      TMP                   ;分加1
    CPI      TMP, $5A              ;分是否到60
    BREQ     H                     ;分到60则跳到H行执行
    CPI      TMP, 10               ;分未60时,如果个位大于10(0x0A),则进行BCD调整
    BRHS     MOK                   ;若半进位位H为1,则跳到MOK行执行
    SUBI     TMP, $FA              ;减去$FA,进行BCD调整
MOK:
    STS      MINUTE, TMP           ;回写分的值
    RETI                           ;中断返回
H:                                 ;时部分
    CLR      TMP
    STS      MINUTE, TMP           ;分清0
    LDS      TMP, HOUR             ;获取当前时的值
    INC      TMP                   ;时加1
    CPI      TMP, $24              ;时是否到24
    BREQ     R                     ;时到24则跳到R行执行
    CPI      TMP, 10               ;时未24时,如果个位大于10(0x0A),则进行BCD调整
    BRHS     HOK                   ;若半进位位H为1,则跳到HOK行执行
    SUBI     TMP, $FA              ;减去$FA,进行BCD调整
HOK:
    STS      HOUR, TMP             ;回写时的值
    RETI                           ;中断返回
R:
    CLR      TMP
    STS      HOUR, TMP             ;时清0
    RETI                           ;中断返回
;定时器Timer0溢出中断服务程序
TIME0_OVF:
    LDI      TMP, $06
    OUT      TCNT0, TMP            ;重载定时器初始值
    SBRS     SHIFT, 7              ;扫描的第7位为1则继续看第6位
    RJMP     MG                    ;扫描的第7位为0则跳到MG(秒的个位)去执行
    SBRS     SHIFT, 6              ;扫描的第6位为1则继续看第5位
    RJMP     MS                    ;扫描的第6位为0则跳到MS(秒的十位)去执行
    SBRS     SHIFT, 5              ;扫描的第5位为1则继续看第4位
    RJMP     FGF                   ;扫描的第5位为0则跳到FGF(分隔符)去执行
    SBRS     SHIFT, 4              ;扫描的第4位为1则继续看第3位
    RJMP     FG                    ;扫描的第4位为0则跳到FG(分的个位)去执行
    SBRS     SHIFT, 3              ;扫描的第3位为1则继续看第2位
    RJMP     FS                    ;扫描的第3位为0则跳到FS(分的十位)去执行
    SBRS     SHIFT, 2              ;扫描的第2位为1则继续看第1位
    RJMP     FGF                   ;扫描的第2位为0则跳到FGF(分隔符)去执行
    SBRS     SHIFT, 1              ;扫描的第1位为1则继续看第0位
    RJMP     SG                    ;扫描的第1位为0则跳到SG(时的个位)去执行
    SBRS     SHIFT, 0              ;扫描的第0位为1则跳一行执行
    RJMP     SS                    ;扫描的第0位为0则跳到SS(时的十位)去执行
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    RETI                           ;中断返回
SS:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, HOUR             ;获取时的当前值
    ANDI     CNT, $F0              ;屏蔽低4位
    SWAP     CNT                   ;高低4位交换
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第7位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     BRIDGE                ;如查进位位C的值为0,则跳到NEXT执行(此处超出了跳转范围,故借用RJMP来实现),否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回
BRIDGE:                            ;桥接跳转    
    RJMP     NEXT
SG:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, HOUR             ;获取时的当前值
    ANDI     CNT, $0F              ;屏蔽高4位
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第6位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回
FS:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, MINUTE           ;获取时的当前值
    ANDI     CNT, $F0              ;屏蔽低4位
    SWAP     CNT                   ;高低4位交换
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第4位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回
FG:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, MINUTE           ;获取时的当前值
    ANDI     CNT, $0F              ;屏蔽高4位
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第3位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回
MS:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, SECOND           ;获取时的当前值
    ANDI     CNT, $F0              ;屏蔽低4位
    SWAP     CNT                   ;高低4位交换
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第1位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回
MG:
    LDI      ZL, LOW(LED7 * 2)     ;获取字形码所在的首地址低字节,字形码的数量不超过255,所以可以不用再次获取地址高字节,因为没改动过
    LDS      CNT, SECOND           ;获取时的当前值
    ANDI     CNT, $0F              ;屏蔽高4位
    ADD      ZL, CNT               ;低字节加上要显示的时十位值
    LPM                            ;查表后的内容送入R0中
    OUT      PORTB, R0             ;把R0的内容送显
    OUT      PORTA, SHIFT          ;打开显示的第0位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI                           ;如果还没扫描到头,则中断返回            
FGF:
    LDI      TMP, $FD
    OUT      PORTB, TMP            ;把字符-送显
    OUT      PORTA, SHIFT          ;打开显示的第6位和第1位
    SEC                            ;进位位C置1,由于下面用到带进位位的左移
    ROL      SHIFT                 ;带进位位左移
    BRCC     NEXT                  ;如查进位位C的值为0,则跳到NEXT执行,否则顺序执行
    RETI
NEXT:                              ;扫描到头
    LDI      SHIFT, $FE            ;从端口A的第0位开始扫描
    RETI                           ;中断返回
.CSEG                              ;以下数据放置在Code区域,即Flash中
LED7:                              ;字形码数据,以字节方式顺序存放
    .DB $03,$9F,$25,$0D,$99,$49,$41,$1F
    .DB $01,$09,$11,$C1,$63,$85,$61,$71

本例使用到了AVR中I/O空间的13个寄存器,即SPH、SPL、DDRA、DDRB、PORTA、PORTB、TCNT0、TCNT2、TCCR0、TCCR2、ASSR、TIFR和TIMSK。其中SPH、SPL、DDRA、DDRB、PORTA、PORTB等6个寄存器的介绍可参考“基于ATMega16的流水灯实例”一文,TIMSK寄存器可参考“基于ATMega16的数码管动态扫描实例”一文。另外,本例还使用了RAM空间的3个字节,分别作为时、分、秒的存储单元。

先来看TCNT0寄存器和TCNT2寄存器,它们分别是定时器T0和T2的计数寄存器,其结构完全一样,这里就只给出TCNT0的具体结构,如下表所示。

定时器T0和T2的计数位宽均为8位,初始值为0,最大计数值为255。要改变它们的初始计数值,直接写寄存器即可。 

再来看TCCR0寄存器,它是定时器T0的控制寄存器,初始值为全0,具体如下表所示。

T0的工作模式由第3、6两位(WGM01 、WGM00)来决定,这里需要它们都为0,即让T0工作在普通定时模式。低3位(CS00~CS02)用来确定时钟的分频情况,它与定时器T1中的CS10~CS12完全一样,具体可参考“基于ATMega16的数码管动态扫描实例”一文中的相关部分,这里就不在重复了。

接下来看TCCR2寄存器,它是定时器T2的控制寄存器,初始值为全0,具体如下表所示。

T2的工作模式由第3、6两位(WGM21 、WGM20)来决定,这里需要它们都为0,即让T2工作在普通定时模式。低3位(CS20~CS22)用来确定时钟的分频情况,这里与T0、T1中的不完全一样,具体如下表所示。

在本例中,由于T2采用异步晶振的方式,晶振频率为32768Hz,所以上面3位设置为101,即选择128分频,分频后的频率为256Hz。这样在T2计数256次之后,就可以产生精确的秒信号。

下面来看ASSR寄存器,它是异步状态寄存器,为定时器T2所特有,初始值为全0,具体如下表所示。

第3位为异步设定位,写0时为系统时钟模式,写1时为异步时钟模式。在异步模式下,由于与系统时钟有差异,所以在对TCCR2、OCR2、TCNT2等寄存器进行写入时,需要等待。第0~2位分别代表相应寄存器的更新忙标志位,只有等待值变为0时,才能对相应的寄存器进行写入操作。需要说明的是,在进行异步模式切换时,可能会损坏TCCR2、OCR2、TCNT2这3个寄存器原来的值,因此建议在切换完成后,再对这3个寄存器进行配置。

最后看TIFR寄存器,它是中断标志寄存器,为T0、T1和T2所共有,初始值为全0,具体如下表所示。

其中的第0、2、6三位分别用来标志T0、T1、T2的溢出中断,即当定时器有溢出中断发生时,相应的位会被硬件置1 ,中断响应后会自动清零,写1将强制清零该位。

本例中一共使用到了27种指令,其中的JMP、RJMP、LDI、OUT、SER、SEC、ROL、BRNE等8条指令可参考“基于ATMega16的流水灯实例”一文。 NOP、RETI、CLR、SEI、INC、ADD、LPM、BRCC等8条指令可参考基于ATMega16的数码管动态扫描实例“”,其余11条指令解释如下。

1)寄存器数据直接送SRAM
  STS  k,Rr  0 ≤ r ≤ 31,0 ≤ k ≤ 65535
说明:将寄存器中的内容直接存储到数据存储空间中。对于带有SRAM的芯片,数据空间由寄存器堆,I/O 存储器和内部SRAM 存储器组成。对存储器数据的访问被限定在当前数据段的64K 字节的空间。该指令不一定支持所有的AVR芯片。
操作:(k) ← Rr  PC ← PC + 2   32位机器码:1001 001d dddd 0000 kkkk kkkk kkkk kkkk
2)I/O空间数据送寄存器
  IN   Rd, A    0 ≤ d ≤ 31,0 ≤ A ≤ 63
说明:将I/O空间的数据传送到寄存器Rd中。
操作:Rd ← I/O(A)    PC ← PC + 1   16位机器码:1011 0AAd dddd AAAA
3)“或”立即数
  ORI   Rd, K    16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:将寄存器Rd的值与常量进行“或”操作,结果送入寄存器Rd中。
操作:Rd ← Rd Ⅴ K    PC ← PC + 1   16位机器码:0110 KKKK dddd KKKK
4)与立即数比较
  CPI   Rd, K    16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:完成寄存器Rd和常数的比较操作,寄存器的内容不改变,该指令后能使用所有条件跳转指令。
操作:Rd — K    PC ← PC + 1   16位机器码:0011 KKKK dddd KKKK
5)SRAM数据直接送寄存器
  LDS  Rd,k  0 ≤ d ≤ 31,0 ≤ k ≤ 65535
说明:从数据区中指定的位置装入一个字节的数据到寄存器。对于带有SRAM的芯片,数据空间由寄存器堆,I/O 存储器和内部SRAM 存储器组成。对存储器数据的访问被限定在当前数据段的64K 字节的空间。该指令不一定支持所有的AVR芯片。
操作:Rr ← (k)  PC ← PC + 2   32位机器码:1001 000d dddd 0000 kkkk kkkk kkkk kkkk
6)相等跳转
  BREQ  k  -64 ≤ k ≤ 63
说明:条件相对跳转,测试零标志位Z,如果Z位被置位,则相对PC值跳转k个字。如果在执行CP、CPI、SUB 或SUBI 指令后,立即执行该指令,且当寄存器Rd中数与寄存器 Rr中数相等时,将发生跳转。可跳转k个字,k 为7位带符号数,最多可向前跳63个字,向后跳64个字。这条指令相当于指令“BRBS 1,k”。
操作:If Rd = Rr (Z = 1) then PC ← PC + k + 1, else PC ← PC + 1  16位机器码:1111 00kk kkkk k001
7)半进位标志为1跳转
  BRHS  k  -64 ≤ k ≤ 63
说明:条件相对跳转,测试半进位标志H,如果H位被置位,则相对PC值跳转k个字。k 为7位带符号数,最多可向前跳63个字,向后跳64个字。该指令相当于指令“BRBS 5,k”。
操作:If H = 1 then PC ← PC + k + 1, else PC ← PC + 1  16位机器码:1111 00kk kkkk k101
8)减立即数
  SUBI   Rd, K    16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:寄存器Rd的内容和常数相减,结果送目的寄存器Rd。该指令工作于寄存器R16~R31之间,非常适合X、Y 和Z指针的操作。
操作:Rd ← Rd - K    PC ← PC + 1   16位机器码:0101 KKKK dddd KKKK
9)寄存器位为1跳行
  SBRS  Rr,b  0 ≤ r ≤ 31,0 ≤ b ≤ 7
说明:测试寄存器的某一位,如果这一位为1则跳过下一条指令。
操作:If Rr(b) = 1 then PC ← PC + 2 (or 3) else PC ← PC + 1  16位机器码:1111 111r rrrr 0bbb
10)“与”立即数
  ANDI   Rd, K    16 ≤ d ≤ 31,0 ≤ K ≤ 255
说明:寄存器Rd的内容和常数逻辑与,结果送目的寄存器Rd。
操作:Rd ← Rd · K    PC ← PC + 1   16位机器码:0111 KKKK dddd KKKK
11)寄存器半字节交换
  SWAP   Rd    0 ≤ d ≤ 31
说明:将一个寄存器中的高四位与低四位进行交换。
操作:Rd(7:4) ← Rd(3:0),Rd(3:0) ← Rd(7:4)    PC ← PC + 1   16位机器码:1001 010d dddd 0010

此外,程序中还用到了另外一些伪指令(.INCLUDE、.DEF、.ORG可参考“基于ATMega16的流水灯实例”一文,.CSEG、.DB可参考“基于ATMega16的数码管动态扫描实例”一文),具体解释如下。 

1)声明数据段(SRAM)
语法:.DSEG
说明:DSEG伪指令声明数据段的起始。一个汇编程序文件可以包含几个数据段,这些数据段在汇编过程中被连接成一个数据段。在数据段中,通常仅由BYTE伪指令(和标号)组成。每个数据段内部都有自己的字节定位计数器。可使用ORG伪指令定义该字节定位计数器的初始值,作为数据段在SRAM中的起始位置。DSEG伪指令不带参数。
2)定义标识符常量
语法:.EQU 标号 = 表达式
EQU伪指令将表达式的值赋给一个标识符,该标识符为一个常量标识符,可以用于后面的指令表达式中,在汇编时凡遇到该标识符都以其等值表达式替代。在编写程序中,只要修改此表达式,就修改了程序中多处涉及该表达式的地方,减少了程序的修改量。但该标识符的值不能改变或重新定义。

下面对程序中的相关部分进行一下说明。
1)ATMega16可以让定时器T2工作在异步模式下,即工作在低频晶振模式下。一般在引脚PC6和PC7之间接一个32768Hz的时钟晶振,让T2在异步模式下使用。在T2中选择时钟的128分频,然后让计数器计满256次后产生溢出中断,则此时T2的中断时间刚好就是1秒。这样做的好处在于,避免在使用较高频率系统晶振的情况下,通过8位定时器难以精确地实现较长时间的定时。
2)定时器T2工作在异步模式时,对其寄存器的操作由于处于不同的时钟频率之下,所以需要等待。即在操作完TCCR2、OCR2、TCNT2等寄存器之后,需要等待他们完成。或者说在操作他们之前需要判忙,不忙才能进行操作。另外,为了保险,在配置完成T2之后,还需要强制清一下T2的中断标志位再使用。
3)有了秒时钟之后,再产生出分和时,并在SRAM中开辟三个字节空间,分别用于存放实时的时、分、秒。申请的空间最好位于SRAM的最低地址处(因为最高地址被用作了堆栈空间)。在ATMega16中,数据空间一共包含了三类空间,地址从低到高依次是通用寄存器空间、I/O寄存器空间和内部SRAM空间。而SRAM空间是从地址$0060开始到地址$045F结束,所以本例中申请的时、分、秒空间被指定在地址$62~$60处。
4)定时器T2产生出的时、分、秒等计时信号的值,需要作BCD调整,以适应时钟的计时规则。由于时、分、秒最大都只有两位数,且十位的最大值只到5,所以只需要对个位进行调整即可。调整原理是这样,判定个数位在加1后是否大于10(十六进制$A),若不大于则取实际的值,若大于则减去一个十六进制数$FA,这样所得结果就调整过来了。比如,假设当前秒的值为十六进制的$09,则在加1后为十六进制的$0A,而非想要的$10。这时进行$0A-$FA的操作,其结果的最后两位就是$10(前面的若干个F表示负数,不用管它)。然后再把$10回存到秒的存储单元(SRAM中地址为$60处)中,以后取出来再加1,就又回到个位数不超过10的操作了,如此循环。所谓的BCD调整,其实就是让十六进制数“看起来”像十进制数一样的操作。虽然本质上还是十六进制,但“看上去”就像是十进制一样。在本例中,使用了CPI指令来比较,其本质是比较的两个数相减,这里,当小于10的数与10相减时,半进位位H会被置1(因为产生了借位),所以紧接着使用判定半进位位的跳转指令BRHS进行分支判断,H为1时直接回存,H为0时进行BCD调整(减去$FA)后回存。
5)在分和秒的边界(最大值)判定上,由于是在加1之后BCD调整之前,所以对比的值就是真实的十六进制数,即在判断是否大于60时,并不是与$60比较,而是与$5A比较(因为BCD调整前,$59+1=$5A,调整后才为$60)。但时的边界判定就不存在此问题了(因为$23+1=$24)。
6)本例的动态扫描原理与“基于ATMega16的数码管动态扫描实例”一文中的一样,只不过把所使用的定时器换成了T0。这里要强调的是,如何把时间的个位和十位拆分开来显示。在本例中,当取低4位(个位数)时,直接把高4位屏蔽。取高4位(十位数)时,先把高低4位交换,再屏蔽高4位。

posted @ 2024-01-18 17:52  fxzq  阅读(171)  评论(0编辑  收藏  举报