嵌入式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;
}