machao 发表于 2005-2-26 22:10:57

8段LED测试:简易时钟(AVR汇编)

由M128移植到M16上实现,通过。(AVR Studio V4.11)



    该系统为一个带1/100秒的简易24小时制时钟,它在上电后能够自动从11时59分55秒00开始计时和显示时间。

    系统使用板上8个LED数码管显示 时、分、秒、1/100秒 4个时段的数字,每个时段占用2个LED。

    显示方式采用动态扫描方式,M16的PA口输出显示数字的7段码,PC口用于控制8个LED的位选。   

    M16使用外部(或内部)4MHz晶振。

    使用M16片内的计数/定时器T1,设计T1工作在定时溢出中断方式,定时间隔为2ms,即T1每2ms产生一次中断。5次中断得到10ms的时间间隔,此时时钟的1/100秒加1,并相应进行时、分、秒的调整。

    LED动态扫描方式的设计如下:在每2ms的时间中,点亮8个LED中的一个,显示其相应的数字(PC口的输出只有一位为低电平,选通一个LED,保持2ms)。因此PC口的输出值为0b11111110,每隔2ms循环右移,到0b01111111时8个LED各点亮一次,时间为16ms。在1秒钟内,循环8个LED的次数为62.5(1000/16),是人眼的滞留时间(25次/秒)的2.5倍,保证了LED显示亮度均匀,无闪烁。在程序设计中,在各个LED转换和7段码输出时,关闭位选信号(PC输出0b11111111),消除了显示的拖尾现象(消影功能)。

    T1的设计:T1为16位定时器,系统时钟为4M,采用其64分频后的时钟作为T1的计数信号(寄存器TCCR1B = 0x03),一个计数周期为16us,2ms需要计125个(0x007D)。由于T1溢出中断发生在0xFFFF后下一个T1计数脉冲的到来(参见第二章关于定时器原理部分),因此T1的计数初始值为0xFF83=0XFFFF-0X007C(65535-124)。



;********************************************************

;AVR汇编程序实例

;简易带1/100秒的24小时制时钟

;Mega164MHz

;********************************************************

.include "m16def.inc"                                ;引用器件I/O配置文件



;定义程序中使用的变量名(在寄存器空间)

.def count                =        r18                ;循环计数单元

.def position        =        r19                ;LED显示位指针,取值为0-7

.def p_temp         =        r20                ;LED显示位选,其值取反由PC口输出

.def count_10ms        =        r21                ;10ms计数单元

.def flag_2ms        =        r22                ;2ms到标志

.def temp                =        r23                ;临时变量

.def temp1                =        r24                ;临时变量

.def temp_int        =        r25                ;临时变量(在中断中使用)



;中断向量区定义,flash程序空间$000-$029

.org $000

        rjmp reset                ;复位处理

        nop

        reti                ;IRQ0 Handler

        nop

        reti                ;IRQ1 Handler

        nop

        reti                ;Timer2 Compare Handler

        nop

        reti                ;Timer2 Overflow Handler

        nop

        reti                ;Timer1 Capture Handler

        nop

        reti                ;Timer1 Compare-A Handler

        nop

        reti                ;Timer1 Compare-B Handler

        nop

        rjmp time1_ovf        ;Timer1 Overflow Handler

        nop

        reti                ;Timer0 Overflow Handler

        nop

        reti                ;SPI Transfer Complete Handler

        nop

        reti                ;USART RX Complete Handler

        nop

        reti                ;USART UDR Empty Handler

        nop

        reti                ;USART TX Complete Handler

        nop

        reti                ;ADC Conversion Complete Handler

        nop

        reti                ;E2PROM Ready Handler

        nop

        reti                ;Analog Comparator Handler

        nop

        reti                ;Two-wire Serial Interface Handler

        nop

        reti                ;IRQ2 Handler

        nop

        reti                ;Timer0 Compare Handler

        nop

        reti                ;SPM Ready Handler

        nop               



;程序开始

.org $02A

reset:

ldi r16,high(RAMEND)        ;设置堆栈指针高位

out sph,r16

ldi r16,low(RAMEND)                ;设置堆栈指针低位

