STM32H7 SPI DMA 低电平 - 仅发送一个帧

STM32H7 SPI DMA Low Level - sends only one Frame

提问人:Chris_B 提问时间:10/26/2023 更新时间:10/27/2023 访问量:89

问:

我在使用带有 DMA 和低级驱动程序的 STM32H723 SPI 时遇到了问题。

问题是:它只发送一个帧,在第一帧之后,每次传输都会因传输错误中断而中止。

SPI 应每 100 毫秒触发一次,并应发送 10 字节的帧。我使用 HAL 运行它,所以硬件没问题(我能够接收/发送数据),但我无法让它使用低级驱动程序运行。我的猜测是:我忘记了 SPI / DMA 中断处理程序中的某些内容。当程序(重新)启动时,它正在工作,但在第一帧之后,DMA 保持阻塞/锁定状态,并在下一个请求时触发“传输错误”。

硬件设置

SPI:全双工主机,8位,摩托罗拉,MSB优先,NSS输出硬件,FIFO阈值1数据SPI Setup

DMA:Rx 和 Tx 各一个通道,正常模式,数据宽度 = 1 字节,内存增量。DMA Setup

基本初始化和代码生成由 Cube MX 完成

法典

基本初始化,调用一次以配置 DMA

void myDMA_init(void) {
   // DMA Basic Configuriation 
   LL_DMA_ConfigAddresses( DMA1, LL_DMA_STREAM_2, 
      LL_SPI_DMA_GetRxRegAddr(SPI2), 
      (uint32_t)RxBuffer,
      LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_STREAM_2));

   LL_DMA_ConfigAddresses(DMA1, LL_DMA_STREAM_3,
      (uint32_t)TxBuffer,
      LL_SPI_DMA_GetTxRegAddr(SPI2),
      LL_DMA_GetDataTransferDirection(DMA1, LL_DMA_STREAM_3));
}

发送函数:在主循环中周期性调用

void SendData(void) {
   // configure RX DMA
   uint32_t rxAddr = (uint32_t)RxBuffer;
   LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_2, rxAddr);
   LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_2, DataSize);
   LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_2);
   LL_SPI_EnableDMAReq_RX(SPI2);
   // configure TX DMA
   uint32_t txAddr = (uint32_t)TxBuffer;
   LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_3, txAddr);
   LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_3, DataSize);
   LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_3);
   LL_SPI_SetTransferSize(SPI2, DataSize);
   LL_SPI_EnableDMAReq_TX(SPI2); 
   // enable Transfer Complete and Transfer Error IT for RxDMA Stream
   LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_2);
   LL_DMA_EnableIT_TE(DMA1, LL_DMA_STREAM_2);
   // enable and start SPI Master Transfer
   LL_SPI_Enable(SPI2);
   LL_SPI_StartMasterTransfer(SPI2);
}

注意:DataSize = 10,uint8_t TxBuffer[10] 和 uint8_t RxBuffer[10] 是全局变量。TxBuffer 在发送前被填充,RxBuffer 在发送前被清除。SendData 函数工作一次。我可以看到示波器上的传输。

此外,我有 3 个中断处理程序:

DMA1 流 2 (Rx DMA) 的处理程序

void DMA1S2_IRQHandler(void) {
   // clear Transfer Complete and Transfer Error IRQ-Flag
   if(LL_DMA_IsActiveFlag_TC2(DMA1)) LL_DMA_ClearFlag_TC2(DMA1);
   if(LL_DMA_IsActiveFlag_TE2(DMA1)) LL_DMA_ClearFlag_TE2(DMA1);
   LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
   LL_DMA_DisableIT_TE(DMA1, LL_DMA_STREAM_2);
   // enable SPI End of Transfer Interrupt
   LL_SPI_EnableIT_EOT(SPI2);
}

DMA1 流 3 (Tx DMA) 的处理程序

void DMA1S3_IRQHandler(void) {
   // clear Transfer Complete and Transfer Error IRQ-Flag
   if(LL_DMA_IsActiveFlag_TC3(DMA1)) LL_DMA_ClearFlag_TC3(DMA1);
   if(LL_DMA_IsActiveFlag_TE3(DMA1)) LL_DMA_ClearFlag_TE3(DMA1);
   LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
   LL_DMA_DisableIT_TE(DMA1, LL_DMA_STREAM_2);
}

