OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 4017|回复: 10

浅谈Volatile和串口使用

[复制链接]

13

主题

314

帖子

0

精华

高级会员

Rank: 4

积分
713
金钱
713
注册时间
2012-7-20
在线时间
102 小时
发表于 2015-12-27 17:37:58 | 显示全部楼层 |阅读模式
本帖最后由 shibusha 于 2015-12-27 17:37 编辑


浅谈Volatile和串口使用

光阴似箭,时光如梭。眨眼的功夫2015已经走过。仿佛昨日我们还在讨论2008奥运会,今天恒大已经对决巴萨了。“光阴似箭,时光如梭”作为文章开头,被太多人引用,然而,经典还是经典。

田晓菲在她《十三岁的际遇》里写道:
“我从未怀疑过我要成为北大的学生。那份稚气十足的自信,似乎预示了一段奇妙的尘缘。只是我没有想到,我会这么快就实现了童年的梦想;而且,在白驹过隙的一瞬,这已是我来到北大的第三个秋天。
蓦然回首,我仿佛认出了两年前的自己:短短的头发,天真的目光,还不满14岁,完全是个一脑子浪漫念头的小女孩,对什么都充满了兴趣与好奇。纷扬的白雪里,依稀看到她穿着蓝色羽绒衣,在冰冻的湖面掷下一串雪团般四处迸(bèng)溅的清脆笑声。如今,秋风又起,树枝树叶交织出金色的穹(qióng)隆。落叶遍地,踩上去很柔软,好像此时此刻不胜凉意的心情。眼看87级新生穿着军训的绿军衣满校走,我才恍悟到自己已是三年级的老生了。悄立在朋友般亲切的35号楼下,不由地感到有些茫然若失……

       当然对于每一个人,也许都不会相信自己的未来,工作,生活,感情是命中注定的。
这就要说起那个古老的,无数人为之辩论的话题:你相信命运吗?你相信自己生来的一切都是命中注定吗?这时候会有人说,你这是决定论,是悲观主义者。决定我们生命的因素太多,我们的人生是蝴蝶效应,不可能是注定的。那么,当初那只蝴蝶是为什么拍打了一次翅膀呢?

哲学之上是信仰,信仰之上是神学。
在电影《迷雾》中说到,“只要有两个以上的人,就会存在宗教,就会存在斗争。斗争双方会拼命让对方相信自己的信仰。”当然我也不是北大的。

让我们回到标题,2015年就要结束了。回首这一年,不知道大家过得好不好,但无论如何生活还是要继续,还是要“爱这千疮百孔的世界”。毕竟我们应用电子这一行,没有点情怀,不就是一个彻头彻尾的Geek了?今天要说的就是在工作中,遇到的问题—Volatile。

印象中,搞单片机应用的,大多是院校的“电子信息”、“自动化”等等专业出身,或者半路出家。大多对C语言的学习不够深入。

首次遇到Volatile的“陷阱”时,当时在做一个无线转串口的模块。主要功能就是将无线信号通过串口转发。将串口接收的数据,通过无线转发出去。

我们知道一般一帧数据是连续发送/接收的,每帧信息中,每个字节之间的发送接收不会间隔很长时间。我们在串口接收里做一个时间标量USART_RX_Time,每收到一个字节,将计时复位,当计时标量超时,说明一帧数据接收完毕。下面是大概的代码(举例):
[mw_shl_code=c,true]
u16 USART1_RX_Cnt = 0;          //串口接收计数               
u8 USART_RX_Time = 0;           //一帧数据超时时间
void USART1_Handle(void)
{
       if(ISR == 1)                        //收到一个字节
       {
              USART1_BUFF[USART1_RX_Cnt++] = USART1->DR;
              USART_RX_Time = 20;                     //复位计时
       }
}

void TIMER2_Handle(void)         //1ms计时中断
{
      
       if(USART_RX_Time > 0)
              USART_RX_Time--;
}

