先来看我不久前遇到的avr studio 6.0中的一个问题:
我手上有一块atmega128开发板,我想要通过设置定时器1来实现间隔1s控制LED灯呈现不同花样的效果,于是,我写下了下面的代码:
/*********************************** 描述:利用定时器1进行1s的计数,时间到则led灯变换一种花样 定时器1可以作为16位加法计数器,最大计数值2^16-1=65535,板上外部晶振提供时钟为8MHz 可以设置为8分频,所以计数器加1就是1us,计数器初值设为(65535-10000),就是计10ms, 10ms到,time_count加1,加到100,说明大约是1s,就把PORTA口输出变化。 ************************************/ #include "common.h" #include <avr/io.h> #include <avr/interrupt.h> #define BIT(n) (1<<n) typedef unsigned char uchar; uchar time_count=1; uchar i=0; void timer1_init(); uchar my_led[5]= { 0x00,0x01,0x02,0x03,0xf3 }; int main(void) { led_init(); //初始化板上的LED灯模块 timer1_init(); //初始化定时器1 sei(); //开中断 while(1) { if (time_count>=100) { PORTA = my_led[i++]; time_count=1; if (i>4) { i=0; } } } } //初始化定时器1 void timer1_init() { TCNT1H = (65535-10000)/256; TCNT1L = (65535-10000)%256; TIMSK |= BIT(TOIE1); //设置中断 TCCR1B |= BIT(CS11); //系统时钟8分频 } //中断服务 ISR(TIMER1_OVF_vect) { cli(); TCNT1H = (65535-10000)/256; TCNT1L = (65535-10000)%256; time_count++; sei(); }
但是下载到开发板后,我发现LED灯根本没有变化····
但是,如果我把程序改成下面那样呢?
/*********************************** 描述:利用定时器1进行1s的计数,时间到则led灯变换一种花样 定时器1可以作为16位加法计数器,最大计数值2^16-1=65535,板上外部晶振提供时钟为8MHz 可以设置为8分频,所以计数器加1就是1us,计数器初值设为(65535-10000),就是计10ms, 10ms到,time_count加1,加到100,说明大约是1s,就把PORTA口输出变化。 ************************************/ #define F_CPU 800000UL #define BIT(n) (1<<n) #include "common.h" #include <avr/io.h> #include <avr/interrupt.h> typedef unsigned char uchar; uchar time_count=1; uchar i=0; void timer1_init(); uchar my_led[5]= { 0x00,0x01,0x02,0x03,0xf3 }; int main(void) { led_init(); //初始化板上的LED灯模块 timer1_init(); //初始化定时器1 sei(); //开中断 while(1) { /*这次注释掉 if (time_count>=100) { PORTA = my_led[i++]; time_count=1; if (i>4) { i=0; } } */ } } //初始化定时器1 void timer1_init() { TCNT1H = (65535-10000)/256; TCNT1L = (65535-10000)%256; TIMSK |= BIT(TOIE1); //设置中断 TCCR1B |= BIT(CS11); //系统时钟8分频 } //中断服务 ISR(TIMER1_OVF_vect) { cli(); TCNT1H = (65535-10000)/256; TCNT1L = (65535-10000)%256; time_count++; //这里不一样了 if (time_count>=100) { PORTA = my_led[i++]; time_count=1; if (i>4) { i=0; } } sei(); }
竟然成功了···只要把对time_count的自增和判断放到一个函数里面,竟然就行了···这是什么原因?
经过一番搜索,终于找到了问题的原因,那便是volatile关键字。之所以第一个代码没有运行成功,是因为avr studio 6.0的编译器在编译过程中进行了代码优化,为了节省频繁取内存操作的时间,把time_count变量写入了寄存器中,之后虽然中断程序在不断的修改time_count的值,但是可怜的main函数里的while循环却是一直在读取“另外一个”time_count的值,自然就永远到不了所要的100了,所以只会在while循环里不停地运行,却无法进入分支语句进行LED的控制。为了能够让第一个代码段运行成功,当然可以改变编译器的优化选项,但是这毕竟不是长久之计,因为很有可能你的编译器和他人的编译器优化选项是不同的。这个时候就要用到volatile关键字了。
volatile(易变的)提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象,也就是上面提到的那种问题了。
一般来收,volatile关键字主要用在以下几个地方:
1.中断服务程序中修改的供其它程序检测的变量要加上volatile关键字,比如我遇到的这种情况;
2.多任务环境下各任务间共享的标志变量要加volatile;
3.存储器映射的硬件寄存器通常也要加volatile。
要注意的是,这些情况还要考虑到不同进程之间共同修改这个变量带来的问题。在1的情况下,我们可以用开关中断来控制,也就是说,在中断服务程序A修改变量值的时候,暂时关闭中断,防止其他中断发生;2中可以禁止任务调度,总之要确保在某一个时刻,只有一个进程在修改这个值。
所以要想解决上面的问题,我们只需要在声明time_count的时候将它的类型声明为volatile就可以了~~~
也就是
volatile uchar time_count=1;