在Core M33上,如何对I/O端口的位进行原子修改?与Core M4不同,M33没有位带(bit-banding)
我们有一个针对M4内核的编译器。现在我们想把它移植到不支持位带(bit banding)的M33内核。
我们已经使用下面的宏生成了ARM汇编代码,这些代码使用位带逻辑。
#define BITBAND_ALIAS(addr, bit) ((0x42000000) + (((addr) - 0x40000000) * 32) + ((bit) * 4))
#define ACCESS_BIT(addr, bit) (*((volatile unsigned long *)(BITBAND_ALIAS(addr, bit))))
我应该如何为M33内核替换我的位带宏?
我在代码中持续且非常快速地访问外设的单一端口引脚,同时还在使用多个中断。那么我应该如何实现?我知道LDREX和 STRX,但应该在代码中如何使用,以确保其他功能不受影响。
读取很容易,只需读取整个端口并提取所需的位。
但如何在不对其他位进行非原子性RMW(读取-修改-写入)的情况下写入某个位?因为被我们中断的代码可能对其他位进行了写操作,从而干扰当前写入?
解决方案
Cortex-M33不支持位带,因此没有直接替代 BITBAND_ALIAS() 宏的方法。
对于Cortex-M33 / Armv8-M,当你需要原子的软件读取-修改-写入操作时,推荐的体系结构替代是使用exclusive访问,例如 LDREX / STREX。
然而,对于GPIO或 PIO外设,通常最好的选项是在外设提供原子寄存器时使用硬件支持的原子寄存器。例如,许多外设具有专用的SET、CLEAR或 TOGGLE寄存器,例如:
GPIOx->SET = pin_mask; // atomically set pin
GPIOx->CLEAR = pin_mask; // atomically clear pin
GPIOx->TOGGLE = pin_mask; // atomically toggle pin, if supported
这些避免了非原子性读取-修改-写入的问题,因为位操作由外设硬件处理。
如果外设没有提供SET / CLEAR / TOGGLE寄存器,那么就需要在软件中对读取-修改-写入进行保护。在Cortex-M33上,这通常意味着使用exclusivity的访问循环,前提是目标外设/总线区域支持独占访问:
do {
old_val = __LDREXW(reg);
new_val = old_val | pin_mask; // or old_val & ~pin_mask
} while (__STREXW(new_val, reg) != 0);
__DMB();
你应该查阅MCU厂商的文档,因为并非所有外设区域都支持独占访问。
因此实际的偏好顺序是:
- 使用外设的原子SET / CLEAR / TOGGLE寄存器。
- 否则在该外设支持的情况下使用
LDREX/STREX。 - 否则在RMW操作周围使用一个简短的临界区。
简而言之:在Cortex-M33上你不能用另一个别名地址的宏来替代位带。你需要将访问方式改为硬件原子寄存器,或采用受保护的软件RMW序列。