这个问题是在为PIC微控制器编写一些固件时想到的。
我知道有两种方法可以初始化微控制器中的寄存器。举例来说,如果我们将端口初始化为输出,则一种方法是编写如下命令,它将为TRISx寄存器的每一位分配1
方法1
TRISX = 0xFF;
同一件事可以通过分别分配位来完成。
方法2
_TRISX0 = 1;
_TRISX1 = 1;
_TRISX2 = 1;
...
_TRISX7 = 1;
我的问题是,编译器会否将其视为相同,并且完成这两项操作所需的时间是否相同?还是方法1占用一个时钟周期,而方法2占用8个时钟周期(我的意思是慢8倍)?
我尝试阅读X16 compiler guide,但找不到任何提示。
答案 0 :(得分:11)
硬件寄存器始终是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
答案 1 :(得分:7)
这取决于处理器指令集和编译器。例如,对于PIC18F45K20,sdcc
编译器将编译以下内容
TRISDbits.TRISD0 = 1;
到
BSF _TRISDbits, 0
在编译时
TRISD = 0xFF;
到
MOVLW 0xff
MOVWF _TRISD
因此在这种情况下,设置单个位会更快,因为它不涉及在工作寄存器中放置临时值。
但是,并非所有指令集都包含BSF
指令,并且某些体系结构不需要为后一项任务使用工作寄存器。
P.S。上面的示例基于sdcc编译器的输出,但是我想xc8
和xc16
编译器会产生相似的结果。
P.P.S。检查生成的程序集时,请记住,某些指令比其他指令消耗更多的处理器周期。有关详细信息,请参见数据表。
答案 2 :(得分:1)
有一件事,您没有提供C代码来显示这些位是如何被实际引用的。但是,可以说这是通过位域的并集和结构实现的。
最好的方法是实际检查编译器生成的ASM。您确实需要知道自己的硬件架构,但是仍然需要查看生成的ASM才能真正知道。
仅分配一个位,例如_TRISX0 = 1; vs TRISX = 0x01 ;,取决于架构和编译器,与整个寄存器相比,编译器仅针对单个位分配就可能生成更高效(更少的周期,可能更少的指令)的代码。 TI至少有一个这样的MCU / DSP处理器和编译器,我知道这是真的。
对于具有多个(> 1)语句,方法2(具有单独的位分配)的情况,单行寄存器分配可能会更高或更有效:如果编译器推论(错误或错误地)所有这些位分配都按顺序分配给同一寄存器,它可以像在方法1中那样用单线替换它们。
我没有特别考虑使用PIC。我建议您在需要时检查所有MCU的ASM。