以及 SPI 中断的处理程序:

void SPI2_myIRQHandler(void) {
   // clear the End of Transfer Interrupt Flag
   LL_SPI_ClearFlag_EOT(SPI2);
   LL_SPI_DisableIT_EOT(SPI2);
   // Disable SPI2
   LL_SPI_Disable(SPI2);
   // Disable DMA1 Stream 2 and DMA Request
   LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_3);
   LL_SPI_DisableDMAReq_RX(SPI2);
   // Disable DMA1 Stream 3 and DMA Request
   LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_3);
   LL_SPI_DisableDMAReq_TX(SPI2);
}

行为:

在第一次运行时,DMA Rx 中断调用中断处理程序,设置了传输完成标志,但没有传输错误。在处理程序结束时,SPI 传输中断结束被启用,该调用几乎没有任何延迟。在第二次和后续所有运行中,调用 Rx DMA 中断处理程序时启用 TE 和 TC 标志。SPI EoT 中断永远不会再次触发。在示波器中,我可以看到已经发出了一帧,但仅此而已。

问题:有没有人知道,为什么DMA在第一次运行后立即中止并出现传输错误?谢谢!

附注:

  • 这只是一个测试程序,用于使SPI正常工作。它不是 最终的生产代码。这里的目的是让它尽可能简单 可能弄清楚这些东西是如何工作的
  • 这是来自 STM 论坛的(改进/审查的)交叉帖子,我最初在那里发布它,但由于某种我不明白的原因,我的问题被标记为垃圾邮件
  • 主循环只是一个HAL_Delay、一个闪烁的 LED、TxBuffer 的更新和对 SPI Send 函数的调用。
STM32 SPI DMA 低电平 STM32H7

评论

0赞 wek 10/26/2023
您确定使用像 DMA1S2_IRQHandler() 这样的 ISR 名称吗,这些 ISR 是否实际调用?
0赞 Chris_B 10/26/2023
是的。我敢肯定。我在 stm32h7xxx_it.c 中调用它们,我可以将断点放入我的处理程序中,我看到它们被调用。

答:

0赞 Chris_B 10/27/2023 #1

我找到了一个解决方案。

我的错误是,我试图关闭 - SPI 中断处理程序中的 Rx 和 Tx DMA。

我也必须在“发送”功能中为两个 DMA 流(流 2 Rx 和流 3 Tx)启用传输完成和传输错误中断:

void SendData(void) {
   // configure RX DMA
   uint32_t rxAddr = (uint32_t)RxBuffer;
   LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_2, rxAddr);
   LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_2, DataSize);
   LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_2);
   LL_SPI_EnableDMAReq_RX(SPI2);
   // configure TX DMA
   uint32_t txAddr = (uint32_t)TxBuffer;
   LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_3, txAddr);
   LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_3, DataSize);
   LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_3);
   LL_SPI_SetTransferSize(SPI2, DataSize);
   LL_SPI_EnableDMAReq_TX(SPI2); 
   // enable Transfer Complete and Transfer Error IT for RxDMA Stream
   LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_2);
   LL_DMA_EnableIT_TE(DMA1, LL_DMA_STREAM_2);
   // enable Transfer Complete and Transfer Error IT for TxDMA Stream
   LL_DMA_EnableIT_TC(DMA1, LL_DMA_STREAM_3);
   LL_DMA_EnableIT_TE(DMA1, LL_DMA_STREAM_3);
   // enable and start SPI Master Transfer
   LL_SPI_Enable(SPI2);
   LL_SPI_StartMasterTransfer(SPI2);
}

两个中断处理程序函数(在自动生成的 stm32h7xx_it.c 中调用)都需要禁用 DMA-Stream 并删除 SPI 配置中的 DMA-Request 标志。Rx DMA传输完成中断处理程序还支持SPI传输结束中断:

