设置或清除 I/O 端口中的任何位组合(即使设置某些位和清除其他位)最多需要三个指令;当重复这样的指令时,每个指令应该有一个指令。不幸的是,似乎许多供应商(其中包括 ST)倾向于定义 I/O 库,这些库即使对于诸如此类的常见操作也会生成子例程调用。
我建议定义自己的方法来设置和清除 I/O 位,并__inline
在 .h 文件中包含这些方法的代码,用限定符标记[而不是仅在 .h 和方法中包含原型.c 文件中的代码]。
如果在 .h 文件中,则编写:
__inline void SetPorts(GPIO_TypeDef* Addr, uint16_t data) { *Addr = data; }
__inline void ClearPorts(GPIO_TypeDef* Addr, uint16_t data) { *Addr = data; }
然后代码:
setPorts(GPIOA, 1);
clearPorts(GPIOA, 2);
clearPorts(GPIOA, 1);
可能会产生类似的东西(我对一些细节并不积极):
ldr r0,=GPIOA
mov r1,#1
strh r1,[r0+20]
mov r2,#2
strh r2,[r0+22]
strh r1,[r0+22]
相比之下,如果使用GPIO_WriteBit
代码最终会更像:
ldr r0,=GPIOA
mov r1,#1
mov r2,#1
bl _GPIO_WriteBit
ldr r0,=GPIOA
mov r1,#2
mov r2,#0
bl _GPIO_WriteBit
ldr r0,=GPIOA
mov r1,#2
mov r2,#0
bl _GPIO_WriteBit
_GPIO_WriteBit: ; Code below assumes pretty good compiler optimization
cmp r2,#0
itteq
streq r1,[r0+20]
strne r1,[r0+22]
bx lr
第一个示例对所有三个操作总共执行六条指令。第二个示例在主代码中执行 12 条指令,在 WriteBits 函数中执行大约 4 条指令,每条指令执行 3 次,总共 24 条[跳过的指令有时会增加执行时间,有时不会]。通常调用子程序的目的是在代码大小与执行速度之间进行权衡,但在这种情况下,子程序调用从空间和时间的角度来看都是一场灾难。指令完成的唯一有用工作是单个存储操作;该代码可能会使寄存器 r0-r2 不受干扰,但调用代码将无法知道这一点。因此,必须在每个方法调用之前显式设置所有三个参数。
我不知道为什么芯片供应商会定义写入 GPIO 位但不费心将它们内联的方法,但我建议在大多数情况下,如果有人关心任何事情,应该避免使用芯片供应商提供的函数来写入 GPIO 端口关于效率。
顺便说一下,作为一般性的进一步说明,我通常对供应商提供的 I/O 功能持怀疑态度。虽然它们有时可以为程序员提供比原始硬件更高级别的抽象,这很有用,但在许多情况下,它们会使代码更难编写、更难阅读且效率更低。它们有时还会产生不希望的副作用,可能会导致其他地方出现问题。例如,如果外设需要将时钟设置为两种模式中的一种,并且通常与其中一种模式配合得更好,则供应商提供的外设库可能会将时钟配置为“更好”模式,即使该时钟是与另一个需要它的外围设备共享。当外围设备共享资源时(就像他们经常做的那样),如果不阅读和理解其中的所有代码,可能无法正确使用这些库。如果库的全部目的是保存一些寄存器写入,那么库调用的后果可能比它替换的代码的后果更难弄清楚。