volatile关键字的使用

先来看我不久前遇到的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;


 

原文地址:https://www.cnblogs.com/xmfbit/p/3872200.html