void DMA1S2_IRQHandler(void) {
   // handle Rx DMA Transfer Complete / Error Interrupt
   // clear Transfer Complete and Transfer Error IRQ-Flag
   LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_2);     
   LL_SPI_DisableDMAReq_RX(SPI2);   
   // clear interrupt flags
   if(LL_DMA_IsActiveFlag_TC2(DMA1)) LL_DMA_ClearFlag_TC2(DMA1);
   if(LL_DMA_IsActiveFlag_TE2(DMA1)) LL_DMA_ClearFlag_TE2(DMA1);
   LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
   // enable SPI End of Transfer Interrupt
   LL_SPI_EnableIT_EOT(SPI2);
}

Tx DMA 传输完成始终在 Rx DMA 传输完成中断之前:

void DMA1S3_IRQHandler(void) {
   // handle Tx DMA Transfer Complete / Error Interrupt
   // clear Transfer Complete and Transfer Error IRQ-Flag
   LL_DMA_DisableStream(DMA1, LL_DMA_STREAM_3);     
   LL_SPI_DisableDMAReq_RX(SPI2);   
   if(LL_DMA_IsActiveFlag_TC3(DMA1)) LL_DMA_ClearFlag_TC3(DMA1);
   if(LL_DMA_IsActiveFlag_TE3(DMA1)) LL_DMA_ClearFlag_TE3(DMA1);
   LL_DMA_DisableIT_TC(DMA1, LL_DMA_STREAM_2);
   LL_DMA_DisableIT_TE(DMA1, LL_DMA_STREAM_2);
}

SPI EoT 中断处理程序只需关闭 SPI:

void SPI2_myIRQHandler(void) {
   // clear the End of Transfer Interrupt Flag
   LL_SPI_ClearFlag_EOT(SPI2);
   LL_SPI_DisableIT_EOT(SPI2);
   // Disable SPI2
   LL_SPI_Disable(SPI2);
   // add "transmission complete" handler here
}

现在,这引出了以下问题: 显然,Tx DMA 是麻烦制造者。在发送“传输完成”中断后,需要立即关闭它。

我的问题是:为什么会这样? 为什么 DMA 在传输了

LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_2, DataSize);

如果此限制对外围设备的行为没有影响,那么设置数据长度似乎没有多大意义。我希望 DMA 在“剩余字节数”计数器达到零时停止任何传输。

RM0468 修订版 3,第 619 页,第 15.3.8 章 {源、目标和传输模式} 指出:

存储器到外设模式 图 81 描述了此模式。当这个 mode 启用(通过在 DMA_SxCR 寄存器中设置 EN 位),则 流立即启动从源到完全的传输 填充 FIFO。每次发生外围请求时,内容 FIFO被排空并存储到目的地。当水平 的FIFO低于或等于预定义的阈值水平, FIFO完全重新加载了存储器中的数据。一旦DMA_SxNDTR寄存器达到零外设 请求传输结束(在外围流的情况下 controller) 或当 DMA_SxCR 寄存器中的 EN 位被清除时 软件。

有没有人回答这个问题,或者我的代码中仍然存在一个错误,导致 DMA 无法自动停止传输?

评论

0赞 wek 10/27/2023
'H7 SPI过于复杂,所以也许还有其他东西;但这里的罪魁祸首是 IMO,您在开始下一次传输之前没有清除 Tx 流的 DMA 状态寄存器中的状态标志。请参阅 RM 中 DMA 章节的流配置过程子章节中清单第一项的最后一句话。
0赞 Chris_B 10/27/2023
感谢您@wek提示,我会立即检查!
0赞 Chris_B 10/28/2023
@wek:我遵循了您的提示,现在在开始新的 TxRx 之前清除了 Interupt 状态寄存器。但这并不能改变一个事实,即我仍然需要TX DMA流上的TC中断来立即将其关闭,否则会卡住一些东西。我使用 HAL 跟踪了具有相同功能的寄存器状态,HAL 也做了同样的事情。每个 DMA 通道都有一个 TC 中断。我不会抱怨太多,因为我有一个有效的解决方案,另一方面,我真的很想了解为什么我需要两个 IRQ,其中手册说 TX DMA 会自动停止......
0赞 wek 10/28/2023
也许是 Cube/HAL 的线束需要 TX DMA ISR,我不知道,我不使用 Cube。