用按键控制的1位LED数码管显示系统
硬件电路
图为硬件电路图。其中LED数码管的控制显示连接与前面例6.4(没有做摘抄)相同,PA口工作于输出方式,作为LED数码管的段码输出,LED数码管的位信号接地,因此这个1位的LED数码管工作于静态显示方式。图中使用了两个按键K1、K2,按键的一端分别与PD2(INT0)、PD3(INT0)连接。INT0和INT1作为外部中断的输入,采用电平变化的下降沿触发方式,当K1(K2)按下时,会在PD2(PD3)引脚上产生一个高电平到低电平的跳变,触发INT0或INT1中断。
系统的功能还是控制一个8段数码管显示0~F 16个十六进制的数字。为系统上电时,显示0。K1键的作用是加1控制键,按一下加1,到F之后又从0开始,K2则是减1键,到0后从F开始。
软件设计
首先在CVAVR中使用C语言编写程序。再次建议读者使用CVAVR中的程序生成向导功能来帮助建立整个程序的框架,并采用芯片初始化部分的语句,可以省掉过多地查看器件手册和考虑寄存器设置值的时间等。
程序如下:
/******-
File name :demo_7_1.c
Chip type :ATmega16
Program type :Application
Clock frequency :4.000000MHz
Memory model :Small
External SRAM size :0
Data Stack size :256
******/
#include<mega16.h>
flash unsigned char led_7[16] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
unsigned char counter;
//INT0 中断服务程序
interrupt [EXT_INT0] void ext_int0_isr(void)
{
if (++counter >= 16) counter = 0;
}
//INT1 中断服务程序
interrupt [EXT_INT1] void ext_int1_isr(void)
{
if (counter) --counter;
else counter = 15;
}
void main(void)
{
PORTA = 0xFF;
DDRA = 0xFF;
GICR |= 0xC0;//使能INT0、INT1中断
MCUCR = 0x0A;//INT0、INT1下降沿触发
GIFR = 0xC0;//清除INT0、INT1中断标志位
counter = 0;//计数单元初始化为0
#asm("sei");//使能全局中断
while(1)
{
PORTA = led_7[counter];//显示计数单元
};
}
上面的程序,就是利用CVAVR的程序生成向导功能进行配置,然后在它生成的程序框架基础上完成的。程序中定义了一个计数变量counter,执行一次INT0中断服务程序,counter加1,而执行一次INT1中断服务程序,counter减1.在主程序中,只显示counter的值。INT0、INT1初始化为电平变化的下降沿触发。
下面这个程序为汇编程序,思想方法与上面的C语言程序相同,可参考。
*******
;AVR汇编程序实例:demo_7_1.asm
;使用INT0、INT1控制LED数码管显示
;ATmega16,4MHz
;*******
.includeb "m16def.inc"
.def temp = r23;临时变量
.def counter = r24;计数变量
;中断向量区配置,Flash空间为$000~$028
.org $000
jmp RESET ;复位处理
jmp EXT_INT0 ;TRQ0中断向量
jmp EXT_INT1 ;TRQ1中断向量
reti ;Timer2 比较中断向量
nop
reti ;Timer2 溢出中断向量
nop
reti ;Timer1 捕捉中断向量
nop
reti ;Timer1 比较A中断向量
nop
reti ;Timer1 比较B中断向量
nop
reti ;Timer1 溢出中断向量
nop
reti ;Timer0 溢出中断向量
nop
reti ;SPI 传输结束中断向量
nop
reti ;USART TX 结束中断向量
nop
reti ;UDR 空中断向量
nop
reti ;USART RX 结束中断向量
nop
reti ;ADC 转换结束中断向量
nop
reti ;EEPROM 就绪中断向量
nop
reti ;模拟比较器中断向量
nop
reti ;两线串行接口中断向量
nop
reti ;IRQ2 中断向量
nop
reti ;定时器0比较中断向量
nop
reti ;SPM就绪中断向量
nop
.org $02A ;上电初始化程序
RESET:
ldi r16,high(RAMEND)
out SPH,r16
ldi r16,low(RAMEND)
out SPL,r16 ;设置堆栈指针为RAM的顶部
ser temp
out ddra,temp ;设置PORTA为输出,段码输出
out porta,temp ;设置PORTA输出全1
ldi temp,0x0a
out mcucr,temp ;INT0、INT1下降沿触发
ldi temp,0xc0
out gicr,temp ;使能INT0、INT1中断
out gifr,temp ;清除INT0、INT1中断标志位
clr counter
sei ;使能中断
MAIN:
clr r0
ldi zl,low(led_7 * 2)
ldi zh,high(led_7 * 2) ;Z寄存器取得7段码组的首指针
add zl,counter ;加上要显示的数字
adc zh,r0 ;加上低位进进位
lpm ;读对应7段码到R0中
out porta,r0 ;LED段码输出
rjmp MAIN ;循环显示
EXT_INT0:
in temp,sreg
push temp ;中断现场保护
inc counter ;计数单元加1
cpi counter,0x10 ;与16比较
brne EXT_INT0_RET ;小于16,转中断返回
clr counter ;计数单元清0
EXT_INT0_RET:
pop temp ;中断现场恢复
out sreg,temp ;中断返回
reti
EXT_INT1:
in temp,sreg
push temp ;中断现场保护
dec counter ;计数单元减1
cpi counter,0xFF ;与255比较
brne EXT_INT1_RET ;未到255,转中断返回
ldi counter,0x0F ;计数单元设置为15
EXT_INT1_RET:
pop temp ;中断现场恢复
out sreg,temp ;中断返回
reti
led_7: ;7段码表
.db 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07
.db 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71
思考与实践
//TODO 明天把问题部分摘抄下来,回答无能为力
Q:平稳地按下K1和K2,观察显示的变化
Q:修改程序,将INT0、INT1的中断触发方式分别改成电平变化的上升沿触发中断,以及电平变化触发中断和低电平触发中断,然后运行程序使显示数据加1或减1变化,与使用电平变化的下降沿触发中断的情况做比较,有何不同?
Q:不管使用哪种中断触发方式,经常会产生按键控制不稳定的现象,例如显示为5时,按下K1键一次,应该显示6,但时间显示7或8甚至更高,这是为什么?
Q:查看在CVAVR开发环境中编写编译demo_7_1.c的过程中,CVAVR都生成了哪些文件?这些文件的内容是什么?文件的扩展名是什么?有何作用?
Q:查看CVAVR生成的demo_7_1.lst的内容,问:CVAVR对中断向量是如何处理的?counter变量分配在什么地方?在CVAVR中,如何对中断现场进行保护和恢复?是使用硬件堆栈进行保护的吗?
Q:整理出编译demo_7_1.c生成的汇编代码,体会宏的使用,并与demo_7_1.asm进行比较。
Q:CVAVR在编译demo_7_1.c生成的汇编代码中还做了哪些工作?
Q:参考CVAVR的help,尝试在CVAVR中采用嵌入汇编语言的方式编写INT0和INT1的中断服务程序。