out spl,r16

       

        ser temp                       

        out ddra,temp        ;设置PORTA为输出,段码输出

        out ddrc,temp        ;设置PORTC为输出,位码控制

        out portc,temp        ;PORTC输出$FF, 无显示



        ldi position,0x00        ;段位初始化为1/100秒低位

        ldi p_temp,0x01        ;LED第1位亮



;初始化时钟时间为11:59:55:00

        ldi xl,low(time_buff)        ;

        ldi xh,high(time_buff)        ;X寄存器取得时钟单元首指针

        ldi temp,0x00

        stx+,temp                ;1/100秒 = 00

        ldi temp,0x55

        stx+,temp                ;秒 = 55

        ldi temp,0x59

        stx+,temp                ;分 = 59

        ldi temp,0x11

        stx,temp                        ;时 = 11



        ldi temp,0xff                ;T1初始化,每隔2ms中断一次

        out tcnt1h,temp

        ldi temp,0x83

        out tcnt1l,temp

        clr temp

        out tccr1a,temp

        ldi temp,0x03                ;4M,64分频 2ms

        out tccr1b,temp

        ldi temp,0x04

        out timsk,temp                ;允许T1溢出中断

        sei                        ;全局中断允许



;主程序

main:

        cpi flag_2ms,0x01                ;判2ms到否

        brne main                        ;No,转main循环

        clr flag_2ms                ;到,请2ms标志

        rcall display                ;调用LED显示时间(动态扫描显示一位)

d_10ms_ok:

        cpi count_10ms,0x05                ;判10ms到否

        brne main                        ;No,转main循环

        clr count_10ms                ;10ms到,清零10ms计数器

        rcall time_add                ;调用时间加10ms调整

        rcall put_t2d                ;将新时间值放入显示缓冲单元

        rjmp main                        ;转main循环



;LED动态扫描显示子程序,2ms执行一次,一次点亮一位,8位循环

display:

        clr r0

        ser temp                        ;temp = 0x11111111

        out portc,temp                ;关显示,去消影和拖尾作用

        ldi yl,low(display_buff)

        ldi yh,high(display_buff)        ;Y寄存器取得显示缓冲单元首指针

        add yl,position                ;加上要显示的位值

        adc yh,r0                        ;加上低位进位

        ld temp,y                        ;temp中为要显示的数字



        clr r0

        ldi zl,low(led_7 * 2)

        ldi zh,high(led_7 * 2)        ;Z寄存器取得7段码组的首指针

        add zl,temp                ;加上要显示的数字

        adc zh,r0                        ;加上低位进位       

        lpm                        ;读对应七段码到R0中

        out porta,r0                ;LED段码输出



        mov r0,p_temp

        com r0

        out portc,r0                ;输出位控制字,完成LED一位的显示

       

        inc position                ;调整到下一次显示位

        lsl p_temp

        cpi position,0x08

        brne display_ret

        ldi position,0x00

        ldi p_temp,0x01

display_ret:

        ret



;时钟时间调整,加0.01秒

time_add:

        ldi xl,low(time_buff)        ;

        ldi xh,high(time_buff)        ;X寄存器为时钟单元首指针

        rcall dhm3                        ;ms单元加1调整

        cpi temp,0x99                                ;

        brne time_add_ret                ;未到99ms返回

        rcall dhm                        ;秒单元加1调整

        cpi temp,0x60

        brne time_add_ret                ;未到60秒返回

        rcall dhm                        ;分单元加1调整

        cpi temp,0x60

        brne time_add_ret                ;未到60分返回

        rcall dhm                        ;时单元加1调整

          cpi temp,0x24

          brne time_add_ret                ;未到24时返回

        clr temp

        st x,temp                        ;到24时,时单元清另

time_add_ret:

        ret



;低段时间清零,高段时间加1,BCD调整

dhm:clr temp                ;当前时段清零

dhm1: stx+,temp                ;当前时段清零,X寄存器指针加一

dhm3: ldtemp,x                ;取出新时段数据

      inc temp                 ;加一

      cpi temp,0x0A                ;若个位数码未到$0A(10)

      brhs dhm2                ;例如$58+1=$59,不须调整;

      subi temp,0xFA        ;否则做减$FA调整:例如$49+1-$FA=$50

