你今天volatile了吗?--准确定位

  在前一篇关于 volatile文章《你今天 volatile了吗?--慎重使用》(在后面,简称《慎重使用》)中提到, volatile定义的对象其内容可能会忽然的变化。换句话讲,如果你定义了一个volatile对象,就等于你告诉编译器该对象的内容可能会改变,即使代码中没有任何语句去改变该对象。编译器访问非volatile对象和volatile对象的方式很不一样对于前者(经优化后),它先将非 volatile对象的内容读到CPU寄存器中,等操作CPU寄存器一段时间后,才最终将CPU寄存器的内容写回 volatile对象。然而,对于 volatile对象就没有这种优化操作。 这时候编译器有些“笨”,代码要求它读取或写入volatile,它就马上如实地去做。前一篇《慎重使用》主要讲述如何明智地正确使用 volatile,本篇文章通过一些实际应用进一步阐述 volatile在解决嵌入式问题中的一些微妙作用并继续深入探讨使用 volatile要注意的一些细节问题。
 
1.构造内存映射的设备寄存器
  许多处理器使用内存映射的I/O设备。这些设备将寄存器映射到普通内存空间的某些固定地址。这些基于内存映射的设备寄存器看起来与一般的数据对象没啥两样。在《慎重使用》中提到ARM Evaluator-7T 的特殊寄存器的定义为:
typedef uint32_t special_register;
在嵌入式应用中,许多设备有时候不仅仅与一个寄存器打交道,有时可能与多个寄存器的集合同时打交道。在Evaluator-7T板子上,串口UART就是一个很好的例子。在这个板子上有两个UART,UART0和UART1。每个UART都由6个特殊寄存器控制。我们可以通过一个数据结构来表示这些寄存器的集合:
 
注意:数据结构UART标识符UART的不同使用方法和位置。
 
typedef struct UART UART
struct UART
{
  special_register ULCON; 
  special_register UCON;    /*控制*/
  special_register USTAT;     /*状态*/
  special_register UTXBUF;    /*发送缓冲*/
  special_register URXBUF;    /*接收缓冲*/
  special_register UBRDIV;    
};
UART0对应的特殊寄存器被映射到0x03FFD000。我们有两种方法来访问该寄存器,一种是《智用篇》中提到过的宏定义方法:
#define UART0 ((UART *)0x03FFD000)
另一种是通过常量指针:
UART *const UART0  = (UART *) 0x03FFD000;
 
2.使用volatile
  《慎重使用》提到,如果你不希望编译器对你的代码作优化以防止出现你预想不到的情况,那么使用 volatile是不二之选。显然,要访问串口的设备寄存器,我们必须要关掉编译器优化。现在, volatile可以大显身手了。我们修改前面的定义为:
#define UART0 ((UART volatile *) 0x03FFD000)
或:
UART volatile *const UART0  = (UART *) 0x03FFD000;
如果使用后者(常量指针),就建议做强制转化:
UART volatile *const UART0  = (UART volatile *)0x03FFD000;
但这并不是必须。对于任意类型T,c编译器提供T指针到 volatile T指针的标准内置转化,就如同T指针到const T指针的转化,整个过程自动完成。另外,将一个对象定义为 volatile类型,那么该对象中的所有成员也将成为 volatile类型。显然,在 UART0前面加 volatile类型,不可避免在其它地方也必须要加上 volatile
比如,我们有下面的函数实现串口的字符串输出:
void put(char const *s, UART *u);
如果 UART0是指向 UART对象的 volatile指针,那么如下调用会有什么问题呢:
put("hello, world/n", UART0);   
编译出错通不过!因为编译器不会将 volatile UART指针转化为 UART指针,所以我们能做的就是将其强制转化: /* UART == struct UART */
put("hello, world/n", (UART *)UART0);/*volatile UART -> UART*/
这个强制转化虽然骗过了编译器,但在运行态(run time)可能会出问题。因为这时编译器将 volatile类型 UART0当做非 volatile类型使用。为了避免这个缺陷,可以这样声明:
void put(char const *s, UART volatile *u);

注意:在这里加了 volatile之后,在其它相关的地方别忘了也要加上 volatile
 
2.准确地构造寄存器
  先看下面对 UART0的声明: UART volatile*const UART0 = ...;
这种添加 volatile的同时还添加 const的做法有下面微妙的隐含功能: UART结构本身并不是 volatile的,这个声明使得 UART0指向一个 volatile类型的 UART 常量对象。然而,其它的串口比如UART1有可能不是定义成 volatile类型(有可能将UART1定义成 UART类型)。除非系统确实有这样区分的需要,否则这种不一致并不是值得提倡的编程风格。解决这种问题的方法之一就是将 volatile引入到 UART类型:
typedef struct UART volatile UART;
有些人可能更愿意这么定义:
typedef volatile struct UART UART;
但我本人推荐将const/ volatile放到类型右侧的定义风格(即前者的风格)。使用上面的定义,我们不用担心哪里是否遗漏了 volatile。 另外, UART0的定义也修正为:
#define UART0 ((UART *)0x03FFD000)

UART *const UART0  = (UART *) 0x03FFD000;
而put函数也修正为:
void put(char const *s, UART *u);
这时的 UART已是 volatile类型。如果UART1定义成 UART类型,那么显然它也是 volatile类型。先打住,假如有人将 UART1 定义为struct UART呢?:
struct UART *const UART1  = (struct UART *) 0x03FF...;
哎呀,没错!我们遗漏了有人可能用 struct UART 定义 UART1的可能,这种定义使得
UART1的访问还是非 volatile方式 。到此,我们可以看出 UART 定义为 volatile struct UART 并不能防止有人做出不恰当或不统一的定义。所以,想从根本上解决这种不一致的问题,就只能这么定义:
typedef struct   
{   
  special_register ULCON; 
  special_register UCON;     /*控制*/
  special_register USTAT;      /*状态*/
  special_register UTXBUF;     /*发送缓冲*/
  special_register URXBUF;     /*接收缓冲*/
  special_register UBRDIV;
} volatile UART;

这样使得 任何使用UART的地方都是 volatile类型的。
或:
struct UART
{    
  special_register volatile ULCON;
  special_register volatile UCON;    /*控制*/
  special_register volatile USTAT;     /*状态*/
  special_register volatile UTXBUF;   /*发送缓冲*/
  special_register volatile URXBUF;    /*接收缓冲*/
  special_register volatile UBRDIV;
};/
* UART结构每个成员都是 volatile 类型*/
虽然我们用上面的方法解决 UART结构类型和struct UART类型的不统一,但可以看出special_register不是固有的 volatile类型,所以在别的地方,特殊积存器可能不是 volatile类型(有人可能不需要用 UART来定义寄存器组,他要的只是用special_register定义单个寄存器)。为了从根本上彻底解决这种潜在问题,需要将special_register
作如下定义:
typedef uint32_t volatile special_register;
 
这样一来,不论你定义寄存器组还是单个寄存器都是volatile类型的!
 
总结:本篇文章始终围绕设备寄存器定义,就volatile到底该用在什么地方,该用在什么位置展开深入的分析讨论,最终得到将special_register定义为volatile类型是嵌入式应用中最理想的设计。
 
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值