今天在网上看到一个讨论“嵌入式C指针问题”的贴子,个人觉得有必要在温习一习,现转贴如下
楼主: 嵌入式C指针问题 |
各位高手: 以上的指针表示 /* General Purpose Input/Output (GPIO) */ /* 通用并行IO口的特殊寄存器 */
#define IO0PIN (*((volatile unsigned long *) 0xE0028000)) /* no in lpc210x*/ #define IO0SET (*((volatile unsigned long *) 0xE0028004)) /* no in lpc210x*/ #define IO0DIR (*((volatile unsigned long *) 0xE0028008)) /* no in lpc210x*/ #define IO0CLR (*((volatile unsigned long *) 0xE002800C)) /* no in lpc210x*/
为什么(volatile unsigned long *) 这里要括号呢? 上面的意思应该是定义IO0PIN 为 0xE0028000(IO0PIN的寄存器为0xE0028000) , 直接 #define IO0PIN 0xE0028000 // 不就得了吗。
还有下面 *((volatile INT16U *)FLASH_START_ADDR)=FLASH_SID_EXIT;
*((volatile INT16U *)FLASH_START_ADDR+FLASH_ADDR_UNLOCK1)=FLASH_DATA_UNLOCK1;
哪位高手帮忙解惑一下。
|
|
XDream 发表于 2007-12-20 19:20 ZLG-ARM ←返回版面  |
2楼: re |
首先你要对这条代码进行理解 (*((volatile unsigned long *) 0xE0028000)) 这条代码的意思是,取0xE0028000地址单元的内容。
而0xE0028000则是代表一个数值。
编译器处理是不一样的。
|
|
zwen01 发表于 2007-12-21 14:45 ZLG-ARM ←返回版面  |
3楼: 还是不清楚 |
直接 #define IO0PIN 0xE0028000 // 应该是可以的,如下例:
#define BEEPCON 0x00000080 // P0.7引脚控制B1,低电平蜂鸣 /**************************************************************************** * 名 称:main() * 功 能:控制蜂鸣器蜂鸣。 ****************************************************************************/ int main(void) { PINSEL0 = 0x00000000; // 设置管脚连接GPIO IO0DIR = BEEPCON; // 设置I/O为输出
while(1) { IO0SET = BEEPCON; // 把0x00000080值送入0xE0028000寄存器,即p0.7置高 } return(0); }
意思我知道,但具体分析就不懂了。
|
|
ATmega16 发表于 2007-12-22 09:27 ZLG-ARM ←返回版面  |
4楼: 看C语言书去吧 |
(*((volatile unsigned long *) 0xE0028000)) 与 ((volatile unsigned long *) 0xE0028000) 都分不清,好好地看C语言书去吧。
|
|
zlgarm 发表于 2007-12-22 11:17 ZLG-ARM ←返回版面  |
5楼: 关于<<嵌入式C指针问题>> |
zwen01 您好!
#define IO0PIN (*((volatile unsigned long *) 0xE0028000)) /* no in lpc210x*/
以此句为例,简述如下:
与51单片机的头文件<reg51.h>类似,在ARM处理器中,也必须有自己的寄存器头文件声明,不同的是,在ARM应用中,需要用户自己来编写.
0xE0028000:在ARM处理器中,只能识别为一个十六进制数值,具体是数据还是地址,它并不能自动区分.
而使用(volatile unsigned int *)0xE0028000,对此数据进行强制转换,表明此数值为一个无符号的长整型地址指针值,关键字volatile 告诉编译器它指向的内容是易变的,可能会被硬件等意外地修改.这一点很关键. *((volatile unsigned int *) 0xFFE00000),则是获取指针所指向地址处的内容,从而,使用了#define 预处理语句对IO0PIN进行定义后,在用户程序中,对IO0PIN的操作,就等同于在0xE0028000地址上进行读写操作. 具体C语言的知识,请参考相关书籍.如果平时在学习中遇到ARM相关的问题,也可以登陆以下网页:
http://www.zlgmcu.com.cn/index.asp?boardid=29&page=1
我们会将一些解决方案汇总在这个网页中进行回复.敬请留意.
zlgarm _wgg
|
|
chenmi0201 发表于 2007-12-23 20:01 ZLG-ARM ←返回版面  |
6楼: zlgarm请继续关注 关于<<嵌入式C指针问题>> |
请问楼上的 zlgarm #define IO0PIN (*((volatile unsigned long *)0xE0028000)) /* no in lpc210x*/ 就像C51的绝对地址访问
但是在EasyARM2200实验板的例程实验十三中(存储器重映射实验),定义了一个宏#define VECTOR_RAM ((unsigned char *) 0x40000000) 这也是绝对地址访问啊,但是为什么没有存储修饰符volatile。地址0x40000000指向的是LPC2210内部的静态RAM,数据也容易改变且也易被编译器优化掉。请大侠给予解惑! 实验程序如下: #include "config.h" #define VECTOR_RAM ((unsigned char *) 0x40000000) /********************************************************** * 名称:main() * 功能:存储器重映射。 **********************************************/ int main(void) { uint8 i; uint8 *addr;
MEMMAP = 0x02; // 中断向量从静态RAM重映射(用户RAM模式)
addr = VECTOR_RAM; for(i=0; i<64; i++) // 用0x55填充0x40000000~0x4000003F { *addr = 0x55; addr++; } addr = VECTOR_RAM; for(i=0; i<64; i++) // 用0xAA填充0x40000000~0x4000003F { *addr = 0xAA; addr++; }
MEMMAP = 0x00; while(1); return(0); }
* - 本贴最后修改时间:2007-12-23 20:16:44 修改者:chenmi0201
|
|
王紫豪 发表于 2007-12-23 23:40 ZLG-ARM ←返回版面  |
7楼: 强制类型转换 |
签名:
----------------------------- ----------||||||||----------- ----------------------------- |
点击进入技术大杂烩,好多资料下载哦!! |
|
|
XDream 发表于 2007-12-24 08:43 ZLG-ARM ←返回版面  |
8楼: volatile |
编译器并不是什么都优化的,它有一定规则。 建议先了解一下volatile。
|
|
chenmi0201 发表于 2007-12-24 11:43 ZLG-ARM ←返回版面  |
9楼: volatile |
个人认为 #define VECTOR_RAM ((unsigned char *) 0x40000000) 加上volatile更可靠。因为在编写代码初期,我们没有去关注编译器如何优化代码的 以上纯属我个人的意见,如果有错误,望大家见量,并且提出宝贵的意见,并多多的指点。
* - 本贴最后修改时间:2007-12-24 11:52:32 修改者:chenmi0201
|
|
zwen01 发表于 2007-12-25 12:18 ZLG-ARM ←返回版面  |
10楼: 有点明白 |
首先,谢谢zlgarm! 用我是会用,只是知其然不知其所以然, 可能如你所说“在ARM处理器中,只能识别为一个十六进制数值,具体是数据还是地址,它并不能自动区分”, 所能要这样定义。 #define IO0PIN (*((volatile unsigned long *) 0xE0028000)) /* no in lpc210x*/ 整句是不是可以这样理解:定义IO0PIN 为一个指针,这个指针为一个易变的无符号长整数型的地址为0xE0028000的指针。 为什么*要跟前面类型结合,而不是跟0xE0028000先结合了。
|
|
machunshui 发表于 2007-12-27 14:40 ZLG-ARM ←返回版面  |
11楼: 这样理解 |
#define IO0PIN (*((volatile unsigned long *) 0xE0028000))
(volatile unsigned long *) 0xE0028000 把0xE0028000这个地址强制转换为long型指针. 即对指针的操作的范围是从0xE0028000开始的4个字节(long型).
volatile 限定编译器不对这个指针的指向的存储单元进行优化, 即不用通用寄存器暂时代替这个指针的指向的存储单元, 而是每次取值都直接到指针的指向的存储单元取值.
volatile 主要用于变量会异步改变的情况下,主要有三个方面:
1.cpu外设寄存器 2.中断和主循环都会用到的全局变量 3.操作系统中的线程间都会用到的公共变量.
|
|
hasayakipw 发表于 2007-12-28 21:49 ZLG-ARM ←返回版面  |
12楼: 转:volatile |
volatile的本意是“易变的”,由于访问寄存器的速度要快过RAM,所以编译器一般都会作减少存取外部RAM的优化。比如: static int i=0; int main(void) { ... while (1) { if (i) dosomething(); } }
/* Interrupt service routine. */ void ISR_2(void) { i=1; }
程序的本意是希望ISR_2中断产生时,在main当中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过 i,因此可能只执行一次对从 i 到某寄存器的读操作,然后每次 if 判断都只使用这个寄存器里面的“i副本”,导致dosomething永远也不会被调用。如果将将变量加上volatile修饰,则编译器保证对此变量的 读写操作都不会被优化(肯定执行)。此例中 i 也应该如此说明。 一般说来,volatile用在如下的几个地方: 1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 2、多任务环境下各任务间共享的标志应该加volatile; 3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义; 另外,以上这几种情况经常还要同时考虑数据的完整性(相互关联的几个标志读了一半被打断了重写),在1中可以通过关中断来实现,2中可以禁止任务调度,3中则只能依靠硬件的良好设计了。
简单点: 就是该变量会以编译器无法预知的方式发生变化,请编译器不要做优化(所有的编译器的优化均假设编译器知道变量的变化规律)
2.关于volatile关键字的说明以及测试 volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。 使用该关键字的例子如下:
int volatile nVint;
当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。例如:
volatile int i=10; int a = i; 。。。//其他代码,并未明确告诉编译器,对i进行过操作 int b = i;
volatile 指出 i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中。而优化做法是,由于编译器 发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在b中。而不是重新从i里面读。这样以来,如果i是一个寄存器变量或者 表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问。 注意,在vc6中,一般调试模式没有进行代码优化,所以这个关键字的作用看不出来。下面通过插入汇编代码,测试有无volatile关键字,对程序最终代码的影响:
首先用classwizard建一个win32 console工程,插入一个voltest.cpp文件,输入下面的代码: #include <stdio.h> void main() { int i=10; int a = i;
printf("i= %d\n",a); //下面汇编语句的作用就是改变内存中i的值,但是又不让编译器知道 __asm { mov dword ptr [ebp-4], 20h }
int b = i; printf("i= %d\n",b); }
然后,在调试版本模式运行程序,输出结果如下: i = 10 i = 32
然后,在release版本模式运行程序,输出结果如下: i = 10 i = 10
输出的结果明显表明,release模式下,编译器对代码进行了优化,第二次没有输出正确的i值。
下面,我们把 i的声明加上volatile关键字,看看有什么变化:
#include <stdio.h> void main() { volatile int i=10; int a = i;
printf("i= %d\n",a); __asm { mov dword ptr [ebp-4], 20h }
int b = i; printf("i= %d\n",b); }
分别在调试版本和release版本运行程序,输出都是: i = 10 i = 32
这说明这个关键字发挥了它的作用!
|
|
uploadfile-/2008-1/59305.73223235.pdf