dhm2: st x,temp                ;并将调整结果送回

      ret



;将时钟单元数据送LED显示缓冲单元中

put_t2d:

        ldi xl,low(time_buff)                ;

        ldi xh,high(time_buff)        ;X寄存器时钟单元首指针

        ldi yl,low(display_buff)

        ldi yh,high(display_buff)        ;Y寄存器显示缓冲单元首指针

        ldi count,4                ;循环次数 = 4

loop:

        ld        temp,x+                ;读一个时间单元

        mov        temp1,temp

        swap temp1

        andi temp1,0x0f                ;高位BCD码

        andi temp,0x0f                ;低位BCD码

        st y+,temp                        ;写入2个显示单元

        st y+,temp1                ;低位BCD码在前,高位在后

        dec        count

        brne loop                        ;4个时间单元->8个显示单元

        ret



;T1时钟溢出中断服务

time1_ovf:

        in temp_int,sreg

        push temp_int                ;保护状态寄存器

       

        ldi temp_int,0xff                ;T1初始值设定,2ms中断一次

        out tcnt1h,temp_int

        ldi temp_int,0x83

        out tcnt1l,temp_int

       

        inc count_10ms                ;10ms计数器加一

        ldi flag_2ms,0x01                ;置2ms标志到

       

pop temp_int

        out sreg,        temp_int                ;恢复状态寄存器

        reti                        ;中断返回               

       

.CSEG                                ;LED七段码表,定义在Flash程序空间

led_7:                                ;7段码表

.db 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07

.db 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71       



;字 PA7 PA6 PA5 PA4 PA3 PA2 PA1 PA0 共阴极 共阳极

;        h        g        f        E        d        c        b        a               

;0        0        0        1        1        1        1        1        1        3FH                C0H

;1        0        0        0        0        0        1        1        0        06H                F9H

;2        0        1        0        1        1        0        1        1        5BH                A4H

;3        0        1        0        0        1        1        1        1        4FH                B0H

;4        0        1        1        0        0        1        1        0        66H                99H

;5        0        1        1        0        1        1        0        1        6DH                92H

;6        0        1        1        1        1        1        0        1        7DH                82H

;7        0        0        0        0        0        1        1        1        07H                F8H

;8        0        1        1        1        1        1        1        1        7FH                80H

;9        0        1        1        0        1        1        1        1        6FH                90H

;A        0        1        1        1        0        1        1        1        77H                88H

;b        0        1        1        1        1        1        0        0        7CH                83H

;C        0        0        1        1        1        0        0        1        39H                C6H

;d        0        1        0        1        1        1        1        0        5EH                A1H

;E        0        1        1        1        1        0        0        1        79H                86H

;F        0        1        1        1        0        0        0        1        71H                8EH



.DSEG                                ;定义程序中使用的变量位置(在RAM空间)

.ORG   $0060

display_buff:                        ;LED显示缓冲区,8个字节

.BYTE        0x00                        ;LED 1 位显示内容

.BYTE        0x00                        ;LED 2 位显示内容

.BYTE        0x00                        ;LED 3 位显示内容

.BYTE        0x00                        ;LED 4 位显示内容

.BYTE        0x00                        ;LED 5 位显示内容

.BYTE        0x00                        ;LED 6 位显示内容

.BYTE        0x00                        ;LED 7 位显示内容

.BYTE        0x00                        ;LED 8 位显示内容



.org        $0068

time_buff:                                ;时钟数据缓冲区,4个字节

.BYTE        0x00                        ;1/100s单元

.BYTE        0x00                        ;秒单元

.BYTE        0x00                        ;分单元

.BYTE        0x00                        ;时单元



    程序实例采用比较规范标准的设计理念和风格,程序中已给出比较详细的注解。关于程序如何具体完成和实现系统的功能请读者仔细阅读程序,用心体会。下面仅对编写M16汇编程序时,在结构和语句使用上一些需要注意的方面加以介绍。