void main(void)
{
       if(USART1_RX_Cnt > 0) && (USART_RX_Time == 0)
       {
              /**
                     认为接收到了一帧完整数据
*/
              Send(USART1_BUFF, USART1_RX_Cnt); //转发出去
              USART1_RX_Cnt = 0;
       }
}[/mw_shl_code]

上述代码在一帧数据中设置20MS超时时间,每个字节之间的接收/发送大于20MS的并不常见。所以这样判断的方法没问题,那么这个代码在运行过程中会出现什么问题呢?

实际上,你可以将代码加入到自己的板子运行一下,会发现一些问题,比如在接收过程中出现一帧数据只收到一个字节的现象。

那么是什么原因导致的呢,笔者在仿真中发现,比如现在USART_RX_Time的值是1;在定时器中断中,我们操作USART_RX_Time--;,若此时有串口中断发生(假设串口优先级>定时器),内核会将当前的压栈,去执行串口中断。而在串口中断中我们将USART_RX_Time = 20;,那么问题出现了,等内核退出串口中断,接着执行定时器中断时,此时USART_RX_Time是等于20,还是等于0?

按照正常来说,由于在串口中断中,我们改变了USART_RX_Time的值,那么内核在返回定时器中断时USART_RX_Time 的值就是20.并没有什么问题。然而当我们实际操作时,发现执行完定时器中断后,USART_RX_Time的值等于0!


为什么会这样?最初发现这个问题的时候,网上查下资料,认为是临界变量的问题。当时的解决方法是操作这些中断中使用的全局变量时,先进入临界保护。后来发现这样有点傻,C语言发展这么久,单片机这么些年,不可能这样简单的问题就要临界保护去做。

于是回去翻看C语言的书,找到了Volatile。众里寻他千百度,暮然回Volatile却在书中。Volatile顾名思义,易变的。所有容易改变的归他管就对了。Volatile的作用是什么,搜索引擎给的解释是:“简单地说就是防止编译器对代码进行优化.”。然而这句话虽然直观,但并不好理解。带入到我们今天说的问题就很好理解了,上面说的串口中断执行完,回到定时器中断,代码并不是从USART_RX_Time的实际内存地址去读值,而是从编译器指定的一个“虚拟”地方读,因为编译器觉得这样“又快又好”。但对于程序员就是灾难了,于是C语言里的Volatile就出现了。对Volatile修饰的变量,每次使用时,都要从实际的内存地址中去读取。这样就解决了上述的问题。

说了那么多,总结下来就是,在中断中有操作的全局变量,别忘记Volatile就对了。

很多问题,都源于编程语言学的不够深入,共勉吧。
互联网,智能设备爱好者,欢迎讨论任何有意思的想法。
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

9

主题

538

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
3371
金钱
3371
注册时间
2015-1-7
在线时间
794 小时
发表于 2015-12-27 18:45:38 | 显示全部楼层
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
发表于 2015-12-27 20:21:19 | 显示全部楼层
一直知道Volatile的作用是防止优化,每次都从原内存地址读取不从缓存cache获取,但是实际没有遇到过楼主这种问题,以后遇到可以参考下,谢谢分享!!顺便问下什么情况下编译器会存到缓存去加快访问速度?是编译器自己决定吗?
回复 支持 反对

使用道具 举报

88

主题

7377

帖子

5

精华

资深版主

Rank: 8Rank: 8

积分
14980
金钱
14980
注册时间
2013-11-13
在线时间
1823 小时
发表于 2015-12-28 12:04:48 | 显示全部楼层
不错,多谢分享
开往春天的手扶拖拉机
回复 支持 反对

使用道具 举报

88

主题

7377

帖子

5

精华

资深版主

Rank: 8Rank: 8

