【发布时间】:2020-08-23 14:36:50
【问题描述】:
我有一个来自 AVR 项目的简短程序集 sn-p:
uint8_t high = _BV(0);
uint8_t low = ~high;
uint8_t port_value = 0;
asm volatile (
"in %0, %1 \n\t"
"or %0, %3 \n\t"
"out %1, %0 \n\t"
T1H_NOOP
"and %0, %2 \n\t"
"out %1, %0 \n\t"
T1L_NOOP
: "=r" (port_value)
: "I" (_SFR_IO_ADDR(PORTB)), "r" (low), "r" (high));
此块背后的想法是在短时间内启用特定引脚(实际物理微处理器引脚)(T1H_NOOP),然后将其关闭。上面的代码实际上完美无缺。
但是,在上面的代码中,确切的引脚是硬编码的:PORTB,引脚 0 (_BV(0))。我想要的是传递这样一个地址:
struct IO_ADDR {
volatile uint8_t *port;
uint8_t pin
}
只要我保留在 C 代码中,这确实有效。
struct IO_ADDR addr = { .port = &PORTB, .bit = 0 };
latch(&addr);
void latch(struct IO_ADDR *addr) {
if (addr->bit >= 8) return;
*(addr->port) &= ~(_BV(addr->bit));
_delay_us(50);
}
当我这么说的时候,我的意思是我已经通过模拟器运行它并且看到引脚按预期启动,另外我已经将这个 sn-p 与上面的组件配对并在硬件上运行它。所以,很明显,*(addr->port) &= ... 正在寻址引脚本身,而不是指针。很酷。
但是当我这样做时,我得到一个组装错误:
asm volatile (
"in %0, %1 \n\t"
"or %0, %3 \n\t"
"out %1, %0 \n\t"
T1H_NOOP
"and %0, %2 \n\t"
"out %1, %0 \n\t"
T1L_NOOP
: "=r" (port_value)
: "I" (_SFR_IO_ADDR(*(addr->port))), "r" (low), "r" (high));
这个错误:
/nix/store/j31yaksw2dh82by2lgz1ysgh494cz6j2-src/neopixels.c: In function 'write_value':
/nix/store/j31yaksw2dh82by2lgz1ysgh494cz6j2-src/neopixels.c:29:9: warning: asm operand 1 probably doesn't match constraints
29 | asm volatile (
| ^~~
/nix/store/j31yaksw2dh82by2lgz1ysgh494cz6j2-src/neopixels.c:29:9: error: impossible constraint in 'asm'
如果我将 addr->port 参数替换为 _SFR_IO_ADDR(addr->port),也会发生这种情况。
SFR_IO_ADDR(*(addr->port)) 对此进行预处理:
: "I" (
# 38 "src/neopixels.c" 3 4
(((uint16_t) &(
# 38 "src/neopixels.c"
*(addr->port)
# 38 "src/neopixels.c" 3 4
)) - 0x20)
# 38 "src/neopixels.c"
)
在PORTB 的情况下,最终程序集应该是这个特定硬件上的地址 0x24(并忽略编译器选择的确切寄存器):
in r18, 24
or r18, r21
out 24, r18
我需要做什么才能将该特定 IO 地址传递给我的汇编代码?
【问题讨论】:
-
您的基本问题是您使用的 IO 地址和指令需要立即操作数,因此需要在编译时进行评估,但一般只能在运行时确定地址.您要么需要使 IO 地址成为始终可以在编译时评估的常量,要么使用将寄存器或内存操作数作为 IO 地址的指令。请注意,IN 和 OUT 指令的地址空间不同于 C 指针使用的地址空间。需要减去 0x20 才能将内存地址转换为 IO 地址。
-
注意这里真的没有必要使用内联汇编。当使用的内存地址是编译时常数时,GCC可以生成IN和OUT指令,并在转换后映射到有效的IO地址。
-
确实编译器可以生成比你更好的汇编代码:gcc.godbolt.org/z/rKshaM
-
如果您需要对每条指令进行精确控制,除了我在上面链接中给出的示例
t1h_noop延迟函数之外,那么您应该使用常规汇编。 -
您可以查看 GCC 为您展示的使用指针的 C 代码 sn-p 生成的程序集。然后你可以在汇编中编写相同类型的东西。