stm32很多外设都是用结构体封装寄存器,偏移地址由高到底依次排列,来看下面一段对NVIC的封装
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
可以看到每个寄存器前面有_IO或者_O进行修饰,那么这到底是甚麽意思嘞,是GPIO嘛?当然不是!
通过definition来到底层文件可以看到这样两行宏定义:
#define __O volatile /*!< defines 'write only' permissions */
#define __IO volatile /*!< defines 'read / write' permissions */
说到底这些寄存器变量都用volatile进行了修饰,那么到底有何作用?
我们可以写二段简单的c语言测试代码:
int a;
a = 1;
a = 2;
volatile int a;
a = 1;
a = 2;
我们可以观察到第一段代码的变量a的赋值过程被编译器优化,没有进行a = 1的操作,a变量被直接赋值为2
而第二段代码则不同,a变量首先被赋值为1,然后才被赋值为2
由此我们可以总结出volatile修饰变量的作用:
- 防编译器优化,从而保证任何一个时刻此变量的值都是最新的
- 外设寄存器的值由外设进行实时修改,如果不加volatile修饰,则编译器不会实时去读这个变量的值,可能采用上一时刻缓存中的值
- 由此可以看出这个作用在IO操作,寄存器操作,特殊变量,多线程变量读写都是很重要。