许多 ARM 处理器包含允许在 I/O 和/或 RAM 上进行各种形式的读-修改-写操作的特殊功能。然而,似乎没有一种方法可以在 Cortex-M3 和 Cortex-M0 设备的范围内始终如一地工作。
当我针对 Cortex-M3 设备时,我编写了一些方法MaskedSet32(uint32_t *target, uint32_t mask, uint32_t newval)
,例如使用 load-linked/compare-exclusive 以中断安全的方式原子地修改 32 位数字的一部分。然而,Cortex M0 不提供这些指令。我也许可以让代码保存中断状态、禁用中断、执行读取-修改-写入并重新启用中断,但这似乎有点笨拙。一些 Cortex 处理器提供位带或其他功能,因此可以使用单个原子内存操作来执行诸如以原子方式设置单个位之类的操作,但不同的处理器似乎做事不同。此外,我不确定哪种抽象可以实现最佳程度的可移植性。
对于 RAM 中的东西,我的猜测是,当速度不重要时,最好的方法是为诸如MaskedSet32
, Increment
, BottomPegI32
(的原子版本*dest = (*dest < limit) ? limit : *dest;
)之类的东西设置子例程。这种方法也可以与 I/O 一起使用,但不利用某些处理器提供的一些专用硬件来提高此类效率似乎是一种耻辱。例如,飞思卡尔 KL-25 可以执行如下操作:
MaskedSet32(&IOWhatever, 0x0F00, 0x0500); // Update bits 8-11 with a value of 0101
通过将值 5 写入某个 I/O 地址。5
加载一个 I/O 地址和两个其他参数,然后调用一个子程序来完成可以通过加载一个 I/O 地址和一个常量并执行单个内存存储来完成的工作,这似乎很浪费。
虽然我看到的很多处理器演示代码只使用普通的布尔运算符,但我觉得这样做的想法令人反感
IoWhatever = (IoWhatever & ~0x0F00) | 0x0500;
在任何情况下,中断可能想要写入 I/O 地址的一部分,而主线代码正在写入另一部分。使用原子操作作为一种常见的做法似乎是一种更安全的方法,但我想以尽可能少的方式使代码陷入困境。
我是否应该定义一个 SetIoBitField 宏,它需要一个地址(将被转换为uint32_t*
)、字段偏移量、字段宽度和新数据,要求字段偏移量和宽度是恒定的,然后使用原子读取-修改-写入方法(使用基于常量预先计算的掩码),内联加载链接/存储独占循环(同样),或写入特定于处理器的特殊 I/O 空间,或者我应该定义和使用这样的宏仅在具有此类原语并在其他处理器上使用 MaskedSet32 的处理器上[认为没有位域支持的处理器上的 I/O 位分配无论如何都会有所不同],或者哪种方法看起来最清晰和最便携?