我正在阅读http://embeddedgurus.com/embedded-bridge/2010/03/different-bit-types-in-different-registers/,其中说:
通过读/写位,固件可在需要时设置和清除位.它通常首先读取寄存器,修改所需的位,然后将修改后的值写回
我已经遇到了这个问题,同时保留了一些由老盐嵌入式人员编写的生产代码.我不明白为什么这是必要的.
当我想设置/清除一点时,我总是只是或者使用位掩码.在我看来,这解决了任何线程安全问题,因为我假设设置(通过赋值或使用掩码进行设置)寄存器只需要一个周期.另一方面,如果您首先读取寄存器,然后修改,然后写入,读取和写入之间发生的中断可能导致向寄存器写入旧值.
那么为什么读 - 修改 - 写?还有必要吗?
Chr*_*tix 16
这在某种程度上取决于您的特定嵌入式设备的架构.我将举出三个涵盖常见案例的例子.然而,它的基本要点是,基本上CPU内核不能直接在I/O设备的寄存器上操作,除非以字节或甚至字方式读取和写入它们.
1)68HC08系列,一个8位独立微控制器.
这包括"位设置"和"位清除"指令.如果您仔细阅读本手册,实际上内部会自行执行读 - 修改 - 写周期.它们确实具有原子操作的优点,因为作为单个指令它们不能被中断.
您还会注意到它们比单个读取或写入指令花费的时间更长,但是比使用三个指令更少的时间(见下文).
2)ARM或PowerPC,传统的32位RISC CPU(通常也存在于高端微控制器中).
这些不包括任何可以同时访问存储器和执行计算(和/或)的指令.如果你用C写:
*register |= 0x40;
它变成了下面的程序集(对于这个PowerPC示例,r8包含寄存器地址):
LBZ r4,r8
ORI r4,r4,#0x40
STB r4,r8
Run Code Online (Sandbox Code Playgroud)
因为这是多个指令,所以它不是原子的,可以被中断.使其成为原子甚至SMP安全超出了这个答案的范围 - 它有特殊的指令和技术.
3)IA32(x86)和AMD64.为什么你将这些用于"嵌入式"是超出我的,但它们是另外两个例子之间的中途.
我忘记了x86上是否存在单指令内存中位设置和位清除.如果没有,那么请参阅上面的RISC部分,它只需要两个指令而不是三个,因为x86可以在一条指令中加载和修改.
假设有这样的指令,他们还需要在内部加载和存储寄存器以及修改它.现代版本将在内部将指令分解为三个类似RISC的操作.
奇怪的是,x86(与HC08不同)可以通过总线主机在中间事务中在内存总线上中断,而不仅仅是传统的CPU中断.因此,您可以手动将LOCK前缀添加到需要执行多个内存周期的指令中,如本例所示.但是你不会从普通的C中得到这个.
问题是如果你不想修改寄存器中的其他位,你必须在写它之前知道它们是什么.因此,读/ modiy /写.请注意,如果您使用C语句,如:
*pRegister |= SOME_BIT;
Run Code Online (Sandbox Code Playgroud)
事件虽然在第一次看起来像一个简单的写操作,但编译器必须首先执行读操作以保留值中的其他位(这通常是正确的,即使您不是在讨论硬件寄存器,除非编译器能够使用有关该值的其他知识来优化读取).
请注意,内存映射硬件寄存器通常是volatile专门标记的,因此无法进行这些优化(否则许多硬件寄存器例程将无法正常工作).
最后,有时硬件支持寄存器,专门设置或清除硬件中的位而无需读/修改/写序列.我使用过的一些Atmel ARM微控制器具有特定寄存器,可以清除或设置硬件中的位,只读取写入寄存器时设置的位(仅保留任何未设置的位).此外,Cortex M3 ARM CPU支持通过使用他们称之为"位带"的技术访问特定地址空间来访问存储器或硬件寄存器中的单个位(用于读取或写入).位带算法乍一看看起来很复杂,但实际上只是将一个地址中的位偏移映射到另一个"位特定"地址的简单算法.
无论如何,最重要的是,有一些处理器,你可以在没有读/修改/写入系列的情况下离开,但这绝不是普遍真实的.