UART IRQ question

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

UART IRQ question

Jump to solution
2,126 Views
errorek123
Contributor III

Hello,

I'm working with FRDM-K22F and i'm developing my own lib to control it but i'm struggling with IRQ which can be called from rx and tx.

The problem is that i want to handle non-blocking read and also non blocking write(both uses interrupt)

void UART2_RX_TX_IRQHandler(void)
{
         if(UART2->S1 & UART_S1_RDRF_MASK)
         {
                  circular_buff_put(internal_RX_UART_handle, UART2->D);
         }

         if(UART2->S1 & UART_S1_TDRE_MASK)
         {
                  UART_transferNonBlocking(UART2,internal_TX_UART_handle);
         }      
}

Problems starts when IRQhandler is invoked by RX, both flags RDRF and TDRE will be active but i dont want to send data unless interrupt was caused by TX. I could use else if statement and that "solves" the problem temporary.

Now let's assume there was interrupt caused by RX and TX almost at exact same time, would be UART2_RX_TX_IRQHandler called twice or there is small chance i would have to handle 2 interrupts in 1 handler?

0 Kudos
1 Solution
1,843 Views
mjbcswitzerland
Specialist V

Brian

Yes, I made an error when simplifying and coping the original code. This is how it actually is:

    usDMA_rx = ptrDMA_TCD->DMA_TCD_CITER_ELINK;                          // snap-shot of DMA reception progress
    if (ulDMA_progress[channel] >= usDMA_rx) {
        tty_queue->chars += (QUEUE_TRANSFER)(ulDMA_progress[channel] - usDMA_rx); // the extra number of characters received by DMA since last check
    }
    else {
        tty_queue->chars += (QUEUE_TRANSFER)ulDMA_progress[channel];
        tty_queue->chars += (ptrDMA_TCD->DMA_TCD_BITER_ELINK - usDMA_rx); // the extra number of characters received by DMA since last check
    }
    ulDMA_progress[channel] = usDMA_rx;                                  // remember the check state
‍‍‍‍‍‍‍‍‍

If you later need to do the same on devices without eDMA (like most of the m0+ based parts) the equivalent is:

    ulDMA_rx = ptrDMA->DMA_DAR;                                          // snap-shot of DMA reception progress
    if (ulDMA_progress[channel] <= ulDMA_rx) {
        tty_queue->chars += (QUEUE_TRANSFER)(ulDMA_rx - ulDMA_progress[channel]); // add the extra number of characters received by DMA since last check
    }
    else {
        tty_queue->chars += (QUEUE_TRANSFER)(RxModulo[channel] - (ulDMA_progress[channel] - ulDMA_rx)); // add the extra number of characters received by DMA since last check
    }
    ulDMA_progress[channel] = ulDMA_rx;                                  // remember this check state for future comparisons
    ptrDMA->DMA_DSR_BCR |= (0xffff0);                                    // retrigger maximum DMA transfer at each poll

where RxModulo[] is the modulo buffer length (2, 4, 8, 16, 32,...etc. up to max. 256k).

Regards

Mark

View solution in original post

0 Kudos
7 Replies
1,843 Views
mjbcswitzerland
Specialist V

Brian

if(UART2->S1 & UART_S1_TDRE_MASK & UART2->C2)

solves your conundrum.

But use DMA for better performance.

Regards

Mark

Complete Kinetis solutions for professional needs, training and support:http://www.utasker.com/kinetis.html
Kinetis K22:
- http://www.utasker.com/kinetis/FRDM-K22F.html
- http://www.utasker.com/kinetis/TWR-K22F120M.html
- http://www.utasker.com/kinetis/BLAZE_K22.html
- http://www.utasker.com/kinetis/tinyK22.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market

0 Kudos
1,843 Views
errorek123
Contributor III

Yeah your solution will guard me from unintentional TX send but else if is still required because if i got interrupt enabled for both i need to tell which one fired, i can easily check if that was RX and if it wasn't RX it have to be TX.

DMA for UART seems a little tricky, send should be straight forward but receive gonna be problematic because i dont know for how many bytes i should set DMA for?

0 Kudos
1,843 Views
mjbcswitzerland
Specialist V