1.将程序中操作最频繁以及需要特殊位处理的变量定义在AVR的32个工作寄存器空间,因为MCU对R0-R31的操作仅需要一个时钟周期,而且功能强大。由于R0-R31的功能有不同,而且也仅有32个,所以程序员应认真考虑和规划这32个工作寄存器的使用。如尽量不要将变量放置在R26-R31中,因为这6个寄存器构成3个16位的X、Y、Z地址指针寄存器,应保留用于各种寻址使用。

2.M16有21个中断源,Flash程序存储器的低段空间为这21个中断向量地址。注意:M16的一个中断向量地址空间为2个字长度(4字节)。在中断向量处可使用长转移指令jmp转移(2字)或rjmp转移(1字)到中断服务程序,而一般的AVR的一个向量地址空间为1个字长度,使用rjmp转移指令。

   出于提高系统可靠性的设计,对于系统中不使用的中断向量,应填充2个中断返回指令reti(每个reti占一个字)。在本程序中,为了程序的理解和阅读方便,使用rjmp和nop,以及reti和nop指令填充一个2个字长度的向量地址空间。

3.程序中使用X、Y、Z三个16位的地址指针寄存器,基于他们的一些指令有自动加(减)一的功能,以及先加(减)、后使用,和先使用、后加(减)的区别,在使用中应注意正确和灵活的使用。

4.由于LED的七段码对照表是固定不变的,程序中将LED的七段码表放置在Flash存储器中。对于Flash存储器的间址取数只能使用Z寄存器。由于程序存储器的地址是以字(双字节)为单位的,因此,16位地址指针寄存器Z的高15位为程序存储器的字地址,最低位LSB为“0”时,指字的低字节;为“1”时,指字的高字节。程序中使用伪指令db定义的七段码为一个字节,他保存在一个字的低字节处。如果定义字,应使用伪指令dw。

    本例使用指令lpm读取Flash中的一个字节,因此在取七段码表的首地址时乘2(ldi zl,low(led_7 * 2)),将地址左移一位,Z寄存器的LSB为“0”,表示取该字的低位字节。

5.中断服务程序中,必须对MCU的标志寄存器SREG进行保护。在T1的溢出中断服务程序中,还需要对TCNT1的初值进行设置,以保证下一次中断仍为2ms。中断服务程序应尽量短小,因此在中断服务中,只将2ms标志置位和10ms加一计数,其它处理应尽量放在主程序中。

6.程序中定义了8个字节的显示缓冲区和4个字节的时钟数据缓冲区,分别存放8个LED所对应的显示数字和4个时间段的时间值(BCD码),这12个单元定义放置在M16的RAM中。M16的RAM单元应从0x0060开始,前面的地址分别对应的是32个工作寄存器、I/O寄存器,因此不要把一般的数据单元定义在小于0x0060的空间。

7.与使用db或dw伪指令在Flash空间定义常量不同的是,在RAM空间予留变量空间的定义应使用byte伪指令。byte伪指令的功能是定义变量的位置(予留空间),不能定义(填充)变量的值,变量具体的值是需要由程序在运行中写入的。而伪指令db、dw具有数据位置和值定义(填充)的功能。





    在M16板上的连接方式:



    使用短路片短路M16边上的:

    PB5、PB6、PB7、RST(以上为ISP口);

    VCC、GND;

    X2、X1(使用内部震荡可不短路);

    PD0、PD1(不同PC通信时可不连);

    AVCC、GND(在M16的右边);

    如使用外部晶体时:JN(连AVR)、J1(连4M);



    用16根连接线:

    PA0-->a

    PA1-->b

    PA2-->c

    PA3-->d

    PA4-->e

    PA5-->f

    PA6-->g

    PA7-->p



    PC0-->Jc3_8

    PC1-->Jc3_7

    PC2-->Jc3_6

    PC3-->Jc3_5

    PC4-->Jc3_4

    PC5-->Jc2_3

    PC6-->Jc2_2

    PC7-->Jc2_1



    首先使用ISP下载线对M16的熔丝位配置(建议使用BASCOM-AVR中的下载软件,非常直观):

    禁止JTAG口,PORTC全部作为I/O使用;

    使用外部晶体或内部RC振荡(根据需要);

    允许BOD检测,门限电压4.0v;

    RESET向量为0x0000(确省值为0X0000,一般不用改)。





    谢谢testcode。将T1计算的中间值改为正确的值。
