单片机论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 409|回复: 0
收起左侧

嵌入式的C程序中(*(volatile unsigned long *)的理解

[复制链接]
liu100m 发表于 2018-10-22 13:41 | 显示全部楼层 |阅读模式
嵌入式的C程序中(*(volatile unsigned long *)的理解
  (*(volatile unsigned long *)
  
    对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为C语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便的多了。
       
    Volatile    表示可随硬件端口变化。
   
   

以 #define    IOPIN (*((volatile unsigned long *) 0xE0028000)) 为例:作为一个宏定义语句,define是定义一个变量或常量的伪指令。首先( volatile unsigned long * )的意思是将后面的那个地址强制转换成 volatile unsigned long * ,unsigned long    * 是无符号长整形,volatile 是一个类型限定符,如const一样,当使用volatile限定时,表示这个变量是依赖系统实现的,以为着这个变量会被其他程序或者计算机硬件修改,由于地址依赖于硬件,volatile就表示他的值会依赖于硬件。

   
    volatile 类型是这样的,其数据确实可能在未知的情况下发生变化。比如,硬件设备的终端更改了它,现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中,这样,在某些条件下,程序就可以直接访问这些私有内存了。另外,比如共享的内存地址,多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。归纳起来如下:
    1.    volatile变量可变允许除了程序之外的比如硬件来修改他的内容
    2. 访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消

   
   

对于((volatile unsigned long *) 0xE0028000)为随硬件需要定义的一种地址,前面加上“*”指针,为直接指向该地址,整个定义约定符号IOPIN代替,调用的时候直接对指向的地址寄存器写内容既可。这实际上就是内存映射机制的方便性了。其中volatile关键字是嵌入式系统开发的一个重要特点。上述表达式拆开来分析,首先(volatile    unsigned long *) 0xE0028000的意思是把0xE0028000强制转换成volatile unsigned long类型的指针,暂记为p,那么就是#define A *p,即A为P指针指向位置的内容了。这里就是通过内存寻址访问到寄存器A,可以读/写操作。

   
   

对于(volatile unsigned char *)0x20我们再分析一下,它是由两部分组成:
    1)(unsigned char *)0x20,0x20只是个值,前面加(unsigned char *)表示0x20是个地址,(等效#define A *p  )而且这个地址类型是unsigned    char ,意思是说读写这个地址时,要写进unsigned char 的值,读出也是unsigned char 。
   
   

   
   
   

2)volatile,关键字volatile 确保本条指令不会因C 编译器的优化而被省略,且要求每次直接读值。例如用while((unsigned char *)0x20)时,有时系统可能不真正去读0x20的值,而是用第一次读出的值,如果这样,那这个循环可能是个死循环。用了volatile    则要求每次都去读0x20的实际值。
   
      那么(volatile unsigned char *)0x20是一个固定的指针,是不可变的,不是变量。而char *u则是个指针变量。
      

   

再在前面加"*":*(volatile    unsigned char *)0x20则变成了变量(普通的unsigned char变量,不是指针变量),如果#define i (*(volatile unsigned char *)0x20),那么与(等效unsigned char i)  是一样了,只不过前面的i的地址是固定的。
   
   

    那么如何对这条语句进行理解呢?
   
    首先,我们来分析define语句后面的内容: (*(volatile unsigned char *)0x20),首先看到里面使用了关键字volatile和数据类型unsigned char以及C语言中的地址引用符号“*”,将这段代码拆开来看, (unsigned char *)0x20中0x20是一个地址,其存储数据的类型为unsigned char型,即一个8位的二进制数据,如一个8位的寄存器。利用关键字volatile对其进行修饰(volatile unsigned char *)0x20则表示这个地址中存放的数据容易被外部所软件/硬件改变,告诉编译器不要去优化该条语句,每次执行时均从该地址中去读取这个值。再在(volatile unsigned char *)0x20的前面加上地址引用符号"*"则表示一个指针对应的变量,则这条语句就成为了一个可操作的指针所指向的地址内容,相当于*p,只不过这儿p的地址固定为了0x20,*p所指向的数据类型定义为了unsigned char,最后将define后面的内容用括号括起来,这是一个良好的编程习惯!最终就形成了我们所看到的 (*(volatile unsigned char *)0x20)形式。
    ---------------------
    实例
   

以前看到#define SREG    (*(volatile    unsigned char *)0x5F)这样的定义,总是感觉很奇怪,不知道为什么,今天终于有了一点点心得,请大虾们多多批砖~~~
         嵌入式系统编程,要求程序员能够利用C语言访问固定的内存地址。既然是个地址,那么按照C语言的语法规则,这个表示地址的量应该是指针类型。所以,知道要访问的内存地址后,比如0x5F,
         第一步是要把它强制转换为指针类型
    (unsigned    char *)0x5F,AVR的SREG是八位寄存器,所以0x5F强制转换为指向unsigned char类型。
       volatile(可变的)这个关键字说明这变量可能会被意想不到地改变,这样编译器就不会去假设这个变量的值了。这种“意想不到地改变”,不是由程序去改变,而是由硬件去改变

   
   


       第二步,对指针变量解引用,就能操作指针所指向的地址的内容了
       *(volatile unsigned char *)0x5F
       第三步,小心地把#define宏中的参数用括号括起来,这是一个很好的习惯,所以#define SREG    (*(volatile    unsigned char *)0x5F)
        类似的,如果使用一个32位处理器,要对一个32位的内存地址进行访问,可以这样定义:
        #define RAM_ADDR     (*(volatile    unsigned long  *)0x0000555F)
        然后就可以用C语言对这个内存地址进行读写操作了
        以下与普通操作一样

   

读:tmp =    RAM_ADDR;
         写:RAM_ADDR = 0x55

   
   
      
结论:那么你的问题就可解答了,(*(volatile unsigned char *)0x20)可看作是一个普通变量,这个变量有固定的地址,指向0x20。而0x20只是个常量,不是指针更不是变量。
   
  
用法
在嵌入式的C程序中,我们经常看到类似如下的定义:
#define AAA  (*(volatile unsigned char *)0x20)  取内容等效(X=5
#define BBB  (*(volatile unsigned char *)0x4B000020)
读temp=AAA;

写AAA=X22;

完整的Word格式文档51黑下载地址:
volatile unsigned long.zip (16.96 KB, 下载次数: 2)

评分

参与人数 2黑币 +55 收起 理由
sad520ljh + 5 共享资料的黑币奖励!
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

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

本版积分规则

QQ|手机版|小黑屋|单片机论坛 |51hei电子论坛2群 联系QQ:125739409;技术交流QQ群219535678

Powered by 单片机教程网

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