Course contentsShow
← Embedded Systems & Computer Architecture
Lesson 405 of 9365. Communication Protocols and Data TransferFree lesson

Implementing Double Buffering for Continuous Streams

Construct ping-pong buffer schemes to allow the CPU to process one data block while the DMA fills another.

In this lesson, you will learn how to use a "ping-pong" buffer scheme to eliminate data loss when processing high-speed, continuous data streams via DMA.

The Bottleneck Problem

In previous lessons, you learned that DMA allows data to move from a peripheral to memory without CPU intervention. However, if your application processes data in real-time, you run into a synchronization hazard: if the CPU is busy processing Buffer A while the DMA is still writing to it, the DMA will overwrite the data you are currently reading, leading to corruption. If you stop the DMA to wait for the CPU, you might miss incoming samples from your peripheral.

The Ping-Pong Solution

Double buffering solves this by creating two memory regions: Buffer 0 (the "Ping") and Buffer 1 (the "Pong"). The strategy works as follows:

  1. The DMA is configured to fill Buffer 0.
  2. Once Buffer 0 is full, the DMA controller generates an interrupt and is instructed to immediately start filling Buffer 1.
  3. While the DMA hardware is busy filling Buffer 1, the CPU is granted exclusive access to process the complete data stored in Buffer 0.
  4. When the DMA finishes Buffer 1, it signals the CPU, switches back to Buffer 0, and the cycle repeats.

Conceptual Implementation

You typically manage this by maintaining a pointer that toggles between the two buffers inside your DMA completion Interrupt Service Routine (ISR):

#define BUFFER_SIZE 512
uint8_t buffer_ping[BUFFER_SIZE];
uint8_t buffer_pong[BUFFER_SIZE];
uint8_t *active_buffer = buffer_ping;

// Inside DMA Interrupt
void DMA_IRQHandler() {
    if (DMA_TransferComplete()) {
        // Toggle the buffer for the next DMA cycle
        if (active_buffer == buffer_ping) {
            active_buffer = buffer_pong;
            process_data(buffer_ping); // CPU works on ping
            configure_dma_destination(buffer_pong); 
        } else {
            active_buffer = buffer_ping;
            process_data(buffer_pong); // CPU works on pong
            configure_dma_destination(buffer_ping);
        }
    }
}

By decoupling the "input" (DMA filling memory) from the "output" (CPU processing memory), the system maintains a continuous flow. As long as your CPU processing time is shorter than the time it takes to fill one buffer, your system will never drop a single byte of data.

Key Takeaway: Double buffering enables true parallelism in embedded systems by allowing the DMA to fill one buffer while the CPU concurrently processes the other, ensuring zero-latency data stream processing.