DMA中局部变量的C++优化

eufgjt7s  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(109)

我在一个嵌入式应用程序中有一个错误,我不确定解决的最佳方法。我有一个局部变量,我使用地址作为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成为一个全局的静态常量。它会被初始化。

我不明白这些解决办法是否是处理这个问题的好方法。

r6hnlfcb

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的内容已通过编译器未知的方式读取。
  • 您的程序必须等待DMA完成,然后才能释放data_out的内存。
pobjuy32

pobjuy322#

有没有办法避免这种优化?
便携式?不。
你想要的是:
1.静态变量,以及
1.不要从main返回。
main的主体执行时,静态变量保证存在。因此,当输入main时,静态变量已经初始化,并且它们将存在,直到从main返回。
所以,简单的解决方法是:

main() {
  static uint8_t data_out[2] = {0xAB, 0xCD};
  spi_write(data_out);
  while (true);
}

字符串
请注意,静态生存期是必需的。仅仅在main的末尾循环并不一定会修复它:

main() {
  uint8_t data_out[2] = {0xAB, 0xCD};
  spi_write(data_out);
  while (true);
}


这是因为在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标准中是找不到的。

相关问题