该系统为一个带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小时制时钟
Mega16 4MHz
********************************************************
.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
st x+,temp 1/100秒 = 00
ldi temp,0x55
st x+,temp 秒 = 55
ldi temp,0x59
st x+,temp 分 = 59
ldi temp,0x11
st x,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: st x+,temp 当前时段清零,X寄存器指针加一
dhm3: ld temp,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,一般不用改)。
这个问题问的太。。。。了,给你找了个 *** 作符(operator)用于 *** 作数据。 *** 作符进行计算、检查等式、进行赋值、 *** 作变量和进行其它更奇怪的工作。C++中有许多 *** 作符,这里不想列出全部,只列出最常用的 *** 作符,如下表所示。表1.2常用C++ *** 作符 *** 作符说明举例算术运算符
+ 加 x=y+z
- 减 x=y-z
* 乘 x=y*z
/ 除 x=y/z
赋值运算符
= 赋值 x=10
+= 赋值与和 x+=10(等于x=x+10)
-= 赋值与减 x-=10
*= 赋值与乘 x*=10
\= 赋值与除 x\=10
&= 赋值位与 x&=0x02
|= 赋值位或 x|=0x02
逻辑 *** 作符
&&逻辑与 if(x &&0xFF) {...}
|| 逻辑或 if(x || 0xFF) {...}
等式 *** 作符
== 等于 if(x == 10) {...}
!= 不等于 if(x != 10) {...}
<小于 if(x <10) {...}
>大于 if(x >10) {...}
<= 小于或等于 if(x <= 10) {...}
>= 大于或等于 if(x >= 10) {...}
一元 *** 作符
* 间接 *** 作符 int x=*y
&地址 *** 作符 int* x=&y
~ 位非 x &=~0x02
! 逻辑非 if(!valid) {...}
++ 递增 *** 作符 x++(等于x=x+1)
-- 递减 *** 作符 x--
类和结构 *** 作符
:: 范围解析 MyClass :: SomeFunction()
->间接成员 MyClass->SomeFunction()
· 直接成员 MyClass . SomeFunction()
可以看出,这个清单长了些,没法一下子记住。使用C++时,你会慢慢熟悉这些 *** 作符的。必须指出,递增 *** 作符既可用作前递增(++x),也可用作后递增(x++)。前递增 *** 作符告诉编译器先递增再使用变量,而后递增 *** 作符则让编译器先使用变量值再递增。例如下列代码:
int x = 10
cout <<"x = " <<x++ <<end1
cout <<"x = " <<x <<end1
cout <<"x = " x <<end1
cout <<"x = " <<++x <<end1
输出结果如下:
x=10
x=11
x=12
x=12
递减 *** 作符也是这样,这里不想将这些内容讲得太深,但读者可以耐心阅读下去,正如彭兹对奥古斯特所说,“奥古,耐心点,罗马不是一天建成的”。说明 在C++中 *** 作符可以过载(overload)。编程人员可以通过过载标准 *** 作符让它在特定类中进行特定运行。例如,可以在一个类中过载递增 *** 作符,让它将变量递增10而不是递增1。 *** 作符过载是个高级C++技术,本书不准备详细介绍。你也许会发现,有些 *** 作符使用了相同的符号。符号的意义随情境的不同而不同。例如,星号(*)可以作为乘号、声明指针或取消指针引用。这初看起来有点乱,事实上,C++编程老手有时也觉得有点乱。多实践,你会慢慢适应的。本书有许多例子介绍这些 *** 作符。读者不必死记每个 *** 作符的作用,而可以在学习中通过程序和码段去理解其作用。 C++中的函数
函数是与主程序分开的码段。这些码段在程序中需要进行特定动作时调用(执行)。例如,函数可能取两个值并对其进行复杂的数学运算。然后返回结果,函数可能取一个字串进行分析,然后返回分析字串的一部分。新术语 函数(function)是与主程序分开的码段,进行预定的一个服务。函数是各种编程语言的重要部分,C++也不例外。最简单的函数不带参数,返回void(表示不返回任何东西),其它函数可能带一个或几个参数并可能返回一个值。函数名规则与变量名相同。图1.5显示了函数的构成部分。新术语 参数(parameter)是传递给函数的值,用于改变 *** 作或指示 *** 作程度。
返回类型 函数名 参数表
↓ ↓ ↓
int SomeFunction(int x, int y){
函数体→int z = (x * y)return z↑返回语句
}
图1.5函数的构成部分使用函数前,要先进行声明。函数声明或原型(prototype)告诉编译器函数所取的参数个数、每个参数的数据类型和函数返回值的数据类型。清单1.4列示了这个概念。新术语 原型(prototype)是函数外观的声明或其定义的说明。
清单1.4Muttiply.cpp
1: #include <iostream.h>
2: #include <conio.h>
3: #pragma hdrstop
4:
5: int multiply(int,int)
6: void showResult(int)
7:
8:int main(int argc,char **argv)
9:{
10: int x,y,result
11: cout <<end1 <<"Enter the first value:"
12: cin >>x
13: cout <<"Enter the second value: "
14: cin >>y
15: result=multiply(x,y)
16: showResult(result)
17: cout <<end1 <<end1 <<"Press any key to continue..."
18: getch()
19: return 0
20: }
21:
22: int multiply(int x,int y)
23: {
24:return x * y
25: }
26:
27: void showResult(int res)
28: {
29:cout <<"The result is: " <<res <<end1
30: }
这个程序的11到14行用标准输入流cin向用户取两个数字,第15行调用multiply()函数将两个数相乘,第16行调用showResult()函数显示相乘的结果。注意主程序前面第5和第6行multiply()和showResult()函数的原型声明。原型中只列出了返回类型、函数名和函数参数的数据类型。这是函数声明的最基本要求。函数原型中还可以包含用于建档函数功能的变量名。例如,multiply()函数的函数声明可以写成如下:int multiply(int firstNumber,int secondNumber)这里函数multiply()的作用很明显,但代码既可通过说明也可通过代码本身建档。注意清单1.4中函数multiply()的定义(22到25行)在主函数定义码段(8到20行)之外。函数定义中包含实际的函数体。这里的函数体是最基本的,因为函数只是将函数的两个参数相乘并返回结果。清单1.4中函数multiply()可以用多种方法调用,可以传递变量、直接数或其它函数调用的结果:
result = multiply(2,5)//passing literal values
result = multiply(x,y)//passing variables
showResult(multiply(x,y))
//return value used as a
//parameter for another function
multiply(x,y)//return value ignored
注意 最后一例中没有使用返回值。本例中调用函数multiply()而不用返回值没什么道理,但C++编程中经常忽略返回值。有许多函数是先进行特定动作再返回一个数值,表示函数调用的状态。有时返回值与程序无关,可以忽略不计。如果将返回值忽略,则只是放弃这个值,而不会有别的危害。例如,前面的样本程序中忽略了getch()函数的返回值(返回所按键的ASCII值)。函数可以调用其它函数,甚至可以调用自己,这种调用称为递归(recursion)。这在C++编程中是个较复杂的问题,这里先不介绍。新术语 递归(recursion)就是函数调用自己的过程。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)