您的位置:首页 > 其它

volatile关键字的使用

2013-03-28 13:31 211 查看
先来看我不久前遇到的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;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: