NOTES¶
Overview¶
Architecture 2 implements a DMA + Double Buffer (ping-pong buffer) pattern for high-frequency real-time sensor data acquisition and processing. This architecture is optimized for high sampling frequencies (0.1 - 10000 Hz, recommended: 1 kHz - 10 kHz) and provides low CPU usage through DMA-assisted SPI transfers.
Architecture Principles¶
DMA + Double Buffer Pattern¶
The architecture uses a ping-pong (double buffer) mechanism:
- Buffer A and Buffer B: Two separate buffers that alternate between acquisition and processing
- Producer Task: Fills one buffer while consumer processes the other
- Consumer Task: Processes filled buffer while producer fills the other
- Parallel Operation: Acquisition and processing can overlap in time
- DMA Support: SPI transfers use DMA automatically, reducing CPU load
Key Design Decisions¶
- Double Buffer (Ping-Pong): Two buffers allow parallel acquisition and processing
- Buffer Switching: When one buffer is full, producer switches to the other buffer
- Semaphore Signaling: Binary semaphores notify consumer when a buffer is ready
- Mutex Protection: Each buffer has its own mutex for thread-safe access
- DMA Automatic: ESP-IDF SPI driver automatically uses DMA for transfers
Implementation Details¶
Timer-Based Sampling¶
- ESP Timer: Creates a periodic timer with period =
1,000,000 / sampling_frequency_hzmicroseconds - Timer Callback: Executes in timer context, notifies producer task via
xTaskNotify - Immediate First Sample: Performs one sample immediately before starting periodic timer
Producer Task¶
Priority: 10 (High priority for timely acquisition)
Functionality:
- Waits for timer notification via
xTaskNotifyWait - Reads sensor data (DMA handles SPI transfer automatically)
- Prepares sample structure with timestamp
- Writes to active buffer (A or B) with mutex protection
- When buffer is full:
- Marks buffer as ready
- Switches to other buffer
- Signals consumer via semaphore
- Resets write index
- Updates acquisition statistics
Buffer Management:
- Tracks active buffer (0 = A, 1 = B)
- Writes sequentially to active buffer
- Switches buffers when full
- Detects overwrites if consumer hasn't processed buffer in time
Consumer Task¶
Priority: 8 (High priority for processing)
Functionality:
- Waits for buffer ready semaphore (either A or B)
- Takes mutex for the ready buffer
- Processes all samples in the buffer
- Marks buffer as processed and resets ready flag
- Updates processing statistics
- Outputs results (serial, MQTT, LCD)
Processing Logic:
- Processes entire buffer (all samples)
- Performs feature extraction, detection, etc.
- Handles acceleration detection with LCD feedback
- Outputs first sample via serial (if enabled)
Double Buffer Mechanism¶
Buffer Size: 512 samples per buffer (configurable via RT_DMA_DB_BUFFER_SIZE)
Memory: Both buffers allocated from PSRAM
Synchronization:
- Mutex for each buffer (protects write/read access)
- Binary semaphore for each buffer (signals when ready)
- Volatile flags for buffer ready state
Operation Flow:
- Producer fills buffer A
- When buffer A is full, producer switches to buffer B and signals consumer
- Consumer processes buffer A while producer fills buffer B
- When buffer B is full, producer switches to buffer A and signals consumer
- Consumer processes buffer B while producer fills buffer A
- Cycle repeats

Figure: Double buffer (ping-pong) mechanism showing parallel acquisition and processing
DMA Support¶
- Automatic DMA: ESP-IDF SPI driver automatically uses DMA for transfers
- CPU Reduction: DMA handles data transfer, freeing CPU for other tasks
- Non-blocking: SPI operations are non-blocking with DMA
Data Flow¶
ESP Timer → Timer Callback → xTaskNotify → Producer Task (Core 0/1)
↓
Read Sensor (SPI + DMA)
↓
Active Buffer (A or B)
↓
Buffer Full → Switch Buffer
↓
Semaphore Signal → Consumer Task
↓
Process Buffer
↓
┌───────────────────┴───────────────────┐
↓ ↓ ↓
Serial MQTT LCD
Configuration¶
Default Configuration¶
#define RT_DMA_DB_BUFFER_SIZE 512
#define RT_DMA_DB_PRODUCER_PRIORITY 10
#define RT_DMA_DB_CONSUMER_PRIORITY 8
#define RT_DMA_DB_PRODUCER_STACK_SIZE 4096
#define RT_DMA_DB_CONSUMER_STACK_SIZE 8192
Configuration Parameters¶
- Sampling Frequency: 0.1 - 10000 Hz (validated at initialization)
- Buffer Size: 512 samples per buffer (fixed at compile time)
- Total Memory: 512 × 2 × 32 bytes = 32 KB from PSRAM
Features¶
Advantages¶
- Low CPU Usage: DMA handles SPI transfers automatically
- High Throughput: Parallel acquisition and processing
- Suitable for High Frequency: Optimized for 1 kHz - 10 kHz range
- True Parallelism: Producer and consumer can operate simultaneously
- No Queue Overhead: Direct buffer access, no queue management
Limitations¶
- Higher Memory Usage: Requires two large buffers (double the memory)
- Fixed Buffer Size: Buffer size is fixed at compile time
- Buffer Overwrite Risk: If consumer cannot keep up, buffers may be overwritten
Performance Characteristics¶
Suitable Frequency Range¶
- Valid Range: 0.1 - 10000 Hz (validated at initialization)
- Recommended: 1 kHz - 10 kHz
- Maximum: Up to 10 kHz (validated limit)
- Minimum: 0.1 Hz (practical limit)
Resource Usage¶
- Memory: Two buffers (512 samples × 2 × 32 bytes = 32 KB from PSRAM)
- CPU: Low usage (DMA handles transfers)
- Synchronization: Two mutexes and two binary semaphores
Usage Notes¶
- Initialization Order: Must call
arch_dma_db_init()beforearch_dma_db_set_sensor_handle() - Sensor Handle: Must be set before starting
- Buffer Monitoring: Monitor
overwrite_countin statistics to detect if consumer is falling behind - Processing Latency: Processing latency depends on buffer fill time (buffer_size / sampling_frequency)
- DMA Configuration: Ensure SPI DMA is enabled in ESP-IDF configuration
Error Handling¶
- Mutex Timeout: Producer drops sample if mutex cannot be acquired within 10ms
- Buffer Not Initialized: Sample dropped if buffers not ready
- Sensor Read Failure: Logged but does not stop processing
- Task Creation Failure: Returns error, cleans up resources
Thread Safety¶
- Mutex Protection: Each buffer has its own mutex for thread-safe access
- Task Isolation: Producer and consumer tasks are properly isolated
- Semaphore Signaling: Binary semaphores provide safe inter-task communication
- Statistics: Updated atomically within mutex-protected sections