浅谈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就对了。
很多问题,都源于编程语言学的不够深入,共勉吧。