ChibiOS Threading Model
The NanoVNA-H firmware runs on ChibiOS/RT, a compact real-time operating system designed for embedded systems. ChibiOS provides preemptive multithreading, allowing the firmware to handle measurement, user interface, and USB communication simultaneously.
Thread Architecture
Section titled “Thread Architecture”The firmware uses two main threads plus background interrupt handlers:
flowchart TB
subgraph KERNEL[ChibiOS Kernel]
SCHED[Scheduler<br/>100 kHz tick]
end
subgraph THREADS[Application Threads]
MAIN[Main Thread<br/>Normal Priority]
SWEEP[Sweep Thread<br/>High Priority]
end
subgraph ISR[Interrupt Handlers]
DMA[I2S DMA ISR]
USB[USB ISR]
SYSTICK[SysTick ISR]
end
SCHED --> MAIN
SCHED --> SWEEP
DMA --> |Audio Data| SWEEP
USB --> MAIN
SYSTICK --> SCHED Main Thread
Section titled “Main Thread”The main thread handles:
- User interface (touch, display updates)
- USB shell command processing
- Calibration calculations
- Non-time-critical operations
int main(void) { // Initialize hardware and peripherals halInit(); chSysInit();
// Start the sweep thread chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 10, Thread1, NULL);
// Main loop: UI and shell processing while (1) { if (sweep_mode & SWEEP_ENABLE) { // UI updates, touch handling ui_process(); }
if (shell_line_received) { // Process USB commands VNAShell_executeCMDLine(shell_line); } }}Sweep Thread
Section titled “Sweep Thread”The sweep thread runs at higher priority and handles:
- Frequency synthesizer control
- DSP accumulation coordination
- Measurement timing
- Calibration application
static THD_FUNCTION(Thread1, arg) { (void)arg;
while (1) { if (sweep_mode & SWEEP_ENABLE) { // Perform frequency sweep sweep(true, get_sweep_mask());
// Apply transform domain processing (if enabled) transform_domain(get_sweep_mask());
// Signal display to update redraw_request |= REDRAW_CELLS; }
chThdSleepMilliseconds(10); // Yield when idle }}System Tick Configuration
Section titled “System Tick Configuration”The system tick drives the scheduler and timing functions:
// From chconf.h#define CH_CFG_ST_FREQUENCY 100000 // 100 kHz tick rate#define CH_CFG_ST_TIMEDELTA 0 // Tickless mode disabled
// Time conversion macros#define US2ST(usec) ((systime_t)(((uint32_t)(usec) * CH_CFG_ST_FREQUENCY) / 1000000UL))#define MS2ST(msec) ((systime_t)(((uint32_t)(msec) * CH_CFG_ST_FREQUENCY) / 1000UL))The 100 kHz tick rate provides 10 microsecond timing resolution, essential for precise PLL settling delays.
Cooperative vs Preemptive Scheduling
Section titled “Cooperative vs Preemptive Scheduling”ChibiOS supports both scheduling modes. The NanoVNA uses preemptive scheduling:
- Higher-priority threads interrupt lower-priority threads
- The sweep thread can preempt the main thread at any time
- Interrupt handlers have highest priority
// Example: sweep timing with precise delaysstatic bool sweep(...) { for (p_sweep = 0; p_sweep < sweep_points; p_sweep++) { // Set frequency (may yield during I2C transactions) delay = set_frequency(frequency);
// Precise delay for PLL settling chThdSleep(delay); // Does not block lower priority threads
// DSP accumulation (interrupt-driven) DSP_START(delay); // ... calibration lookup while accumulating ... DSP_WAIT; // Blocks until DMA complete }}Synchronization Primitives
Section titled “Synchronization Primitives”Event Flags
Section titled “Event Flags”Used to signal between threads and ISRs:
// Wait for DMA completionstatic void DSP_WAIT(void) { chEvtWaitOne(EVT_DMA_COMPLETE);}
// Signaled from I2S DMA interruptvoid i2s_lld_serve_rx_interrupt(...) { dsp_process(buffer, length); if (--wait_count == 0) chEvtBroadcastI(&dsp_complete_event);}Mutexes
Section titled “Mutexes”Protect shared resources:
// Protect I2C bus accessMUTEX_DECL(i2c_mutex);
void i2c_transfer(...) { chMtxLock(&i2c_mutex); // ... I2C transaction ... chMtxUnlock(&i2c_mutex);}Interrupt Handlers
Section titled “Interrupt Handlers”I2S DMA (Audio)
Section titled “I2S DMA (Audio)”Highest-priority interrupt, processes audio samples:
OSAL_IRQ_HANDLER(STM32_I2S_SPI_HANDLER) { OSAL_IRQ_PROLOGUE();
// Process samples from DMA buffer dsp_process(rx_buffer + offset, AUDIO_BUFFER_LEN);
// Signal sweep thread if accumulation complete if (--wait_count == 0) chSysLockFromISR(); chEvtBroadcastI(&dsp_event); chSysUnlockFromISR();
OSAL_IRQ_EPILOGUE();}USB Interrupt
Section titled “USB Interrupt”Handles USB CDC (virtual COM port) communication:
OSAL_IRQ_HANDLER(STM32_USB_HANDLER) { OSAL_IRQ_PROLOGUE();
// ChibiOS USB driver handles the details usb_lld_serve_interrupt(&USBD1);
OSAL_IRQ_EPILOGUE();}SysTick
Section titled “SysTick”Drives the scheduler and system time:
OSAL_IRQ_HANDLER(SysTick_Handler) { OSAL_IRQ_PROLOGUE();
chSysLockFromISR(); chSysTimerHandlerI(); // Advance system time, check timeouts chSysUnlockFromISR();
OSAL_IRQ_EPILOGUE();}Thread Stack Allocation
Section titled “Thread Stack Allocation”Each thread needs its own stack. Stack sizes are carefully tuned to minimize RAM usage:
// Sweep thread stack (needs space for DSP and calibration)static THD_WORKING_AREA(waThread1, 768);
// Shell thread stack (needs buffer space for command parsing)static THD_WORKING_AREA(waThread_shell, 512);Timing Diagram
Section titled “Timing Diagram”A typical sweep cycle shows thread interactions:
Time -->
Main Thread: [UI]---[idle]---[UI]---[idle]--- | |Sweep Thread: -------[sweep point]--[sweep point]-- | | | | |I2S DMA ISR: ^ ^ ^ ^ ^ | | | | | Buffer interruptsCPU Utilization
Section titled “CPU Utilization”Typical CPU usage during sweep:
| Activity | CPU % | Notes |
|---|---|---|
| DSP processing | 30-50% | Depends on bandwidth setting |
| Frequency synthesis | 5-10% | I2C transactions |
| Display update | 10-20% | SPI transfers |
| USB/Shell | 1-5% | Only when active |
| Idle | 15-50% | Available for other tasks |
Memory Map
Section titled “Memory Map”ChibiOS places thread stacks and kernel structures in RAM:
RAM Layout (F072 - 16 KB):0x20000000 +------------------+ | Vector table | +------------------+ | Global variables | | measured[] | | cal_data[] | +------------------+ | Heap (minimal) | +------------------+ | Thread 1 stack | +------------------+ | Main stack |0x20003FFF +------------------+Error Handling
Section titled “Error Handling”ChibiOS provides assertion and panic mechanisms:
// Assertion in kernel codechDbgAssert(ptr != NULL, "null pointer");
// Panic handler (called on fatal errors)void _unhandled_exception(void) { chSysHalt("exception");}In release builds, assertions are disabled to save code space. The firmware generally attempts to continue operation even after errors.
Summary
Section titled “Summary”| Component | Role | Priority |
|---|---|---|
| Main Thread | UI, shell, config | Normal |
| Sweep Thread | Measurement | Normal + 10 |
| I2S DMA ISR | Audio processing | Interrupt |
| USB ISR | Communication | Interrupt |
| SysTick ISR | Scheduling | Interrupt |
Next Steps
Section titled “Next Steps”Learn how the firmware efficiently updates the display in Display Pipeline.