This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] MSP430F5659:SPI over DMA 有时会错过一个 RX 中断

Guru**** 2534260 points


请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/microcontrollers/msp-low-power-microcontrollers-group/msp430/f/msp-low-power-microcontroller-forum/846665/msp430f5659-spi-over-dma-sometimes-misses-one-rx-interrupt

器件型号:MSP430F5659

您好!

我使用 DMA 以19.2MHz 的频率(与微控制器时钟频率相同)写入和读取512字节的块。

SPI TX DMA 通道为通道4、RX 为通道3。 我还有 DMA 通道5、用于大型 memcpys。 这是一个代码示例

#define SPI_READ_DMA_CHANNEL (dma_channel_3)
#define SPI_READ_DMA_CTL (DMA3CTL)
#define SPI_WRITE_DMA_CHANNEL (DMA_CHANGE_4)
#define SPI_WRITE_DMA_CTL (DMA4CTL)

void SPI_init (void){
GPIO_setOutputHighOnPin (SPI_CS_PORT、SPI_CS_PIN);
GPIO_setPeripheralModuleFunctionInputPin (
SPI_PORT、(SPI_MOSI_PIN | SPI_SOMI_PIN | SPI_SCLK_PIN)
);
GPIO_setAsOutputPin (SPI_CS_PORT、SPI_CS_PIN);

USCI_A_SPI_initMaster (
USCI_A1_BASE、
&((USCI_A_SPI_initMasterParam)
{.selectClockSource = USCI_A_SPI_CLOCKSOURCE_SMCLK、
时钟源频率= 19200000、
.desedSpiClock = 19200000、
.msbFirst = USCI_A_SPI_MSB_FIRST、
时钟相位= USCI_A_SPI_PHASE_DATA_Changed_ONFIRST_captured_on_next、
时钟极性= USCI_A_SPI_CLOCKPOLARITY_INACT_HIGH
});

DMA_disableTransfers (SPI_WRITE_DMA_CHANNEL);
DMA_disableTransfers (SPI_READ_DMA_CHANNEL);
dma_init (
&((dma_initParam)
{.channelSelect = SPI_WRITE_DMA_CHANNEL、
.transfermodeSelect = dma_transfer_single、
.transferSize = 0、
triggerSourceSelect = DMA_TRIGGERSOURCE_21、// UCA1TXIFG
.transferUnitSelect = DMA_SIZE SRCBYTE_DSTBYTE、
triggerTypeSelect = DMA_TRIGGER_HIGH
});

dma_init (
&((dma_initParam)
{.channelSelect = SPI_READ_DMA_CHANNEL、
.transfermodeSelect = dma_transfer_single、
.transferSize = 0、
triggerSourceSelect = DMA_TRIGGERSOURCE_20、// UCA1RXIFG
.transferUnitSelect = DMA_SIZE SRCBYTE_DSTBYTE、
triggerTypeSelect = DMA_TRIGGER_HIGH
});

USCI_A_SPI_ENABLE (USCI_A1_base);
}

int16_t SPI_read (uint8_t * buf、uint16_t len){
静态 uint8_t Pending_dma_read = 0;

//如果 DMA 尚未完成操作,则返回 NOT READY。
if (SPI_READ_DMA_CTL 和 DMAEN){
返回0;
}

//如果有一个挂起的读取操作,这意味着
// DMA 已完成复制。 我们可以返回成功。
if (pending_dma_read){
Pending_DMA_read = 0;
SPI_READ_DMA_CTL &&~DMAIFG;
返回1;
}

// DMA 非忙且无挂起读取,启动读取操作。
Pending_DMA_read = 1;

DMA_setTransferSize (SPI_WRITE_DMA_CHANNEL、len);
DMA_setTransferSize (SPI_READ_DMA_CHANNEL、len);

dma_disableTransferDuringReadModifyWrite ();//DMA4权变措施-在读取-修改-写入 CPU 操作期间禁用 DMA 传输
DMA_setSrcAddress (SPI_WRITE_DMA_CHANNEL、(无符号长整型)&SPI_READ_TOKEN、DMA_DIRECTION 不变);
DMA_setDstAddress (SPI_WRITE_DMA_CHANNEL、USCI_A_SPI_getTransmitBufferAddressForDMA (USCI_A1_BASE)、DMA_Direction 不变);
DMA_setSrcAddress (SPI_READ_DMA_CHANNEL、USCI_A_SPI_getReceiveBufferAddressForDMA (USCI_A1_base)、DMA_Directive_unchanged);
DMA_setDstAddress (SPI_READ_DMA_CHANNEL、(无符号长整型) buf、DMA_DIRECTION _IN递增);
USCI_A_SPI_receiveData (USCI_A1_base); //虚拟读取
USCI_A_SPI_clearInterrupt (USCI_A1_base、USCI_A_SPI_Receive_interrupt);
DMA_enableTransferDuringReadModifyWrite ();//DMA4权变措施-在读取-修改-写入 CPU 操作期间重新启用 DMA 传输

//开始传输
DMA_enableTransfers (SPI_READ_DMA_CHANNEL);
DMA_enableTransfers (SPI_WRITE_DMA_CHANNEL);
返回0;
}

#define memcpy_dma_channel (dma_channel_5)
#define memcpy_dma_CTL (DMA5CTL)

void dma_memcpy_init (void){
dma_disableTransfers (memcpy_dma_channel);
dma_init (
&((dma_initParam)
{.channelSelect = memcpy_dma_channel、
.transfermodeSelect = dma_transfer_BURSTBLOCK、
.transferSize = 0、
triggerSourceSelect = DMA_TRIGGERSOURCE_0、// DMAREQ
.transferUnitSelect = DMA_SIZE SRCBYTE_DSTBYTE、
triggerTypeSelect = DMA_TRIGGER_HIGH
});
}

void dma_memcpy (uint8_t * dst、const uint8_t * src、uint16_t len){
如果(len > 0){
dma_setTransferSize (memcpy_dma_channel、len);

dma_disableTransferDuringReadModifyWrite ();//DMA4权变措施-在读取-修改-写入 CPU 操作期间禁用 DMA 传输
dma_setSrcAddress (memcpy_dma_channel、(uint32_t) src、dma_direction、增量);
dma_setDstAddress (memcpy_dma_channel、(uint32_t) dst、dma_Directoration_Increment);
DMA_enableTransferDuringReadModifyWrite ();//DMA4权变措施-在读取-修改-写入 CPU 操作期间重新启用 DMA 传输

dma_enableTransfers (memcpy_dma_channel);
dma_enableInterrupt (memcpy_dma_channel);//以便在传输完成后能够睡眠和唤醒
dma_startTransfer (memcpy_dma_channel);
while (memcpy_dma_CTL 和 DMAEN){
BIS_SR (LPM0_Bits+GIE);//睡眠
};
}
} 

我发现 dma_memcpy 有时发生在512字节 SPI 事务期间。 发生这种情况时、RX DMA 传输永远不会完成。 如果我之后设置一个断点、我会看到 DMA3SZ 为1、DMA4SZ 为512 (这意味着它已完成)、UCRXIFG 为0。

因此、这必须意味着由于 memcpy、事务中的某个位置错过了 RX 字节。 但是、我无法分析它。 通常、在 TX 数据被发送后会有一个 RX 中断标志。 由于 RX DMA 的优先级高于 TX DMA、即使 TX DMA 已准备好发送另一个字节、它也应该等待 RX DMA 被清除、对吧?

谢谢、

Fred

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    USCI 中是否设置了 Rx 溢出标志?  

    如果你想象一下动态:当你启用 Tx DMA 时、它将立即向 SPI 写入两个 Tx 字节、在下一个16 SMCLK==MCLK 内、将有两个 RXIFG。 如果 Rx 侧 DMA 被保持8个时钟 e、g。通过(块传输) memcpy、Rx 侧将溢出、第二个字节将被丢弃。

    当我在 F2系列上执行此操作时、我得出结论、我需要在 DMA 运行时让 CPU 静止不动。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Bruce、

    感谢您的回答。 我刚才测试了更多、似乎 dma_memcpy 不是问题。 我在进入函数时测试了 DMA3CTL 和 DMAEN。 但是、如果 DMA 已经被挂起、那么情况确实如此。 我现在测试(DMA3CTL 和 DMAEN)&&(DMA3SZ > 1)、这种情况永远不会发生。 然后、我在开始时添加了以下代码、以等待 SPI DMA、然后再执行 memcpy DMA。 它不起作用、因为只有 DBG_LED1打开。

    void dma_memcpy (uint8_t * dst、const uint8_t * src、uint16_t len){
    如果(len > 0){
    uint16_t 超时= 10000;//~1.5ms 超时
    uint8_t testForDma = 0;
    IF (DMA3CTL 和 DMAEN){
    testForDma = 1;
    }
    while (((DMA3CTL 和 DMAEN)&&-超时);
    如果(超时= 0){
    DebugLed_on (DBG_LED1);
    } 否则、如果(testForDma = 1){//这意味着等待工作
    DebugLed_on (DBG_LED2);
    }
    dma_setTransferSize (memcpy_dma_channel、len);
    
    /*其余功能*/
    }
    } 

    我还针对溢出位(UCA1STAT 和 UCOE)进行了测试、但这个位从不成立。 这实际上让我感到困惑。 这意味着共捕获512个 TX 事件、但仅捕获511个 RX 事件、不会丢失任何数据。 如何实现? 我不认为 DMA 传输到 SPI TXBUF 的速度太快、因为它是由 TXIFG 触发的、TXIFG 只有在数据已从寄存器中移出时才会被清除。

    我不知道你说了什么。 您说立即发送了2个字节、但不会一次只发送1个字节?

    • TXIFG -> UAC1TXBUF =字节;
    • 对于(I = 0;I < 8;I++)
    •    已移出 txbit
    •    已移入 rxbit
    • TXIFG
    • RXIFG

    因此、每个 TXIFG 事件应始终有一个 RXIFG 事件。 我认为在 RXIFG 发生之前、DMA 可能会向 UCA1TXBUF 传输一个新的字节、但由于没有溢出发生、这无关紧要、对吧?

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    SPI 启动非常简单。 DMA 会将第一个字节传输到 TXBUF、并且由于输出移位寄存器为空、因此数据会立即传输。 将 TXBUF 留空、这将触发另一个 DMA 传输。 之后、DMA 传输按串行数据速率进行。

    由于 SPI DMA 以如此高的速率发生、因此您必须非常小心该块传输。 如果它在 SPI 传输过程中启动、则会导致问题。 SPI 的 DMA cotrollers 会创建周期性 DMA 请求、从而使总线被授予它们。 它们完成了单次传输并完成了。 但是 、在突发或 bburst 块模式中的 DMAC 将在给任何其他 DMA 通道任何变化以在一个周期内滑移之前完成其传输。  (请参阅用户指南中的9.2.6。)

    那么、如果 memcopy 在 SPI DMA 传输过程中启动、会发生什么情况?  当提供到 DMAC 的访问时、SPI 发送器中有两个正在处理的字节。 一个位于输出移位寄存器中、另一个位于 TXBUF 中。 这些数据将继续传输、但在块传输完成之前没有更多数据到达。 同时、SPI 接收器照常获取第一个字节、但其 DMA 请求未得到应答。 然后、当下一个中断发生时、它会设置溢出标志。 (当 DMA 再次开始读取数据时、此位将被复位。) 然后、当块传输完成时、DMA 最终能够再次为 SPI 端口提供服务。 最终结果是、SPI 端口上传输的数据的时序存在很大差异、而接收端缺少一个字节。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    大家好、David、感谢您的回答。

    您可以在我之前对 Bruce 的回答中看到、我已经确定 dma_memcpys 不是问题的根源、并且溢出标志未设置、这使我对正在发生的情况感到困惑。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    每次 DMA 读取 RXBUF 时、溢出标志都会被复位、因此如果您只在传输结束时检查它、就不会看到它被置位。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    哦、这很有趣、我将更深入地研究一下。

    谢谢!

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 David:

    因此、我通过在 SPI_READ 函数的末尾添加以下代码将 SPI_READ 函数更改为阻塞:

    int16_t SPI_read (uint8_t * buf、uint16_t len){
    
    /*函数开始*/
    
    //开始传输
    DMA_enableTransfers (SPI_READ_DMA_CHANNEL);
    DMA_enableTransfers (SPI_WRITE_DMA_CHANNEL);
    
    while (DMA3CTL 和 DMAEN){
    if ((UCA1STAT & UCOE)&&(len > 1)){
    DebugLed_on (DBG_LED1);
    }
    }
    返回0;
    
    } 

    溢出位在事务的中间、有时会被提升。 我想现在有两种选择:

    1. 更改由 DMA3IFG (SPI Rx DMA)而不是 TXIFG 触发的 TX_DMA 行为、以确保在 RX 未提供时不会发生 TX (我担心这会降低吞吐量)
    2. 通过检测并重试它来处理它可能发生的事实。

    请注意、当事务的长度为1时、我发现溢出位始终置位(因此(len > 1)检查)。 这是正常的吗?

    非常感谢、

    Fred

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我想我会以不同的方式更改 DMA 存储器复制代码。 您的大多数代码都丢失了、但有一些提示表明这是多线程代码。 如果在检查 DMAEN 和启动块复制之间有发生中断的机会、那么这是一个问题。

    我会让 SPI 代码设置一个标记、以供 DMA 代码检查。 如果需要、它会使用全局中断禁用。 如果它发现 DMA 被占用、那么它使用循环来执行块复制旧方式。 这样、调用代码不必等待 SPI 传输完成、块复制才会发生。

    在启动之前、SPI 代码还应该有一个方法来检查并查看一个块复制是否正在进行中。 (信标可以很好地完成任务。)

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    你(们)好、David

    感谢您对此主题的支持!

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 David:

    代码由中断驱动、而不是多线程。 正如我在前一个答案中所说的、问题似乎不是来自 dma_mempcys。 但是、我有另一个活动的 DMA 通道(DMA_CHANNEL)、该通道每4ms 复制2个字节(由计时器捕获事件触发)、另一个(DMA_CHANNEL 2)每16ms 复制8个字节、并在4个 DMA_CHANGE_1事务后触发。

    很抱歉、我之前没有提到过它、我认为它在512字节事务中并不重要。 但是、我现在意识到10字节传输(2 + 8)很可能是缺少 SPI_RX_IFG 的根本原因、因为它们的优先级高于 SPI DMA、并且事务一起持续~10个周期(10字节)、 这长于 SPI_RX_IFG 所需的8个周期。

    由于我无法更改通道1和2的行为、我认为我的最佳解决方案是检测 SPI_RX 事务何时中断并重试。

    非常感谢您的帮助、

    Fred

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    我想您已对所需的周期进行了误计数。 由于 MSP430外设是存储器映射的、因此每次传输都需要2个周期。 因此、移动8个字节需要16个 MCLK。 如果使用突发块模式、则时间会更长、因为这允许 CPU 每四次传输运行两个 MCLK。 足够长的时间来导致 SPI 接收器溢出。 根据该传输的开始时间、 可能需要8个 MCLK 的延迟来执行该操作。

    优先级无关紧要。 SPI DMA 请求是周期性的、因此其他 DMA 通道可以并且将被授予 SPI 请求之间的总线输入。 一旦一个 DMA 通道被授予总线、在它完成传输之前、其它 DMA 通道都无法获得它。 即使它是很长的突发。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    你是对的、我记错了。 这会使它变得更糟。

    我很高兴我们找到了这个问题、谢谢您、

    Fred