提问人:Chris_B 提问时间:10/26/2023 更新时间:10/27/2023 访问量:89
STM32H7 SPI DMA 低电平 - 仅发送一个帧
STM32H7 SPI DMA Low Level - sends only one Frame
问:
我在使用带有 DMA 和低级驱动程序的 STM32H723 SPI 时遇到了问题。
问题是:它只发送一个帧,在第一帧之后,每次传输都会因传输错误中断而中止。
SPI 应每 100 毫秒触发一次,并应发送 10 字节的帧。我使用 HAL 运行它,所以硬件没问题(我能够接收/发送数据),但我无法让它使用低级驱动程序运行。我的猜测是:我忘记了 SPI / DMA 中断处理程序中的某些内容。当程序(重新)启动时,它正在工作,但在第一帧之后,DMA 保持阻塞/锁定状态,并在下一个请求时触发“传输错误”。
硬件设置
SPI:全双工主机,8位,摩托罗拉,MSB优先,NSS输出硬件,FIFO阈值1数据
DMA:Rx 和 Tx 各一个通道,正常模式,数据宽度 = 1 字节,内存增量。
基本初始化和代码生成由 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 函数的调用。
答:
我找到了一个解决方案。
我的错误是,我试图关闭 - 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 无法自动停止传输?
评论