我在一个嵌入式应用程序中有一个错误,我不确定解决的最佳方法。我有一个局部变量,我使用地址作为DMA传输的指针。我在程序中这样做了一点,但直到最近才导致失败。我认为这是因为优化是在程序中使用地址之后但在DMA传输发生之前回收内存用于其他目的。示例:
struct {
volatile uint32_t address_ptr;
} DMA;
void spi_write(uint8_t *data) {
DMA.address_ptr = reinterpret_cast<volatile uint32_t>(data);
}
main() {
uint8_t data_out[2] = {0xAB, 0xCD};
spi_write(data_out);
}
字符串
spi上的结果是随机数被发送出去。我可以通过将data_out设置为全局变量来修复它。所以我假设C编译器假设它可以在DMA.address_ptr
被设置后将data_out
内存用于其他目的。我想不出有什么方法可以防止这种情况发生。有什么方法可以防止它像这样被优化吗?还是有其他错误?
处理器是STM32皮质M4。在优化和未优化的情况下,DMA地址指向内存区域,这是它可以访问的(0x 20000000块)。问题显示本身独立于内存位置:godbolt链接。
添加一些额外的调试的基础上的意见和修补godbolt编译器资源管理器.我使用的arm gcc 32位,虽然我确实发现,clang似乎并没有优化出分配.在-O 0有一个明确的初始化data_out的值52651,这是0xCDAB.
movw r3, #52651
型
在任何更高级别的优化中,该指令都会被删除。但程序并没有优化到什么都没有。事实上,它似乎只是从堆栈中加载了一些未初始化的内存。这似乎也是我的大程序中发生的事情。如果我让address_ptr
成为非易失性的,那么它将优化到什么都没有。到目前为止,我已经找到了三种保持赋值的解决方案。
- decrypt spi_write as
spi_write(volatile uint8_t *data)
- 在某处插入汇编指令。它可以是任何
asm("nop");
- 让
data_out
成为一个全局的静态常量。它会被初始化。
我不明白这些解决办法是否是处理这个问题的好方法。
2条答案
按热度按时间r6hnlfcb1#
(a)程序中没有任何东西通知编译器
data_out
将被读取。它的地址被放入DMA.address_ptr
中,而DMA_address_ptr
是volatile的,所以这告诉编译器有东西将读取该地址。但这不足以告诉编译器data_out
中的字节将被任何东西读取。由于编译器没有理由相信这些字节将被读取,它没有理由在字节中存储任何东西。所以编译器可以优化对data_out
的写入,包括它的初始化。要解决这个问题,用volatile
声明data_out
。(b)程序中没有任何东西等待DMA完成。即使
data_out
被声明为static
,程序也可能在DMA完成之前终止。在DMA工作的情况下,可能在终止程序的过程完成之前DMA就已经完成了(main
返回,执行退出函数,调用操作系统以终止进程,并且操作系统回收内存)。你的程序中应该有一些东西等待DMA完成。一旦你有了那个东西,你就可以释放
data_out
的内存。它可以在main
中自动释放。(或其他例程),然后从main
返回(或其他例程),它可以是static
,程序从main
返回,它可以是动态分配的,程序释放它。这是你需要的两个部分volatile
声明data_out
,以通知编译器data_out
的内容已通过编译器未知的方式读取。data_out
的内存。pobjuy322#
有没有办法避免这种优化?
便携式?不。
你想要的是:
1.静态变量,以及
1.不要从
main
返回。在
main
的主体执行时,静态变量保证存在。因此,当输入main
时,静态变量已经初始化,并且它们将存在,直到从main
返回。所以,简单的解决方法是:
字符串
请注意,静态生存期是必需的。仅仅在
main
的末尾循环并不一定会修复它:型
这是因为在
spi_write
函数返回后,*C代码 * 不会访问data_out
。因此,从C代码的Angular 来看,data_out
的生存期已经结束,因为没有代码可以使用data_out
。另一方面,
static
存储类完全符合DMA访问的要求:只要执行的主线程在main
主体内,它就确保变量是活的。因此,static
结合无限循环是一种可移植的方式。该循环还可以监视DMA状态以检测传输的结束,并在DMA传输结束时中断。此时SPI外设可能仍然忙碌传输数据,因此为了完全避免硬件抽象代码中的变化,您可能希望在DMA传输结束后延迟(睡眠)一段固定时间,或者在DMA传输完成后,监视SPI繁忙状态,并且仅在SPI不忙碌时退出。不要跳过DMA传输状态,因为否则当您在DMA传输仍在进行并且下一个字节将很快被加载到SPI传输寄存器中时“捕获”SPI在“字节之间“空闲时,可能会出现竞争条件。只有通过 * 首先 * 等待DMA传输结束,然后等待SPI外设空闲,你可以合理地确定循环(以及
main
)可以退出。不过,在
main
的末尾添加一个无限循环并没有什么错-简单而有效。wait循环的主体中可以有一个
asm("halt");
,以节省能量。没有优化。你的
data_out
是一个自动变量,从它的定义到代码块的结尾都在作用域中。自从你从main退出,data_out
也就不再存在了。这不是编译器做的,而是你的代码告诉编译器去做的-通过C语言的语义。换句话说,编译器并不需要“优化”任何东西,它只是做标准期望它做的事情,而你的代码假设变量的生存期在C标准中是找不到的。