Search code examples
cconcurrencyembeddedinterruptdma

Swap pointers atomically to implement double buffering in C


I have a cortex M3 system that executes some code on a buffer of data that is provided via DMA. There are 3 buffers: 1 for the continuous stream for the DMA, 1 to copy the streamed data at 50% point and 100%, and another to store a copy to be operated on.

The ISR copies the 1st half of the buffer once that buffer is half filled, and again the 2nd half when the buffer is fully filled.

While the lower half of the 1st buffer is streaming in again, I swap the 2nd buffer and the 3rd buffer, and operate on the most recently filled data. My concern arises from the pointer swap. It would result in 1 load instruction and 2 store instructions. If an ISR occurs I can end up with a memory leak and/or data loss. Is there an atomic way to swap these pointers? I'd like to avoid dropping the data and just waiting for the next iteration when the DMA is full again and also avoid disabling interrupts.

Also on the point of interrupts and data loss, lets say there is an audio stream and the processing done on the stream cannot keep up with the input rate. Do we drop the data? Is it similar for things like Ethernet controllers or USB controllers etc? I'm curious how this problem is tackled in other systems.


Solution

  • Do you actually start really processing the data on the first buffer filled per DMA, or do you just copy the data (per memcpy() I assume from your description) to other buffers, whatever the reason is to copy them all that much around.

    Why would you copy at 50%/100%, someplace, and then have another buffer "to store a copy to operate on".

    Especially, when you copy per memcpy() as it sounds like, you even place more copying activity on the CPU, and have less CPU resources left to actually process the data itself.

    Maybe you can even configure your incoming DMA to place the data already in proper double buffer memories. Or if you really need copying, setup a new DMA to do it.

    To update the pointers, have an exclusive area around e.g. locking interrupts.

    void Handler() {
        
        enter_exarea_0();
        // --- Exclusive Area0 start ---
        // .. switch pointers 
        // --- Exclusive Area0 end ---
        exit_exarea_0();
        // activity
    }
    
    void enter_exarea_0() {
        // Disable All Interrupts or up to a certain interrupt lock level
    }
    void exit_exarea_0() {
        // Enable All interrupts or decrease the interrupt lock level
    }
    

    And if you are too slow, it depends a bit on the data.

    You could stop the processing of the current frame/data and start with the next one, so, you might have only 50% or 70% processed, e.g. if each frame has like an array of 1000 samples x/y, maybe you loose here some x/y samples at the end.

    Or you finish the frame/data processing and skip an older available frame due to a newer available frame. Or the DMA just has overwritten an unprocessed frame, e.g. if each frame is a picture, you loose maybe a whole picture of a sequence of pictures.