#define SREG (*(volatile unsigned CHAR *)0x5F)

本文详细解释了在嵌入式系统编程中如何使用volatile和指针来访问固定内存地址,包括如何将地址转换为指针类型、使用volatile关键字的意义以及在不同场景下的应用。

终于理解了#define SREG (*(volatile unsigned CHAR *)0x5F) 以前看到#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;

已下是文章二

对于不同的计算机体系结构,设备可能是端口映射,也可能是内存映射的。如果系统结构支持独立的IO地址空间,并且是端口映射,就必须使用汇编语言完成实际对设备的控制,因为C语言并没有提供真正的“端口”的概念。如果是内存映射,那就方便的多了。

         以 #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是个地址,而且这个地址类型是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的地址是固定的。

那么你的问题就可解答了,(*(volatile unsigned char *)0x20)可看作是一个普通变量,这个变量有固定的地址,指向0x20。而0x20只是个常量,不是指针更不是变量。

/* Servo.h - Interrupt driven Servo library for Arduino using 16 bit timers- Version 2 Copyright (c) 2009 Michael Margolis. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method. The servos are pulsed in the background using the value most recently written using the write() method. Note that analogWrite of PWM on pins associated with the timer are disabled when the first servo is attached. Timers are seized as needed in groups of 12 servos - 24 servos use two timers, 48 servos will use four. The sequence used to sieze timers is defined in timers.h The methods are: Servo - Class for manipulating servo motors connected to Arduino pins. attach(pin ) - Attaches a servo motor to an i/o pin. attach(pin, min, max ) - Attaches to a pin setting min and max values in microseconds default min is 544, max is 2400 write() - Sets the servo angle in degrees. (invalid angle that is valid as pulse in microseconds is treated as microseconds) writeMicroseconds() - Sets the servo pulse width in microseconds read() - Gets the last written servo pulse width as an angle between 0 and 180. readMicroseconds() - Gets the last written servo pulse width in microseconds. (was read_us() in first release) attached() - Returns true if there is a servo attached. detach() - Stops an attached servos from pulsing its i/o pin. */ #ifndef Servo_h #define Servo_h #include <inttypes.h> /* * Defines for 16 bit timers used with Servo library * * If _useTimerX is defined then TimerX is a 16 bit timer on the current board * timer16_Sequence_t enumerates the sequence that the timers should be allocated * _Nbr_16timers indicates how many 16 bit timers are available. */ // Architecture specific include #if defined(ARDUINO_ARCH_AVR) #include "avr/ServoTimers.h" #elif defined(ARDUINO_ARCH_SAM) #include "sam/ServoTimers.h" #elif defined(ARDUINO_ARCH_SAMD) #include "samd/ServoTimers.h" #elif defined(ARDUINO_ARCH_STM32F4) #include "stm32f4/ServoTimers.h" #elif defined(ARDUINO_ARCH_NRF52) #include "nrf52/ServoTimers.h" #else #error "This library only supports boards with an AVR, SAM, SAMD, NRF52 or STM32F4 processor." #endif #define Servo_VERSION 2 // software version of this library #define MIN_PULSE_WIDTH 544 // the shortest pulse sent to a servo #define MAX_PULSE_WIDTH 2400 // the longest pulse sent to a servo #define DEFAULT_PULSE_WIDTH 1500 // default pulse width when servo is attached #define REFRESH_INTERVAL 20000 // minumim time to refresh servos in microseconds #define SERVOS_PER_TIMER 12 // the maximum number of servos controlled by one timer #define MAX_SERVOS (_Nbr_16timers * SERVOS_PER_TIMER) #define INVALID_SERVO 255 // flag indicating an invalid servo index #if !defined(ARDUINO_ARCH_STM32F4) typedef struct { uint8_t nbr :6 ; // a pin number from 0 to 63 uint8_t isActive :1 ; // true if this channel is enabled, pin not pulsed if false } ServoPin_t ; typedef struct { ServoPin_t Pin; volatile unsigned int ticks; } servo_t; class Servo { public: Servo(); uint8_t attach(int pin); // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure uint8_t attach(int pin, int min, int max); // as above but also sets min and max values for writes. void detach(); void write(int value); // if value is < 200 its treated as an angle, otherwise as pulse width in microseconds void writeMicroseconds(int value); // Write pulse width in microseconds int read(); // returns current pulse width as an angle between 0 and 180 degrees int readMicroseconds(); // returns current pulse width in microseconds for this servo (was read_us() in first release) bool attached(); // return true if this servo is attached, otherwise false private: uint8_t servoIndex; // index into the channel data for this servo int8_t min; // minimum is this value times 4 added to MIN_PULSE_WIDTH int8_t max; // maximum is this value times 4 added to MAX_PULSE_WIDTH }; #endif #endif Servo.h文件中是这样的该怎么修改
06-11
<think>好的,我现在需要帮助用户理解C语言中的宏定义代码`(*(unsigned char volatile xdata *)地址)`的含义和用途。根据用户提供的引用内容,特别是引用[2]和[3],以及我之前关于操作的回答,我需要逐步解析这个宏定义的结构和作用。 首先,用户引用的内容提到了Keil C51库中的宏定义,比如`XBYTE`,这些宏用于绝对地址访问。用户给出的例子`(*(unsigned char volatile xdata *)地址)`看起来类似这些宏的结构,需要拆解每个部分。 我需要先分解这个宏定义的各个部分: 1. `unsigned char`:这应该是指数据类型,8无符号字符。 2. `volatile`:关键字,告诉编译器不要优化此变量,因为它可能被外部修改。 3. `xdata`:在51单片机中,xdata指外部RAM存储区,地址范围更大。 4. `*`:指针运算符,将地址转换为指针。 5. 最外层的`*(...)`:解引用指针,访问该地址的内容。 接下来,结合用户提供的引用[2]中的例子`#define IOPIN (*((volatile unsigned long *) 0xE0028000))`,这里也是类似的模式,将地址强制转换为指针类型,然后解引用。引用[3]提到`XBYTE`相当于指向外部数据区的指针,可以通过数组或指针运算访问特定地址。 用户可能想知道这个宏的具体作用,比如如何通过它访问硬件寄存器或特定内存地址。需要解释每个关键字的作用,尤其是`volatile`的重要性,防止编译器优化导致意外行为。同时,`xdata`在51架构中的意义,区分不同的存储区域(data, idata, xdata等)。 可能还需要对比其他宏定义,如引用[1]中的`XBYTE`,说明用户给出的宏与标准库中的宏有何异同。例如,`XBYTE`定义的是基地址0,通过数组索引访问,而用户的例子是直接指定地址,可能需要通过宏参数传入地址值。 另外,用户之前的问题涉及到操作符`&=`和`|=`,这里可能需要连接起来,说明在底层编程中,如何通过宏访问特定地址,然后使用操作来设置或清除寄存器。例如,通过类似`*(volatile unsigned char*)0x3000 |= 0x01;`来设置某个寄存器的。 需要注意用户可能的应用场景,比如嵌入式开发中的硬件寄存器操作,或者访问外部存储器。需要强调这种直接地址访问在系统级编程中的常见用途和注意事项,如确保地址正确性,避免内存冲突等。 最后,要确保解释清晰,避免技术术语过于复杂,同时覆盖用户可能存在的疑问点,比如volatile的作用、指针转换的意义、存储空间修饰符的重要性等。结合用户提供的引用内容,确保回答准确且有依据。</think>在C语言中,宏定义`(*(unsigned char volatile xdata *)地址)`是**直接访问特定物理地址的经典写法**,尤其在嵌入式开发中用于操作硬件寄存器或访问特定内存区域。以下是其详细解析: --- ### 一、语法拆解 #### 1. **`unsigned char`** - **作用**:声明操作的数据类型为**无符号8字符**(1字节)。 - **意义**:表示访问的目标地址存储的是单字节数据,例如外设寄存器的8状态值。 #### 2. **`volatile`** - **作用**:禁止编译器优化对此地址的访问[^2]。 - **必要性**:硬件寄存器或共享内存的值可能被外部修改(如中断、硬件触发),`volatile`确保每次访问都直接从内存读取最新值,而非使用缓存值。 #### 3. **`xdata`** - **作用**:指定存储空间类型(针对8051架构)[^1]。 - **意义**: - 在51单片机中,`xdata`表示**外部扩展RAM**(地址范围0x0000-0xFFFF),需通过MOVX指令访问。 - 其他存储类型如`data`(片内RAM)、`code`(程序存储器)也有类似用法。 #### 4. **`(unsigned char volatile xdata *)`** - **作用**:将数值地址强制转换为**指向xdata空间的无符号字符指针**。 - **示例**: 若地址为`0x3000`,则`(unsigned char volatile xdata *)0x3000`生成一个指向外部RAM 0x3000地址的指针。 #### 5. **最外层的`*`** - **作用****解引用指针**,实现对目标地址的直接读写。 - **完整意义**: `*(指针)`表示访问指针指向的地址内容,等同于直接操作该物理地址。 --- ### 二、实际用途 #### 1. **访问硬件寄存器** 在嵌入式系统中,外设寄存器(如GPIO、UART)的地址是固定的。通过此宏可直接读写寄存器: ```c // 示例:向地址0x3000写入值0xFF *(unsigned char volatile xdata *)0x3000 = 0xFF; // 写操作 unsigned char val = *(unsigned char volatile xdata *)0x3000; // 读操作 ``` #### 2. **替代Keil C51标准库的XBYTE** 引用[1]中的`XBYTE`宏定义: ```c #define XBYTE ((unsigned char volatile xdata *) 0) ``` - 用户代码`*(unsigned char volatile xdata *)地址`相当于直接扩展了`XBYTE[地址]`的功能[^3]。 - 区别:`XBYTE`基地址为0,需通过索引访问(如`XBYTE[0x3000]`),而用户代码直接指定任意地址。 --- ### 三、关键设计思想 #### 1. **地址强制转换** - **原理**:C语言允许将整数直接转换为指针,通过指针操作访问物理地址。 - **语法规则**:`(目标类型)地址`将数值转换为特定类型的指针,例如: ```c int *p = (int *)0x4000; // 将0x4000转换为int型指针 ``` #### 2. **volatile的必要性** - **避免优化陷阱**:若未使用`volatile`,编译器可能合并重复的读操作或删除“冗余”写操作,导致硬件交互错误。 - **典型场景**:轮询状态寄存器时,必须用`volatile`确保每次读取真实值: ```c while (*(volatile unsigned char *)0x3000 & 0x80); // 等待最高变为0 ``` #### 3. **存储空间修饰符(xdata)** - **硬件相关性**:在51架构中,不同存储空间(data/xdata/code)的访问方式不同,需通过修饰符告知编译器生成正确的指令(如MOVX)。 --- ### 四、对比其他写法 #### 1. **结构体映射寄存器组** 引用[4]提到,通过结构体管理连续寄存器更高效: ```c typedef struct { volatile unsigned char CTRL; // 控制寄存器 volatile unsigned char STATUS;// 状态寄存器 } UART_TypeDef; #define UART0 ((UART_TypeDef *)0x3000) UART0->CTRL = 0x01; // 访问0x3000地址 ``` - **优势**:代码可读性高,适合管理连续多寄存器。 #### 2. **宏定义的简化** 可封装为通用宏,提升代码复用性: ```c #define REG(addr) (*(unsigned char volatile xdata *)(addr)) REG(0x3000) = 0xFF; // 等效于原写法 ``` --- ### 五、应用场景 1. **硬件初始化**:配置外设寄存器(如设置GPIO方向)。 2. **中断服务程序**:读写状态寄存器清除中断标志。 3. **内存映射设备**:操作外部扩展RAM或FPGA寄存器。 --- ### 六、注意事项 1. **地址合法性**:确保访问的地址在硬件允许范围内,否则可能导致程序崩溃或硬件损坏。 2. **对齐问题**:某些架构要求地址对齐(如32系统需4字节对齐),需注意数据类型匹配。 3. **跨平台限制**:此写法高度依赖硬件和编译器,在非嵌入式环境(如PC)中可能无效。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值