-----此内容被machao于2005-04-22,13:47:44编辑过

testcode 发表于 2005-4-22 03:59:14

您的贴中:

"T1的设计:T1为16位定时器,系统时钟为4M,采用其64分频后的时钟作为T1的计数信号(寄存器TCCR1B = 0x03),一个计数周期为4us,2ms需要计500个(0x01F4)。由于T1溢出中断发生在0xFFFF后下一个T1计数脉冲的到来(参见第二章关于定时器原理部分),因此T1的计数初始值为0xFF83。"

是否以下应为:

"一个计数周期为16us, 2ms需要计125个(0x07D)。由于T1溢出中断发生在0xFFFF后下一个T1计数脉冲的到来(参见第二章关于定时器原理部分),因此T1的计数初始值为0xFFFF - 0x007C = 0xFF83。"





使用ICCAVR的自动生成向导,得出的T1的计数初始值简直惨不忍睹,为FB1E??

-----此内容被testcode于2005-04-22,05:14:17编辑过


-----此内容被testcode于2005-04-22,05:53:10编辑过

machao 发表于 2005-4-22 14:01:26

谢谢testcode。上面是根据《M128》中的例子改动的。《M128》使用的16M时钟,我把时钟和最后结果修改了,但忘了改中间的计算值了。



不过在我ICCAVR中,向导生成的完全正确的。其实我就看者向导生成的数值填到汇编中的,结果忘了把中间的计算过程从新改过。我的是6.31A



http://cache.amobbs.com/bbs_upload782111/files_1/armok0146604.jpg

testcode 发表于 2005-4-22 23:20:49

谢谢马老师,向导生成的完全正确的,ICCAVR中是我设置错误。

shilingwen 发表于 2005-7-13 19:25:59

马老师,我采用的是你的AVR-51的实验板测试LED,为什么中间四个数码管不亮,而把PC口换成PD口却没上述问题,请问PC口还要做什么设置吗?

HJJourAVR 发表于 2005-7-13 20:27:10

PC口默认使能了JTAG,所以对应管脚没反应。



参考M16的数据手册。

shilingwen 发表于 2005-7-15 16:22:48

哦,谢谢.

wanxiaoping 发表于 2005-7-26 12:45:16

我用共阳极led做没有成功,不知道那里出了问题。上电的时候,led全显,并且有闪动。不知有没有哪位高手能帮我看看?

machao 发表于 2005-7-26 22:22:13

你基本的数字电路基础都没有,先把基础打扎实吧。

map320 发表于 2006-9-15 15:09:14

马老师,这程序能改成倒记时的吗?

machao 发表于 2006-9-20 02:11:28

map320,我先问你,这段程序你看懂了吗,真正掌握了吗?如果你的回答是YES,那么我的回答也是YES!!!

map320 发表于 2006-9-25 10:54:41

谢谢马老师,我试作改过,但是在记到60秒时出错,不知道怎么改最合理,我先把

.db 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07

.db 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71

改了,因为我不想破坏你程序整体,现在发现我太可笑了,



现在我的设计比较紧张,没花大力气读您的设计细节;







还有在程序前面加了些按键设计?(EEPROM状态记忆等)



也许我是盗版;在这里深表感谢,有机会一定报答!!!!!

weiwgml 发表于 2007-3-19 00:14:02

马老师 :

   我用你的AVR-51多功能板子实验自己编的闹钟程序,用的是PC口片选LED,实验时P5--P2

几个口没有了任何反映,我反复试过好几遍,都不能用了,结果我编了个小程序测试P5--P2这几个口,在这个小程序中也没友任何反映,我断定M16的片子坏了,我在浏览帖子时看到5楼和6楼的回答,发现可能不是板子坏了,是JTAG的问题,我是初学,还望老师做个指导,如何解决这个问题。

machao 发表于 2007-3-19 07:49:42

在这里有我的学习讲义,请下载参考5.4.2部分。
页: [1]
查看完整版本: 8段LED测试:简易时钟(AVR汇编)