1. 首页 > 快讯

深入浅出了解单片机Volatile应用

二、用处

关于编译器优化

如果你在编译器器开了优化,那么就要小心了。以下代码使用IAR7.20,优化等级High,选Balanced。main函数的主循环如下

while(1)
{
Delay_ms(500);
LCD_refresh_flg=0;
Delay_ms(500);
if(LCD_refresh_flg){
LCD_refresh_flg=0;
LCD_ShowString(0,13,"receive_data");
}
}

其中LCD_refresh_flg变量在串口中断中

void USART1_IRQHandler(void)
{
if(USART_GetFlagStatus(USART1,USART_FLAG_TC))
{
USART_ClearFlag(USART1,USART_FLAG_TC);
}
if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE))
{
LCD_refresh_flg=1;
USART_ClearFlag(USART1,USART_FLAG_RXNE);
}
}

其中延时函数如下

/**
* @brief Inserts a delay time.
* @param nTime: specifies the delay time length, in 10 ms.
* @retval None
*/
void Delay_ms(uint32_t nTime)
{
TimingDelay=nTime;

while(TimingDelay!=0);
}

/**
* @brief Decrements the TimingDelay variable.
* @param None
* @retval None
*/
void TimingDelay_Decrement(void)
{
if(TimingDelay!=0x00)
{
TimingDelay--;
}
}
/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
void SysTick_Handler(void)
{
TimingDelay_Decrement();
}

这是简单的示例代码(为了说明问题,实际项目开发应该没有类似的代码),代码设计的意图是串口收到数据,LCD显示字符串。但debug时如下

在单步执行时,第85行代码被直接跳过了,且在85行也无法打断点。查看反汇编代码,确认第85,87,88,89代码直接被编译器优化没了。

可能编译器在想,在85行将LCD_refresh_flg变量清零,而86行代码量又不大,所以认为87行的if条件不成立,这全部优化掉了。编译器完全没有想到我们的延时长达500ms,且在串口中断中会将LCD_refresh_flg置1,这完全违背我们的设计意图。

如果我们将LCD_refresh_flg使用volatile修饰,volatileuint8_t LCD_refresh_flg;在编译器不变,优化等级不变时,运行如下

虽然编译器和优化等级都是一样的,但是这个时候,不再优化LCD_refresh_flg变量。

当然这是编译优化的结果,并不是说,在main函数的主循环的先对变量清零,再后边判断变量就会被编译器优化。如下代码,LCD_refresh_flg没有使用volatile修饰。

此时LCD_ShowString函数是代码量很大的函数,这个时候编译器就不敢轻易优化LCD_refresh_flg变量了。希望大家能从这个例子中理解volatile对编译优化的影响。

关于访问硬件

存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。例如:假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。(例子仅为说明问题,不具有实战意义)

int*output=(unsignedint*)0xff800000;//定义一个IO端口;
intinit(void)
{
inti;
for(i=0;i<10;i++){
*output=i;
}
}

经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器最后给你编译编译的代码结果相当于:

intinit(void)
{
*output=9;
}

这明显是不符合我们的设计意图的。

不仅仅是写操作,读操作亦是如此。如果你需要反复读取一个寄存器的值,那么编译器在优化只有读取1次。这时候就该使用volatile通知编译器在遇到此变量时候不要优化。在这里多说一句,我们这里说的是编译器,且是嵌入式方面的,不同的编译器会有不同结果,所以上面的例子并非是在不同的编译器必现的。但是在嵌入式领域反复访问硬件寄存器,加上volatile是有必要的。

中断服务程序

在断服务程序中修改的供其它程序检测的变量,需要加volatile,中断是会突然发生的,当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。加上volatile修饰,内核每次都会小心的从该变量存储的内存中重新读取数据,而不是从已经加载到内核寄存器中的值。

RTOS系统下需要注意

这个问题本质上和上一个是一样的,都是为了防止内核从寄存器中读取变量的值,而不是从变量存储的内存读取。特别是具有抢占属性的RTOS,本质上也是中断时,紧急的任务立即打断了正在执行的任务,“类似”于中断。

如果有同学了解RTOS的话,可以想一下RTOS的临界区的概念,都是为了保护变量。这里不再对RTOS进行详述。如果有同学了解Linux的话,那就更好了,volatile的作用和linux的原子操作是差不过的。这里也不再详述,只是发散一下思维。