积分
14980
金钱
14980
注册时间
2013-11-13
在线时间
1823 小时
发表于 2015-12-28 12:05:19 | 显示全部楼层
229382777@qq.co 发表于 2015-12-27 20:21
一直知道Volatile的作用是防止优化,每次都从原内存地址读取不从缓存cache获取,但是实际没有遇到过楼主这 ...

对于STM32而言,只有F7才有cache的
开往春天的手扶拖拉机
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
发表于 2015-12-28 12:16:06 | 显示全部楼层
zuozhongkai 发表于 2015-12-28 12:05
对于STM32而言,只有F7才有cache的

恩,谢谢指正,在32上用volatile用的比较少,以后得多注意点了
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

3

主题

130

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
373
金钱
373
注册时间
2015-3-7
在线时间
43 小时
发表于 2015-12-28 12:20:11 | 显示全部楼层
zuozhongkai 发表于 2015-12-28 12:05
对于STM32而言,只有F7才有cache的

什么情况下要用到这个关键字。有几次在写程序的时候,发现写进去的变量不起作用,就加了volatile进去就可以了。但不清楚什么时候加这个~
为人莫作千年计,三十河东四十西,莫欺少年穷。
回复 支持 反对

使用道具 举报

15

主题

786

帖子

5

精华

论坛大神

Rank: 7Rank: 7Rank: 7

积分
3223
金钱
3223
注册时间
2015-7-26
在线时间
811 小时
发表于 2015-12-28 12:23:39 | 显示全部楼层
Mcu_learning 发表于 2015-12-28 12:20
什么情况下要用到这个关键字。有几次在写程序的时候,发现写进去的变量不起作用,就加了volatile进去就可 ...

谢谢,应该不是写进去不起作用吧,应该是读的时候没从原内存读导致变化了也不知道。顺便指正下我上面的错误“Cache 就是为了缓解低速存储器而设计的. STM32的数据通常在 SRAM 中, 是零等待的, 不需要缓存. 所以 STM32 的缓存技术只是针对 FLASH 的指令缓存”
我的博客:http://blog.csdn.net/itdo_just
回复 支持 反对

使用道具 举报

2

主题

239

帖子

0

精华

高级会员

Rank: 4

积分
545
金钱
545
注册时间
2015-6-5
在线时间
110 小时
发表于 2015-12-28 13:31:14 | 显示全部楼层
本帖最后由 Rocks 于 2015-12-28 13:33 编辑

這個Volatile關鍵字原意告訴編譯器它是易變的

因此編譯器會把他放在獨立的RAM區段 隨時來改變該數值
而不與其他函數共享該段空間

明顯的例子是
你在一個函數內宣告一個陣列區間
例如:
uint8_t Data[128];

這種 當你離開了該函數 他就把他釋放了

其他函數有需要使用到就會去調用相同位置
但是數值意義都不相同 這是編譯器幫你制定好的
驗證的方式就是你加上Volatile跟不加上Volatile
編譯完成之後看報告內RAM佔用空間的異同


那避免優化是怎麼回事?
舉個例子Delay

[mw_shl_code=c,true]uint8_t Delay_Count;

void Delay(uint8_t Time)
{
Delay_Count = 0;
while(Delay_Count<Time){}
}[/mw_shl_code]

像這個Delay_Count通常是會在計時器中斷內改變其數值
如果沒有跟編譯器說他是易變的
編譯器會變上面那段視為垃圾函數
因為Delay_Count<Time 對編譯器來說永遠不成立

這是我理解的部分
回复 支持 反对

使用道具 举报

10

主题

48

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
407
金钱
407
注册时间
2014-10-22
在线时间
135 小时
发表于 2015-12-28 13:52:50 | 显示全部楼层
学习了
回复 支持 反对

使用道具 举报

17

主题

237

帖子

0

精华

高级会员

Rank: 4

积分
925
金钱
925
注册时间
2012-2-27
在线时间
393 小时
发表于 2015-12-28 15:43:44 | 显示全部楼层
多谢分享
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-6-21 18:04

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表