【问题标题】:Which way is faster in initializing registers in a microcontroller?哪种方式在微控制器中初始化寄存器更快?
【发布时间】:2019-06-18 05:48:11
【问题描述】:

在为 PIC 微控制器编写固件时,我想到了这个问题。

我知道有两种方法可以初始化微控制器中的寄存器。举个例子,如果我们将端口初始化为输出,一种方法是编写如下命令,它将1 分配给 TRISx 寄存器中的每一位

方法一

TRISX = 0xFF;

同样的事情可以通过单独分配位来完成。

方法二

_TRISX0 = 1;
_TRISX1 = 1;
_TRISX2 = 1;
...
_TRISX7 = 1;

我的问题是,编译器是否会将其视为相同,完成这两项操作所需的时间是否相同?还是方法 1 需要一个时钟周期,而方法 2 需要 8 个时钟周期(我的意思是慢了约 8 倍)?

我尝试阅读X16 compiler guide,但找不到任何提示。

【问题讨论】:

  • 为什么不检查生成的机器码?
  • 编译器生成的机器码是hex文件吧?
  • 十六进制文件是原始机器代码的二进制解释,以 ASCII 格式表示。它们不是人性化的阅读。你应该看的是反汇编——任何像样的工具链都有反汇编选项。好的将与 C 源代码一起显示反汇编。一些好的调试器也有这个选项。至少您可以在任何调试器中看到原始反汇编。

标签: c embedded microcontroller


【解决方案1】:

硬件寄存器始终是volatile 限定的,并且不允许编译器优化包含volatile 访问权限的代码。所以如果你给他们写 8 次,那么你得到的就是 8 次写。这当然比 1 写入慢得多。

此外,连续多次写入寄存器是非常糟糕的做法,就像它们是 RAM 中的临时变量一样。硬件寄存器往往具有各种微妙的副作用。它们可以具有“一次写入”属性或仅接受某些模式下的写入。通过分几个步骤写信给他们,你就会养成由不正确的寄存器设置引起的各种疯狂的、微妙的问题的习惯。

正确的做法是写入寄存器一次,或尽可能少地写入。

例如,您可能认为您的示例中的数据方向寄存器是一个非常愚蠢且没有副作用的寄存器。但通常 GPIO 硬件需要一些时间来切换端口电路,从写入数据方向寄存器的点到访问 I/O 端口的点。因此,多次写入可能会不必要地停止端口。

假设REGISTER是内存映射的名称,volatile-限定硬件寄存器,那么...

不要这样做:

MASK1 = calculation();
REGISTER |= MASK1;
MASK2 = calculation();
REGISTER |= MASK2;

这样做:

uintx_t reg_val=0; // temp variable in RAM
MASK1 = calculation();
reg_val |= MASK1;
MASK2 = calculation();
reg_val |= MASK2;
REGISTER = reg_val; // single write to the actual register

【讨论】:

  • 请注意,您的答案并不适用于嵌入式世界中的所有内容。一些处理器(和编译器)支持位操作;并且,例如,一些瑞萨 C 编译器可以优化同一寄存器上的后续位写入(合并它们)。
  • @linuxfan 不,(符合)编译器不允许在 volatile 限定的寄存器上执行此操作。假设我想禁用一个中断,然后清除一个状态标志,这些位是同一个寄存器的一部分。将这两个位指令合并为一个会改变代码的含义。目的是让挂起的中断完成,然后清除中断设置的标志。如果在一条指令中完成,则永远不会执行中断。
  • 或者一个更明显的例子:假设我想通过 GPIO 对寄存器进行 bit-bang 移位。
  • 例如,这个 C 编译器:renesas.com/us/en/doc/products/tool/002/rej10j1995_nc30_u.pdf 在第 19 页说,“-Ono_bit -ONB 抑制基于位操作分组的优化”,甚至“-ONLOC 抑制将连续的优化OR 一起。我并不是说这个编译器是完美的,但考虑到 C 语言对比特的支持很少。
  • @linuxfan 你描述的选项和我说的一样:抑制优化,当优化会破坏预期的程序行为时。
【解决方案2】:

这取决于处理器指令集和编译器。以 PIC18F45K20 为例,sdcc 编译器编译如下

TRISDbits.TRISD0 = 1;

BSF _TRISDbits, 0

编译时

TRISD = 0xFF;

MOVLW   0xff
MOVWF   _TRISD

因此在这种情况下,设置单个位更快,因为它不涉及在工作寄存器中放置临时值。

然而,并非所有指令集都包含 BSF 指令,并且某些架构不需要为后一个任务使用工作寄存器。

附:上面的例子是基于 sdcc 编译器的输出,但我想xc8xc16 编译器会产生类似的结果。

附言检查生成的程序集时,请记住某些指令比其他指令消耗更多的处理器周期。有关详细信息,请参阅数据表。

【讨论】:

    【解决方案3】:

    有一件事是,您没有提供 C 代码来显示这些位是如何实际引用的。但是假设它是通过位域的联合和结构。

    最好的方法是实际检查编译器生成的 ASM。您确实需要知道您的硬件架构,但您仍然需要查看生成的 ASM 才能真正了解。

    只分配一个位,比如 _TRISX0=1; vs TRISX = 0x01;,取决于架构和编译器,编译器可能会为单个位分配生成比整个寄存器更高效(更少的周期和可能更少的指令)的代码。 至少有一个来自 TI 的这样的 MCU/DSP 处理器和编译器,我知道这是真的。

    如果您有多个 (>1) 语句,您的方法 2 具有单独的位分配,您的单行寄存器分配可能会更有效或更有效:如果编译器推断 - 错误与否 -所有这些位分配都按顺序分配给同一个寄存器,它可以像方法 1 中那样用单行替换它们。

    我没有特别考虑 PIC。我建议您在需要时检查任何 MCU 的 ASM。

    【讨论】:

    • 如果 CPU 支持位指令,但生成的 C 代码执行寄存器字节访问的程序效率较低,那么编译器非常糟糕。在具有直接写入与读取-修改-写入指令的普通、面向字节的 CPU 上,您会遇到相同的情况。选择正确的是编译器,而不是 C 程序员。
    • @Lundin,“面向字节的 CPU”是指具有 8 位指令和 8 位寄存器的 CPU 吗?我理解“选择正确的是编译器的工作,而不是 C 程序员”,但这并不总是适用于硬件编程。
    • @Lundin “如果 CPU 支持位指令,但生成的 C 代码执行寄存器字节访问的程序效率较低,编译器就非常糟糕。”这取决于架构 - 寻址模式、可用指令和您访问的寄存器。使用 16 位位指令对拱形上的字节寄存器执行有效的/1 周期位或指令可能会破坏您的断言。您考虑的 C 字节 - 即 uint8_t - 甚至可能不会直接映射到硬件字或直接可寻址内存。
    • 你所说的写一个位而不是整个寄存器的情况只适用于单位指令的内核,比字节指令更快。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    • 2013-01-08
    • 2020-09-12
    • 2013-03-13
    • 1970-01-01
    • 2022-09-26
    • 1970-01-01
    相关资源
    最近更新 更多