三、volatile问题

volatile在面试中是非常容易见到的,下面总结一下常见的

1.一个参数既可以是const还可以是volatile吗?可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2.一个指针可以是volatile吗?可以,当一个中服务子程序修改一个指向buffer的指针时。

3.下面的函数有什么错误?

intsquare(volatileint*ptr)
{
return*ptr**ptr;
}

该程序的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

intsquare(volatileint*ptr)
{
inta,b;
a=*ptr;
b=*ptr;
return a*b;
}

由于*ptr的值可能被意想不到地改变,因此a和b可能是不同的。结果,这段代码可能返回不是你所期望的平方值!正确的代码如下:

longsquare(volatileint*ptr)
{
inta;
a=*ptr;
return a*a;
}

注意:频繁地使用volatile很可能会增加可执行文件的大小和降低性能,因此要合理的使用volatile,不能滥用volatile。

四、总结

volatile 关键字是一种类型修饰符,它的总结如下

使用volatile关键字修饰的变量,可以避免编译器优化,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

使用volatile关键字修饰的变量,每次都是重新读取内存中的值,而不是使用保存在寄存器里的值了;在中断,RTOS,硬件访问多用到。

频繁地使用volatile很可能会增加可执行文件的大小和降低性能,因此要合理的使用volatile,不能滥用volatile。

嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,这是区分C程序员和嵌入式系统程序员的最基本的问题,所以在嵌入式应用中这些要求使用volatile变量。不懂得volatile的话可能将会带来灾难。

好了,文章到这里就结束啦,如果本次分享的深入浅出了解单片机Volatile应用和问题对您有所帮助,还望关注下本站哦!

用户评论

丢了爱情i

这篇文档介绍的很透彻,让我对硬件级别的数据共享有了更清晰的认识。

    有6位网友表示赞同!

执念,爱

volatile关键字确实很有用,尤其是在处理多个任务并发的时候。

    有13位网友表示赞同!

青山暮雪

原来是这样啊,之前一直没明白Volatile的作用,看了以后明白了!

    有19位网友表示赞同!

青瓷清茶倾城歌

写的超级详细,每一个细节都解释的很清楚,很棒!

    有18位网友表示赞同!

枫无痕

单片机 programming 真的是需要不断学习和探索,感谢分享这方面的知识。

    有12位网友表示赞同!

冷眼旁观i

终于找到了解决这个问题的方法!volatile真是个好东西。

    有7位网友表示赞同!

陌潇潇

这个文章正好解决了我的困扰,之前写代码的时候遇到过类似的问题。

    有7位网友表示赞同!

经典的对白

对于初学者来说,这篇文档非常友好,能很容易理解 volatile 的用法。

    有17位网友表示赞同!

窒息

单片机编程确实有一些技巧,学习这些知识可以提高效率。

    有16位网友表示赞同!

浅巷°

这篇文章让我对 Volatile 这个关键字更加了解,学习到了很多。

    有7位网友表示赞同!

栀蓝

分享这样的文章太棒了,能够帮助更多的人解决编程问题!

    有9位网友表示赞同!

断秋风

之前写嵌入式代码的时候遇到过这个问题,看了你的文档感觉很有用!

    有7位网友表示赞同!

相知相惜

单片机编程确实有很多细节需要注意,感谢作者的分享。

    有12位网友表示赞同!

在哪跌倒こ就在哪躺下

学习volatile 关键字非常重要,对于硬件交互很有用处。

    有15位网友表示赞同!

可儿

这篇教程很全面,涵盖了 volatile 的各个方面。

    有14位网友表示赞同!

伪心

我对嵌入式系统越来越感兴趣,这种类型的教程非常有帮助!

    有15位网友表示赞同!

◆乱世梦红颜

文章讲解详细易懂,值得每个学习单片机的同学收藏!

    有12位网友表示赞同!

孤自凉丶

这个知识点我之前还不了解,现在有了更清晰的认识了。

    有15位网友表示赞同!

不相忘

文章例子都很实用,很好的帮助理解 volatile 的应用场景。

    有5位网友表示赞同!

本文采摘于网络,不代表本站立场,转载联系作者并注明出处:https://www.iotsj.com//kuaixun/8030.html

联系我们

在线咨询:点击这里给我发消息

微信号:666666