电子Inc 发表于 2014-11-22 16:46:31

详解C语言如何访问固定的内存地址。 (*(volatile unsigned char *)(0x22))

本帖最后由 jianhong_wu 于 2014-11-22 17:56 编辑

#define __REGb(x) (*(volatile unsigned char *)(x))
#define __REGi(x) (*(volatile unsigned int *)(x))
#define NF_BASE 0x4e000000
#define NFCONF __REGi(NF_BASE + 0x0)
#define NFCMD __REGb(NF_BASE + 0x4)
#define NFADDR __REGb(NF_BASE + 0x8)
#define NFDATA __REGb(NF_BASE + 0xc)
#define NFSTAT __REGb(NF_BASE + 0x10)
把宏定义放入句中就是(*(volatile unsigned int *))(0x4e000000),不知其何意??
好像是定义一指针,该指针指向的内容就是 0x4e000000 该寄存器的内容。

网上查了资料,先看英文的,看看外国人怎么解释:
Using C, I was trying to assign a variable name to a register address so that my
code would be readable. An example of how to do this is as follows:
#define DDRA (*(volatile unsigned char *)(0x22))
This means that if a register or memory location exists at address 0×22, I can use
DDRA to read or write to it like so..
DDRA = 0x05

In my C code.The #define looks really cryptic at first. The way to understand this
is by breaking it down into pieces.
First of all,
unsigned char
means we are using a byte-sized memory location. Byte being 8-bits wide.
unsigned char * means we are declaring a pointer that points to a byte-sized location.
(unsigned char *) (0x22)
means the byte-sized pointer points to address 0×22. The C compiler will refer to
address 0×22 when the variable DDRA is used. The assembly code will end up using
0×22 in Load(LD) and Store (STR) insturctions.
(*(unsigned char *)(0x22))
The first asterisk from the left signifies that we want to manipulate the value in
address 0×22. * means “the value pointed to by the pointer”.
volatile
volatile forces the compiler to issue a Load or Store anytime DDRA is accessed as
the value may change without the compiler knowing it.

再看看中文的解释:
    使用一个 32 位处理器,要对一个 32 位的内存地址进行访问,可以这样定义
#define RAM_ADDR   (*(volatile unsigned long *)0x0000555F)
    然后就可以用 C 语言对这个内存地址进行读写操作了
    读:tmp = RAM_ADDR;
    写:RAM_ADDR = 0x55;
定义 volatile 是因为它的值可能会改变,大家都知道为什么改变了;
如果在一个循环操作中需要不停地判断一个内存数据,例如要等待 RAM_ADDR 的I 标志位置
位, 因为 RAM_ADDR 也是映射在 SRAM 空间, 为了加快速度,编译器可能会编译出这样的代码:
把 RAM_ADDR 读取到 Register 中,然后不停地判断 Register 相应位。而不会再读取
RAM_ADDR,这样当然是不行了,因为程序或其它事件(中断等)会改变 RAM_ADDR,结果很
可能是一个死循环出不来了。如果定义成 volatile 型变量,编译的代码是这样的:每次要
操作一个变量的时候都从内存中读取一次。
#define rGPACON(*(volatile unsigned long *)0x56000000)
对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支
持独立的 IO地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因
为 C 语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便的多了。
举个例子,比如像寄存器 A(地址假定为 0x48000000)写入数据 0x01,那么就可以这样设
置了。
#define A (*(volatile unsigned long *)0x48000000)
...
   A = 0x01;
...
    这实际上就是内存映射机制的方便性了。其中 volatile 关键字是嵌入式系统开发的一个重要特点。上述表达式拆开来分析,首先(volatile unsigned long *)0x48000000的意
思是把 0x48000000 强制转换成 volatile unsigned long 类型的指针,暂记为 p,那么就是
#define A *p, 即A 为P 指针指向位置的内容了。 这里就是通过内存寻址访问到寄存器 A,
可以读/写操作。
用 GCC 编译时。volatile 所指示的寄存器不进行优化!!!

理解#define rRTCCON    (*(volatile unsigned char *)0x57000043) //RTC control
嵌入式系统编程,要求程序员能够利用 C 语言访问固定的内存地址。既然是个地址,那么按
照 C 语言的语法规则,这个表示地址的量应该是指针类型。所以,知道要访问的内存地址后,
比如 0x57000043,
   第一步是要把它强制转换为指针类型
(unsigned char *)0x57000043, s3c2410 的rRTCCON是单字节访问的(怎么看出来的???
我无法理解),所以 0x57000043 强制转换为指向 unsigned char 类型。
   volatile(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不
会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改
变——意想不到。
   第二步,对指针变量解引用,就能操作指针所指向的地址的内容了
   *(volatile unsigned char *)0x57000043
   第三步,小心地把#define 宏中的参数用括号括起来,这是一个很好的习惯。
在嵌入式系统中经常使用到 Volatile,对于volatile 的用法

编译器对代码的优化是指:
CPU 在执行的过程中,因为访问内存的速度远没有 cpu 的执行速度快,为了提高效率,引入了
高速缓存 cache. C 编译器在编译时如果不知道变量会被其它外部因素(操作系统、硬件或者
其它线程)修改,那么就会对该变量进行标识,即优化.那么这个变量在 CPU的执行过程中,就
会被放到高速缓存 cache 去,进而达到对变量的快速访问. 在了解了优化的概念后,试想如
果我们事先就知道该变量会被外部因素改变,那么我们就在这个变量定义前加上 Volatile,
这样编译器就不会对该变量进行优化.这样该变量在 cpu 处理的过程当中,就不会被放到高
速缓存 cache 中。
为什么要让变量在执行的过程中不被放到 cache 中去呢?
如果变量是被外部因素改变,那么 cpu 就无法判断出这个变量已经被改变,那么程序在执行
的过程中如果使用到该变量,还会继续使用 cache 中的变量,但是这个变量其实已经被改变
了.需要到内存地址中更新其内容了.
还有一个原因,在一些寄存器变量或数据端口的使用中,因为寄存器变量本身也是靠 cache
来处理,为了避免引起错误,也可以使用 volatile修饰符.

简单的说使用 volatile的目的就是:
让对 volatile 变量的存取不能缓存到寄存器,每次使用时需要重新存取。
页: [1]
查看完整版本: 详解C语言如何访问固定的内存地址。 (*(volatile unsigned char *)(0x22))