嵌入式C

volatile详解

本质 > 从内存读取,而不是寄存器

在嵌入式C语言中,volatile关键字的主要用法可以总结为以下三点:

用法描述示例
防止编译器优化编译器在优化代码时会尝试将变量的访问操作优化为更高效的方式,例如将变量的值缓存在寄存器中。然而,对于某些特殊的变量,如多线程环境下的共享变量、中断处理中的标志位、硬件寄存器等,这种优化可能会导致意外的行为。使用 volatile 关键字可以告诉编译器不要对该变量进行优化,确保每次访问都从内存中读取或写入。uint32_t i = 0x2000;
while(i--);
//软延时等操作中
访问硬件寄存器防止编译器优化对硬件寄存器的访问,确保每次都能从内存地址中读取最新值。volatile uint32_t *reg = (uint32_t *)0x40021018;
*reg = 0x1234;
中断服务程序在中断服务程序中,防止编译器优化被中断修改的变量,确保中断处理时的数据一致性。volatile int interruptFlag = 0;
// 中断服务程序可能会修改interruptFlag
多线程环境下的共享变量在多线程环境中,确保一个线程对共享变量的修改对其他线程可见。volatile int sharedVariable = 0;
// 多个线程可能会读写sharedVariable`

需要注意的是,volatile仅保证了对变量的每次访问都会直接从内存中读取,但并不保证操作的原子性。在多线程或复杂系统中,还需要结合其他同步机制(如互斥锁、原子操作等)来确保数据的一致性和安全性。此外,过度使用volatile可能会导致性能下降,因此应该根据具体的需求和场景谨慎使用。

示例如下:

// stm32 硬件寄存器
#define     __IO    volatile                  /*!< defines 'read / write' permissions   */
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;

#define PERIPH_BASE             ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define APB2PERIPH_BASE         (PERIPH_BASE + 0x10000)
#define GPIOA_BASE              (APB2PERIPH_BASE + 0x0800)
#define GPIOA                   ((GPIO_TypeDef *) GPIOA_BASE)

置位和清零

在嵌入式C语言中,置1(将某一位设置为1)和清0(将某一位设置为0)的操作通常是通过位操作来完成的。

置位(设置为1)

要将一个变量的特定位设置为1,可以使用按位或(OR)操作。通过将目标位与1进行按位或运算,可以将该位设置为1,而不影响其他位。

uint8_t x = 0xF0;
uint8_t y = 1;

x |= (1 << y); 
// 将第1 bit置1,其他位保持不变。相当于 x |= 0x02; 结果x=0xF2

清0(设置为0)

要将一个变量的特定位清0(设置为0),可以使用按位与(AND)操作结合按位取反(NOT)操作。首先,创建一个掩码,该掩码在目标位上为0,其他位上为1。然后,将这个掩码与变量进行按位与运算,以清除目标位。

uint8_t x = 0xF0;
uint8_t y = 4;

x &= ~(1 << y);
// 将第4 bit清为0,其他位保持不变。相当于 x &= 0xEF; 结果 x=0xE0

异或

异或(XOR)操作可以将一个变量的特定位取反。如果目标位为0,则结果为1;如果目标位为1 ,则结果为0。

uint8_t x = 0xF0;
uint8_t y = 4;
x ^= (1 << y); 
// 将第4 bit取反,其他位保持不变。相当于 x ^= 0x10; 结果 x=0xE0

如下是一个stm32用寄存器的方式操作GPIO的示例,可参考。

//stm32f10x
#define LED1_ON			(GPIOB->BRR = GPIO_BRR_BR8)
#define LED1_OFF		(GPIOB->BSRR = GPIO_BSRR_BS8)
#define LED1_FLIP		(GPIOB->ODR ^= GPIO_BSRR_BS8)

// LED1 ->  PB8
void reg_gpio_init(void)
{
    //GPIO时钟开启
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    
    //配置输出,速度
    GPIOB->CRH &= ~GPIO_CRH_MODE8;
    GPIOB->CRH |= GPIO_CRH_MODE8_1;		//2MHz
    //推挽输出
    GPIOB->CRH &= ~GPIO_CRH_CNF8;
    
    //初始配置为1
    GPIOB->ODR |= GPIO_ODR_ODR8;
}