Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UART DMA for varying sized arrays

Using MPLAB X 1.70 with a dsPIC33FJ128GP802 microcontroller.

I've got an application which is collecting data from two sensors at different sampling rates (one at 50Hz, the other at 1000Hz), both sensor packets are also different sizes (one is 5 bytes, the other is 21 bytes). Up until now I've used manual UART transmision as seen below:

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++;
    }
}

The varying sized arrays (5 or 21 bytes) were sent to this function, with their size, and a simple for loop looped through each byte and outputted it through the UART tx register U1TXREG.

Now, I want to implement DMA to relieve some pressure on the system when transmitting the large amount of data. I've used DMA for my UART receive and ADC, but having trouble with transmit. I've tried both ping pong mode on and off, and one-shot and continuous mode, but whenever it comes to sending the 21 byte packet it messes up with strange values and zero value padding.

I'm initialising the DMA as seen below.

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
}

The DMA interrupt I'm just clearing the flag. To build the DMA arrays I've got the following function:

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
}

This example is with ping pong turned off. I'm using the same DMA2STA register but changing the array depending on which packet type I have. I'm determining the packet type from the data to be sent, changing the DMA bytes to be sent (DMA2CNT), building the array same as before with a for loop, then forcing the first transfer along with re-enabling the channel.

It takes much longer to process the data for the large data packet and I'm starting to think the DMA is missing these packets and sending null/weird packets in its place. It seems to be polling before I build the buffer and force the first transfer. Perhaps the force isn't necessary for every poll; I don't know...

Any help would be great.

like image 282
ritchie888 Avatar asked Nov 01 '22 08:11

ritchie888


1 Answers

After a few days of working on this I think I've got it.

The main issue I experienced was that the DMA interrupt was being polled faster than previous transmission, therefore I was only getting segments of packages before the next package overwrote the previous. This was solved with simply waiting for the end of UART transmission with:

while (!U1STAbits.TRMT);

I managed to avoid the redundancy of recreating a new DMA with the package data by simply making the original data array the one recognised by the DMA.

In the end the process was pretty minimal, the function called every time a package was created is:

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
}

Regardless of what the size of the package, the DMA changes the amount it sends using the DMA2CNT register, then it's simply re-enabling the DMA and forcing the first bit.

Setting up the DMA was:

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

Which is one-shot, no ping-pong, byte transfer, and all the correct parameters for UART1 TX.

Hope this helps someone in the future, the general principle can be applied to most PIC microcontrollers.

like image 97
ritchie888 Avatar answered Nov 15 '22 06:11

ritchie888