Skip to content

NOTES

Overview

Architecture 1 implements a Producer-Consumer pattern using a circular buffer for real-time sensor data acquisition and processing. This architecture is designed for moderate sampling frequencies (0.1 - 1000 Hz, recommended: 100 Hz - 1 kHz) and provides a simple, well-understood implementation pattern.

Architecture Principles

Producer-Consumer Pattern

The architecture uses a classic Producer-Consumer pattern with the following components:

  • Producer Task (High Priority): Reads sensor data at fixed intervals and writes to a circular buffer
  • Consumer Task (Medium Priority): Periodically reads from the circular buffer and processes data
  • Circular Buffer: PSRAM-based buffer that allows overwriting old data when full (FIFO overwrite)
  • Mutex Synchronization: Protects buffer access between producer and consumer tasks

Key Design Decisions

  1. Circular Buffer Instead of Queue: Uses a circular buffer for better memory efficiency and simpler implementation
  2. FIFO Overwrite: When buffer is full, oldest data is overwritten (no data loss tracking for producer, but consumer can detect overwrites)
  3. Periodic Consumer Processing: Consumer processes buffer every 50ms, reading up to 10 most recent samples
  4. Task Notification: ESP timer callback notifies producer task via xTaskNotify to avoid blocking timer callback

Implementation Details

Timer-Based Sampling

  • ESP Timer: Creates a periodic timer with period = 1,000,000 / sampling_frequency_hz microseconds
  • 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:

  1. Waits for timer notification via xTaskNotifyWait
  2. Reads sensor data (acceleration and temperature)
  3. Prepares sample structure with timestamp
  4. Writes to circular buffer with mutex protection
  5. Updates acquisition statistics

Buffer Write Logic:

  • Takes mutex with 10ms timeout
  • Writes to current write pointer position
  • Advances write pointer (circular)
  • If buffer full (write pointer catches read pointer), advances read pointer (FIFO overwrite)
  • Updates overwrite count for monitoring

Consumer Task

Priority: 8 (Medium priority, lower than producer to ensure acquisition priority)

Functionality:

  1. Waits for processing interval (50ms)
  2. Takes mutex to read buffer snapshot
  3. Reads up to 10 most recent samples from read pointer
  4. Advances read pointer
  5. Processes samples (feature extraction, detection, etc.)
  6. Updates processing statistics
  7. Outputs results (serial, MQTT, LCD)

Processing Logic:

  • Calculates available samples (distance between read and write pointers)
  • Reads up to CONSUMER_PROCESS_SAMPLE_COUNT (10) samples
  • Handles circular buffer wrap-around
  • Zero-fills if not enough samples available

Circular Buffer

Size: 512 samples (configurable via CIRCULAR_BUFFER_SIZE)

Memory: Allocated from PSRAM for large buffer support

Synchronization: Mutex-protected access

Behavior:

  • FIFO overwrite when full
  • Read pointer advances as consumer reads
  • Write pointer advances as producer writes
  • Overwrite count tracked for monitoring

Circular Buffer Animation

Figure: Circular buffer operation showing producer writing and consumer reading with FIFO overwrite behavior

Acceleration Detection

When enabled, the consumer task performs acceleration detection on processed samples:

Conditions:

  • |x| > 0.5g OR |y| > 0.5g OR |z| < 0.5g

LCD Feedback:

  • RED when conditions are met
  • WHITE otherwise
  • Color persistence: 0.3 seconds
  • Minimum update interval: 100ms (to avoid SPI bus conflicts)

Data Flow

ESP Timer → Timer Callback → xTaskNotify → Producer Task
                                    Read Sensor (SPI)
                                    Circular Buffer (Mutex)
                                    Consumer Task (50ms interval)
                                    Process Samples
                          ┌───────────────────┴───────────────────┐
                          ↓                   ↓                    ↓
                       Serial              MQTT                  LCD

Configuration

Default Configuration

#define RT_PC_QUEUE_SIZE_DEFAULT 50
#define RT_PC_PRODUCER_PRIORITY 10
#define RT_PC_CONSUMER_PRIORITY 8
#define RT_PC_PRODUCER_STACK_SIZE 4096
#define RT_PC_CONSUMER_STACK_SIZE 8192
#define CIRCULAR_BUFFER_SIZE 512
#define CONSUMER_PROCESS_INTERVAL_MS 50
#define CONSUMER_PROCESS_SAMPLE_COUNT 10

Configuration Parameters

  • Sampling Frequency: 0.1 - 1000 Hz (validated at initialization)
  • Queue Size: Used for configuration structure compatibility (not used in circular buffer mode)
  • Circular Buffer Size: 512 samples (fixed at compile time)
  • Consumer Process Interval: 50ms (fixed at compile time)
  • Consumer Process Sample Count: 10 samples (fixed at compile time)

Features

Advantages

  • Simple Implementation: Well-understood Producer-Consumer pattern
  • Decoupled Acquisition and Processing: Producer and consumer operate independently
  • Memory Efficient: Circular buffer with overwrite capability
  • Low Overhead: Minimal synchronization overhead
  • Suitable for Medium Frequency: Optimized for 100 Hz - 1 kHz range

Limitations

  • Buffer May Become Bottleneck: If consumer cannot keep up, data may be overwritten
  • Fixed Processing Interval: Consumer processes at fixed 50ms intervals
  • Limited to Recent Samples: Only processes last 10 samples per cycle
  • No DMA Support: Uses standard SPI transfers (CPU-bound)

Performance Characteristics

Suitable Frequency Range

  • Valid Range: 0.1 - 1000 Hz (validated at initialization)
  • Recommended: 100 Hz - 1 kHz
  • Maximum: Up to 1 kHz (validated limit)
  • Minimum: 0.1 Hz (practical limit)

Resource Usage

  • Memory: Circular buffer (512 samples × 32 bytes = 16 KB from PSRAM)
  • CPU: Medium usage (producer and consumer tasks)
  • Synchronization: Single mutex for buffer access

Usage Notes

  1. Initialization Order: Must call arch_pc_init() before arch_pc_set_sensor_handle()
  2. Sensor Handle: Must be set before starting
  3. Buffer Monitoring: Monitor overwrite_count in statistics to detect if consumer is falling behind
  4. Processing Latency: Consumer processes every 50ms, so processing latency is up to 50ms
  5. Sample Count: Consumer processes up to 10 samples per cycle for real-time response

Error Handling

  • Mutex Timeout: Producer drops sample if mutex cannot be acquired within 10ms
  • Buffer Not Initialized: Sample dropped if buffer not ready
  • Sensor Read Failure: Logged but does not stop processing
  • Task Creation Failure: Returns error, cleans up resources

Thread Safety

  • Mutex Protection: All buffer access is protected by mutex
  • Task Isolation: Producer and consumer tasks are properly isolated
  • Statistics: Updated atomically within mutex-protected sections