可变大小数组的UART DMA

5
使用MPLAB X 1.70和dsPIC33FJ128GP802微控制器。 我有一个应用程序,它以不同的采样率(一个为50Hz,另一个为1000Hz)从两个传感器收集数据,两个传感器数据包的大小也不同(一个为5字节,另一个为21字节)。 到目前为止,我已经使用手动UART传输,如下所示:
void UART_send(char *txbuf, char size) {
    // Loop variable.
    char i;

    // Loop through the size of the buffer until all data is sent. The while
    // loop inside checks for the buffer to be clear.
    for (i = 0; i < size; i++) {
        while (U1STAbits.UTXBF);
        U1TXREG = *txbuf++;
    }
}

这个函数接收不同长度(5或21字节)的数组,通过for循环遍历每个字节并通过UART tx寄存器U1TXREG输出。现在,我想使用DMA来减轻传输大量数据时对系统的压力。我已经在UART接收和ADC方面使用了DMA,但在发送方面遇到了问题。我尝试了ping pong模式开启和关闭、单次和连续模式,但无论何时发送21字节的包,都会出现奇怪的值和零填充。下面是DMA的初始化。

void UART_TX_DMA_init() {

        DMA2CONbits.SIZE  = 0;                                  // 0: word; 1: byte
        DMA2CONbits.DIR   = 1;                                  // 0: uart to device; 1: device to uart
        DMA2CONbits.AMODE = 0b00;
        DMA2CONbits.MODE  = 1;                                  // 0: contin, no ping pong; 1: oneshot, no ping pong; 2: contin, ping pong; 3: oneshot, ping pong.

        DMA2PAD = (volatile unsigned int) &U1TXREG;

        DMA2REQ = 12;                   // Select UART1 Transmitter

        IFS1bits.DMA2IF  = 0;           // Clear DMA Interrupt Flag
        IEC1bits.DMA2IE  = 1;           // Enable DMA interrupt
}

我只是清除了DMA中断标志。为了构建DMA数组,我有以下函数:

char TXBufferADC[5] __attribute__((space(dma)));
char TXBufferIMU[21] __attribute__((space(dma)));

void UART_send(char *txbuf, char size) {

    // Loop variable.
    int i;

    DMA2CNT = size - 1; // x DMA requests

    if (size == ADCPACKETSIZE) {
        DMA2STA = __builtin_dmaoffset(TXBufferADC);
        for (i = 0; i < size; i++) {
            TXBufferADC[i] = *txbuf++;
        }
    } else if (size == IMUPACKETSIZE) {
        DMA2STA = __builtin_dmaoffset(TXBufferIMU);
        for (i = 0; i < size; i++) {
            TXBufferIMU[i] = *txbuf++;
        }
    } else {
        NOTIFICATIONLED ^= 1;
    }

    DMA2CONbits.CHEN = 1; // Re-enable DMA2 Channel
    DMA2REQbits.FORCE = 1; // Manual mode: Kick-start the first transfer
}

这个示例是关闭乒乓模式的。我使用相同的DMA2STA寄存器,但根据数据包类型更改数组。我从要发送的数据中确定数据包类型,更改DMA要发送的字节数(DMA2CNT),使用for循环构建与之前相同的数组,然后强制执行第一次传输并重新启用通道。

处理大数据包的数据需要更长时间,我开始认为DMA会错过这些数据包,并在其位置发送null / 奇怪的数据包。在构建缓冲区并强制执行第一次传输之前,它似乎正在轮询。也许不需要每次轮询都进行强制操作;我不知道......

任何帮助都将是极好的。


UART_send() 中,您不需要禁用中断以防止其中断触发吗? - chux - Reinstate Monica
好的建议,我明天会尝试一下,谢谢。不过这样做是否会否定DMA的意义呢?如果我故意暂停DMA中断,那么它是否和手动UART方法一样快? - ritchie888
也许我应该说:“在UART_send()期间,您不需要禁用中断以防止中断_在那里被处理_。” 在例程中_引起_中断是可以的,但很可能例程需要是原子的并被锁定以防止在其中断开。 我只建议在UART_send()期间暂停_处理_中断。 - chux - Reinstate Monica
你可以使用一个布尔标志来指示先前的DMA传输是否已完成。当DMA控制器完成发送完整块大小为“size”的元素时,将调用_DMA2Interrupt(因为您正在使用通道号2),然后可以清除标志。在开始新的传输之前,您可以等待标志被清除。这对于所有支持DMA的外设都适用,但请注意,它仅表示传输到UART已完成,UART FIFO可能仍然是满的,因此需要进行测试。 - A Person
1个回答

2

经过几天的努力,我想我已经做到了。

我遇到的主要问题是DMA中断的轮询速度比以前的传输快,因此在下一个包覆盖上一个包之前,我只能获得一些包的片段。这可以通过等待UART传输结束来解决:

while (!U1STAbits.TRMT);

我成功地避免了重新创建新的DMA与包数据的冗余,只需使原始数据数组成为DMA所识别的数组即可。

最终,整个过程非常简单,每次创建包时调用的函数是:

void sendData() {
    // Check that last transmission has completed.
    while (!U1STAbits.TRMT);

    DMA2CNT = bufferSize - 1;

    DMA2STA = __builtin_dmaoffset(data);

    DMA2CONbits.CHEN = 1; // Re-enable DMA0 Channel
    DMA2REQbits.FORCE = 1; // Manual mode: Kick-start the first transfer
}

无论包的大小如何,DMA都会使用DMA2CNT寄存器更改发送的数量,然后只需重新启用DMA并强制第一个位即可。

DMA的设置是:

DMA2CONbits.SIZE = 1;
DMA2CONbits.DIR = 1; 
DMA2CONbits.AMODE = 0b00;
DMA2CONbits.MODE = 1;

DMA2PAD = (volatile unsigned int) &U1TXREG;

DMA2REQ = 12;                               // Select UART1 Transmitter

IFS1bits.DMA2IF = 0;                        // Clear DMA Interrupt Flag
IEC1bits.DMA2IE = 1;                        // Enable DMA interrupt

这是一次性的、无乒乓操作的字节传输,所有UART1 TX的正确参数都包含在内。

希望这能帮助未来的某些人,这个通用原则可以应用于大多数PIC微控制器。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接