作者 | 李肖遥责编 | 欧阳姝黎
最近在使用 Nordic 的最新蓝牙芯片 nRF52832 开发过程中,因为做一些测试涉及到对内存地址的操作,有(*(volatile unsigned int *)0xE000EDFC)的用法然后进行宏定义,本文将解析一下这种用法。
代码解析
先来看下面一段代码:
#define ARM_CM_DEMCR (*(volatile unsigned int *)0xE000EDFC)
#define ARM_CM_DWT_CTRL (*(volatile unsigned int *)0xE0001000)
#define ARM_CM_DWT_CYCCNT (*(volatile unsigned int *)0xE0001004)
而使用(unsigned int *)0xE000EDFC,对此数据进行强制转换,表明此数值为一个无符号整型地址指针值,关键字 volatile 告诉编译器它指向的内容是易变的,可能会被硬件等意外地修改;
*(volatile unsigned int *)0xFFE00000,则是获取指针所指向地址处的内容;
把 #define 宏中的参数用括号括起来,在用户程序中对 ARM_CM_DEMCR 的操作,就等同于在 0xE000EDFC 地址上进行读写操作了。
应用
上面说到了,在 32 位处理器,要对一个 32 位的内存地址进行访问,然后进行读写操作
tmp = ARM_CM_DEMCR;//读
ARM_CM_DEMCR = 0x55;//写
使用 volatile 修饰是因为它的值可能会改变,我们假设在一个循环操作中需要不停地判断一个内存数据,例如要等待 ARM_CM_DEMCR 的 flag 标志位置位,因为 ARM_CM_DEMCR 是映射在 SRAM 空间,为了加快速度,编译器可能会编译出这样的代码:把 ARM_CM_DEMCR 读取到寄存器中,然后不停地判断寄存器相应位,而不会再读取 ARM_CM_DEMCR.
而实际工程中,程序例如中断事件会改变 ARM_CM_DEMCR,而寄存器相应位没有更新,会造成死循环了。如果 volatile 来修饰,那每次要操作一个变量的时候会都从内存中读取一次。
嵌入式系统编程中要求程序员能够利用 C 语言访问固定的内存地址。既然是个地址,那么按照 C 语言的语法规则,这个表示地址的量应该是指针类型。
对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的 IO 地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为 C 语言并没有提供真正的“端口”的概念。
volatile关键字的用途
volatile 的意思是告诉编译器,在编程源代码时,对这个变量不要使用优化,C 语言中可能会优化一些运算过程中的变量值导致最后的结果不对,这里就不多说了。
volatile 还可以防止编译器优化去掉某些语句,在 arm 中假如需要写 1 清中断,举例子如下:
#define INTPAND *(volatile unsigned int *)0x560012300