为何STM32标准外设库使用tmpreg而非直接操作寄存器?
STM32 SPL中ADC寄存器操作使用临时变量的原因分析
问题场景
在STM32标准外设库(SPL)的ADC驱动代码里,修改ADCx->CR1寄存器有两种写法:
写法1:使用临时变量tmpreg
/* 读取寄存器原有值 */ tmpreg = ADCx->CR1; /* 清除模拟看门狗通道选择位 */ tmpreg &= CR1_AWDCH_RESET; /* 设置模拟看门狗通道 */ tmpreg |= ADC_Channel; /* 将修改后的值写回寄存器 */ ADCx->CR1 = tmpreg;
写法2:直接操作寄存器
/* 清除模拟看门狗通道选择位 */ ADCx->CR1 &= CR1_AWDCH_RESET; /* 设置模拟看门狗通道 */ ADCx->CR1 |= ADC_Channel;
下面解释两种写法的差异、优缺点,以及SPL选择写法1的原因。
核心原因:避免竞态条件
SPL采用临时变量的写法,本质是为了实现原子化的读-修改-写操作:
- 直接操作寄存器时,
&=和|=是两次独立的寄存器读写操作。如果在这两个操作之间,有中断服务程序、RTOS任务或者DMA操作修改了ADCx->CR1的其他位,最终的寄存器值就会丢失这些外部修改,导致逻辑错误。 - 用临时变量的话,先把寄存器值读到RAM里,在RAM中完成位清除和设置,最后一次性写回寄存器。整个过程寄存器只被读一次、写一次,中间不会被外部操作打断,从根本上避免了竞态问题。
两种写法的优缺点对比
临时变量写法
- 优点:
- 操作原子性强,在多任务、中断密集的环境下可靠性更高。
- 调试时可以直接查看
tmpreg的中间值,便于定位问题。 - 多数MCU架构中,RAM的读写速度比外设寄存器快,连续的RAM位操作比多次寄存器操作效率更高。
- 缺点:
- 占用少量栈空间(通常是4字节,对STM32来说几乎可以忽略)。
- 代码行数稍多,写法相对繁琐。
直接操作寄存器写法
- 优点:
- 代码简洁直观,行数少,容易理解。
- 不占用额外的栈空间。
- 缺点:
- 非原子操作,存在竞态风险,在复杂运行环境下可能出现数据错误。
- 部分STM32外设寄存器对读-修改-写有特殊要求,分两次操作可能触发意外的硬件行为(虽然ADC_CR1没有这类问题,但其他寄存器比如定时器控制寄存器可能存在)。
自定义驱动开发建议
如果你的驱动需要在中断频繁、多任务(RTOS)或有其他外设共享同一寄存器的场景下运行,优先用临时变量的原子操作写法;如果是单任务、无中断的简单场景,直接操作寄存器也能正常工作,但从代码健壮性和可移植性考虑,更推荐前者。
内容的提问来源于stack exchange,提问作者Lunch
相关产品推荐
相关产品推荐