Brian

You only enable the Tx interrupt when you start transmitting a buffer full of data - since you "always" have the TX empty flag set when you enter the IRQ. If you enable TX empty flag "only" when you send the first byte of a buffer and then disable it when the "last byte" has been copied the method works without any "else" complications.

Tx with DMA is quite easy since you know how many bytes are in the buffer, you can also add to the buffer during transmission.

Rx with DMA is easiest with free-running DMA as show in this video, so that it can be used withut needing to know how many bytes will be received:

Part 1:
https://www.youtube.com/watch?v=dNZvvouiqis&list=PLWKlVb_MqDQFZAulrUywU30v869JBYi9Q&index=10
Part 2:
https://www.youtube.com/watch?v=GaoWE-tMRq4&list=PLWKlVb_MqDQFZAulrUywU30v869JBYi9Q&index=11

In the uTasker project there is Tx and Rx interrupt and DMA driven Rx and Tx UART / LPUART that works on all Kinetis parts: note that if you move from K to KL parts, or parts with LPUART instead of UART you will need to redevelop all interrupt and DMA drivers, whereas the uTasker driver automatically adapts itself to ensure that any part can be used in a compatible manner.

You can also get the code from its open source version [https://github.com/uTasker/uTasker-Kinetis

(and copy parts from it if you want to have your own version), which allows you to simulate the K22, its UART, plus the UART interrupt and DMA operations to make development and debugging easier.

Since its code has been used in many industrial Kinetis based devices since 2011 it is very well proven for reliability and performance and has various additional features as explained in its UART guide: http://www.utasker.com/docs/uTasker/uTaskerUART.PDF

Regards

Mark

Complete Kinetis solutions for professional needs, training and support:http://www.utasker.com/kinetis.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market

0 Kudos
1,843 Views
errorek123
Contributor III

I have watched your videos and read PDF and it's a bit more clear but still i can't quite understand how RX should exactly work. I let DMA free-run so the current DMA pointer is my head but how do i keep track of the tail? At some point(overflow) data will be overwritten and tail should move accordingly but it's not handled by DMA. So the polling function should take care of updating the tail?

Also which register i can read to get current DMA pointer?

0 Kudos
1,843 Views
mjbcswitzerland
Specialist V

Brian

1. You need a count variable (eg. unsigned long ulDMA_progress) to remember the reception progress. It starts with the length of the rx circular buffer.

2. Since the DMA will be copying received data immediately to the circular buffer, and wrapping around automatically, the application must "poll" the progress and read out the data before it gets overwritten. The polling rate depends on how fast a (complete) message needs to be detected and also on how large the buffer is; if the buffer is larger it takes longer for it to be able to overflow.
3. To poll the progress one reads the DMA_TCD_CITER_ELINK register, which indicates the number of bytes that haven't yet been copied (it counts down) before automatically being reloaded to its initial value.
If (ulDMA_progress >= the snap shot value of DMA_TCD_CITER_ELINK) you know that the 'additional' bytes in the input buffer have increased by (ulDMA_progress - DMA_TCD_CITER_ELINK), which can also be 0 if nothing was received in the meantime, else the increase is (ulDMA_progress - (DMA_TCD_BITER_ELINK - DMA_TCD_CITER_ELINK))
4. After the check ulDMA_progress is updated to be equal to DMA_TCD_CITER_ELINK again.
5. In the calculations the snap-shot value of DMA_TCD_CITER_ELINK on entry is to be used throughout and not the present DMA_TCD_CITER_ELINK value since it can change and thus cause race-state errors.

Since the polling will do this each time it also updates the values each time new data has been detected. When new data is ready (or the total data count reaches a threshold that should be handled) the application should use the data (which will at some point later be overwritten) and decrement its the total waiting data count value (which is incremented on each poll by the new 'additional' data count value).

As example, FreeRTOS users (who otherwise don't have libraries for free-running Rx with DMA) can use the uTasker project (which integrates a FreeRTOS configuration) and set up a polling task that does something like:

static void uart_task(void *pvParameters)
{
    QUEUE_TRANSFER length = 0;
    QUEUE_HANDLE uart_handle;
    unsigned char dataByte;
    while ((uart_handle = fnGetUART_Handle()) == NO_ID_ALLOCATED) {      // get the UART handle
        vTaskDelay(500/portTICK_RATE_MS);                                // wait for 500ms in order to allow uTasker to configure UART interfaces
    }
    fnDebugMsg("FreeRTOS Output\r\n");                                   // test a UART transmission
    FOREVER_LOOP() {
        length = fnRead(uart_handle, &dataByte, 1);                      // read a byte from the DMA input buffer (returns immediately)
        if (length != 0) {                                               // if something is available
            fnDebugMsg("FreeRTOS Echo:");                                // echo it back
            fnWrite(uart_handle, &dataByte, 1);                          // send the byte back
            fnDebugMsg("\r\n");                                          // with termination
        }
        else {                                                           // nothing in the input buffer
            vTaskDelay(1);                                               // wait a single tick to allow other tasks to execute
        }
    }
}

which waits for the UART to be configured by uTasker in the appropriate mode and then polls it (here at 1 TICK I believe), and echos the data back to verify operation.

It allows any K, KL, KV, KM, KW part (with DMA HW support) to be used on UART or LPUART in a compatible manner and so doesn't need porting and new driver development each time functions are moved to different processors. It can be used for reception rates of several Mb/s without loss of data.

Regards

Mark

Complete Kinetis solutions for professional needs, training and support:http://www.utasker.com/kinetis.html
uTasker: supporting >1'000 registered Kinetis users get products faster and cheaper to market

0 Kudos
1,843 Views
errorek123
Contributor III

Thanks Mark,

I got it working, made it as You suggested, created 2 functions, one of them returns how many bytes arrived since last check so i can store it in a variable and use second function when i hit threshold to copy desired amount of bytes from dma buffer to my local buffer(this function also keep track of the tail)

And i'm pretty sure u made a mistake  here:

(ulDMA_progress - (DMA_TCD_BITER_ELINK - DMA_TCD_CITER_ELINK))

I'm pretty sure it should be :

(ulDMA_progress + (DMA_TCD_BITER_ELINK - DMA_TCD_CITER_ELINK))

0 Kudos
1,844 Views
mjbcswitzerland
Specialist V

Brian

Yes, I made an error when simplifying and coping the original code. This is how it actually is:

    usDMA_rx = ptrDMA_TCD->DMA_TCD_CITER_ELINK;                          // snap-shot of DMA reception progress
    if (ulDMA_progress[channel] >= usDMA_rx) {
        tty_queue->chars += (QUEUE_TRANSFER)(ulDMA_progress[channel] - usDMA_rx); // the extra number of characters received by DMA since last check
    }
    else {
        tty_queue->chars += (QUEUE_TRANSFER)ulDMA_progress[channel];
        tty_queue->chars += (ptrDMA_TCD->DMA_TCD_BITER_ELINK - usDMA_rx); // the extra number of characters received by DMA since last check
    }
    ulDMA_progress[channel] = usDMA_rx;                                  // remember the check state
‍‍‍‍‍‍‍‍‍

If you later need to do the same on devices without eDMA (like most of the m0+ based parts) the equivalent is:

    ulDMA_rx = ptrDMA->DMA_DAR;                                          // snap-shot of DMA reception progress
    if (ulDMA_progress[channel] <= ulDMA_rx) {
        tty_queue->chars += (QUEUE_TRANSFER)(ulDMA_rx - ulDMA_progress[channel]); // add the extra number of characters received by DMA since last check
    }
    else {
        tty_queue->chars += (QUEUE_TRANSFER)(RxModulo[channel] - (ulDMA_progress[channel] - ulDMA_rx)); // add the extra number of characters received by DMA since last check
    }
    ulDMA_progress[channel] = ulDMA_rx;                                  // remember this check state for future comparisons
    ptrDMA->DMA_DSR_BCR |= (0xffff0);                                    // retrigger maximum DMA transfer at each poll

where RxModulo[] is the modulo buffer length (2, 4, 8, 16, 32,...etc. up to max. 256k).

Regards

Mark

0 Kudos