目录
C语言volatile关键字学习
volatile原意是“易变的”,在嵌入式环境中用volatile关键字声明的变量,在每次对其值进行引用的时候 都会从原始地址取值。由于该值“易变”的特性所以,针对其的任何赋值或者获取值操作都会被执行(而不 会被优化)。由于这个特性,所以该关键字在嵌入式编译环境中经常用来消除编译器的优化,可以分为以下 三种情景:
-
修饰硬件寄存器;
-
修饰中断服务函数中的非自动变量;
-
在有操作系统的工程中修饰会被多个应用修改的变量;
-
修饰硬件寄存器
以STM32F103的HAL库函数中GPIO的定义举例,如下为HAL库中GPIO寄存器定义:
/**
* @brief General Purpose I/O
*/
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint32_t BSRR;
__IO uint32_t BRR;
__IO uint32_t LCKR;
} GPIO_TypeDef;
其中__IO的定义是:
#define __IO volatile /*!< Defines 'read / write' permissions */
然后定义GPIO是:
#define GPIOA ((GPIO_TypeDef *)GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
#define GPIOC ((GPIO_TypeDef *)GPIOC_BASE)
#define GPIOD ((GPIO_TypeDef *)GPIOD_BASE)
#define GPIOE ((GPIO_TypeDef *)GPIOE_BASE)
#define GPIOF ((GPIO_TypeDef *)GPIOF_BASE)
#define GPIOG ((GPIO_TypeDef *)GPIOG_BASE)
而GPIOx_BASE的定义是这样的:
#define GPIOA_BASE (APB2PERIPH_BASE + 0x00000800UL)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x00000C00UL)
#define GPIOC_BASE (APB2PERIPH_BASE + 0x00001000UL)
#define GPIOD_BASE (APB2PERIPH_BASE + 0x00001400UL)
#define GPIOE_BASE (APB2PERIPH_BASE + 0x00001800UL)
#define GPIOF_BASE (APB2PERIPH_BASE + 0x00001C00UL)
#define GPIOG_BASE (APB2PERIPH_BASE + 0x00002000UL)
其中APB2外设基地址的定义:
#define APB2PERIPH_BASE (PERIPH_BASE + 0x00010000UL)
最后再来看外设基地址的定义:
#define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
综合起来,将宏定义一一展开,仅用GPIOA来看,其它的以此类推:
#define GPIOA ((GPIO_TypeDef *)(0x40000000UL + 0x00010000UL + 0x00000800UL))
如此定义之后,那么GPIOA的CRL的地址就是:
(volatile uint32_t *)(0x40000000UL + 0x00010000UL + 0x00000800UL)
CRH的地址就是:
(volatile uint32_t *)(0x40000000UL + 0x00010000UL + 0x00000800UL + 2)
后面的寄存器以此类推,因而在程序中使用:
GPIOA->CRH |= 0x01;
那么实现的功能就是对GPIOA的CRH的寄存器的最低位拉高。如果在定义GPIO的寄存器结构体里面 没有使用__IO uint16_t,而是仅使用uint16_t,那么在程序中再用语句:
GPIOA->CRH |= 0x01;
就有可能会被编译器优化,不执行这一语句,从而导致拉高CRH的最低位这个功能无法实现;但是库函 数中使用了volatile来修饰,那么编译器就不会对此语句优化,在每次执行这一语句的时候都会从CRH对应 的内存地址里面去取值或者存值,保证了每次执行都是有效的。
-
在有操作系统的工程中修饰会被多个任务修改的变量
在嵌入式开发中,不仅仅有单片机裸机开发,也有带有操作系统的开发,通常两者使用C语言开发的较 多。在有操作系统(比如RTOS、UCOS-II、Linux等)的设计中,如果有多个任务在对同一个变量进行赋值 或取值,那么这一类变量也应使用volatile来修饰保证其可见性。所谓可见即:当前任务修改了这一变量的值, 同一时刻,其它任务此变量的值也发生了变化。