我正在开发一个嵌入式项目,我正在尝试为一些代码添加更多结构,这些代码使用宏来优化对USART寄存器的访问。我想将预处理器#defined'd寄存器地址组织成const结构。如果我在宏中将结构定义为复合文字并将它们传递给内联函数,那么gcc已足够聪明,绕过生成的程序集中的指针并直接在代码中对结构成员值进行硬编码。 E.g:
C1:
struct uart {
volatile uint8_t * ucsra, * ucsrb, *ucsrc, * udr;
volitile uint16_t * ubrr;
};
#define M_UARTX(X) \
( (struct uart) { \
.ucsra = &UCSR##X##A, \
.ucsrb = &UCSR##X##B, \
.ucsrc = &UCSR##X##C, \
.ubrr = &UBRR##X, \
.udr = &UDR##X, \
} )
void inlined_func(const struct uart * p, other_args...) {
...
(*p->ucsra) = 0;
(*p->ucsrb) = 0;
(*p->ucsrc) = 0;
}
...
int main(){
...
inlined_func(&M_UART(0), other_parms...);
...
}
这里UCSR0A,UCSR0B和& c被定义为uart寄存器为l值,如
#define UCSR0A (*(uint8_t*)0xFFFF)
gcc能够完全消除结构文字,并且inlined_func()中显示的所有赋值都直接写入寄存器地址,不必将寄存器的地址读入机器寄存器,并且没有间接寻址:
A1:
movb $0, UCSR0A
movb $0, UCSR0B
movb $0, UCSR0C
这会将值直接写入USART寄存器,而不必将地址加载到机器寄存器中,因此根本不需要在结构文件中生成结构文字。 struct literal成为一个编译时结构,在生成的抽象代码中没有任何代价。
我想摆脱宏的使用,并尝试使用标头中定义的静态常量结构:
C2:
#define M_UART0 M_UARTX(0)
#define M_UART1 M_UARTX(1)
static const struct uart * const uart[2] = { &M_UART0, &M_UART1 };
....
int main(){
...
inlined_func(uart[0], other_parms...);
...
}
但是,gcc无法完全删除结构:
A2:
movl __compound_literal.0, %eax
movb $0, (%eax)
movl __compound_literal.0+4, %eax
movb $0, (%eax)
movl __compound_literal.0+8, %eax
movb $0, (%eax)
这会将寄存器地址加载到机器寄存器中,并使用间接寻址写入寄存器。有谁知道无论如何我可以说服gcc生成C2 C代码的A1汇编代码?我尝试了__restrict修饰符的各种用法,但没有用。
答案 0 :(得分:2)
在使用UART和USART多年后,我得出了以下结论:
struct
与UART寄存器进行1:1映射。编译器可以在您不知情的情况下在struct
成员之间添加填充,从而弄乱1:1的对应关系。
在定义指向寄存器的指针时,请记住使用volatile
修饰符。
只有在通过处理器端口而不是内存映射访问UART时才应使用汇编语言。 C语言不支持端口。通过指针访问UART寄存器非常有效(生成汇编语言列表和验证)。有时,在汇编和验证中编码可能需要更多时间。
这是一个很好的候选人。此外,一旦代码经过测试,就让它成为。不必一直(重新)编译库。
答案 1 :(得分:1)
在我的书中使用“跨编译域”的结构是一个重要的罪。基本上使用结构来指向某事物,任何东西,文件数据,内存等。原因是无论编译器如何,它都会失败,它是不可靠的。为此,有许多编译器特定的标志和编译指示,更好的解决方案是不执行此操作。你想指向地址加8,指向地址加8,使用指针或数组。在这个特定的情况下,我有太多的编译器也没有这样做,我编写汇编程序PUT32 / GET32 PUT16 / GET16函数,以保证编译器不会弄乱我的寄存器访问,如结构,你将被烧毁一天,有一段时间弄清楚为什么你的32位寄存器只有8位写入它。跳转到函数的开销值得安心,代码的可靠性和可移植性。这也使你的代码非常便携,你可以把包装器放入put和get函数来交叉网络,在hdl模拟器中运行硬件,并通过单个代码块进入模拟读/写寄存器等不会从模拟到嵌入式设备更改为os设备驱动程序到应用程序层功能。