You are browsing as a guest. Sign up (or log in) to start making projects!

4h 1m 16s logged

Complete RTOS Overhaul, Circular DMA Pipelines, and CPU Abort Fixes

This update focuses on fine-tuning the RTOS, organizing the scheduler, and rewriting microcontroller memory handling. Renode’s simulated DMA restrictions make software-only pipeline testing nearly impossible, so I plan to buy a physical dev board and Raspberry Pi to validate on silicon. While the Git diff might not look massive, the architecture has evolved from a blocking system to a true preemptive RTOS with precise polynomial timing.

1. Reworking DMA Flow & Standardizing Ring Buffers

Sensor and receiver data pipelines are now driven by circular DMA, enabling non-blocking, high-frequency transfers with response times as fast as 0.3 ms (without printToUSART enabled). Everything now relies on ring buffers. Instead of generating complete packets instantly, mock injectors stream bytes directly into the buffers while updating the simulated NDTR register to mirror hardware behavior.

if (byteIdx >= frameLen) { byteIdx = 0; }
crsfRingBuffer[head] = frameBuf[byteIdx++];
head = (head + 1) % CRSF_RING_BUFFER_SIZE;
currentBoardConfig.crsf_dma_stream->NDTR = CRSF_RING_BUFFER_SIZE - head;

Feeding bytes incrementally moves the write head gradually, ensuring the simulation mimics a physical UART and allowing population functions to safely pull data within interrupt locks.

2. DMA Interrupts for Safe Parsing

To prevent data overwrites, DMA streams trigger interrupts at the halfway (HTIE) and full-transfer (TCIE) points to queue tasks. Because registers like LISR are read-only in hardware, I implemented a software mock flag system (mock_LISR) for simulation:

extern "C" void DMA1_Stream0_IRQHandler(void) {
#ifdef SIMULATION
    uint32_t flags = mock_LISR; mock_LISR = 0;
#else
    uint32_t flags = DMA1->LISR;
#endif
    if (flags & DMA_LISR_HTIF0) {
        DMA1->LIFCR = DMA_LIFCR_CHTIF0;
        decideNextInterruptTask(&taskControlBlocks[1]);
    }
}

Functions safely extract data with interrupts disabled, ensuring the CPU never blocks waiting for peripheral transfers.

3. Assembly Bugs & CPU Aborts

Debugging nested hardware interrupts exposed massive faults:

  • The context-switching bug: FPU usage or large stack frames corrupted the return address, causing the CPU to execute BX LR with garbage data. Fixed in PendSV_Handler.S by explicitly storing excReturn inside the TaskControlBlock rather than relying on stacked registers.
  • Second, a 0x00000000 Null Return Crash: Scheduled tasks attempted to return natively, popping 0x00000000 from the zero-initialized Link Register and causing a fetch fault. Every task now runs inside an infinite while(true) loop calling yieldCurrentTask().

4. Clock PLLs & Hardware Re-routing

After configuring the STM32CubeMX PLL for a 25 MHz HSE clock, the MCU runs at its full 216 MHz. I added a standardized initSystemGPIOClocks() function to enable peripheral clocks consistently. I also moved motor 3 from TIM1 to TIM4, synchronizing it with the 216MHz clock and freeing up a TIM1 DMA stream exclusively for the upcoming BlackBox logging.

Future Updates

With the core RTOS stabilized, development shifts to flight-facing systems: hooking the SPI pipeline to the video overlay for live OSD goggle telemetry, adding permanent flash storage for PID tunes/configs, exposing MCU controls via CLI, supporting programmable transmitter bind buttons, building a desktop GUI, and using the freed TIM1 DMA for BlackBox logging.

Below, I have attached a screenshot of every single task working, and outputting values in the simulation, something that wasn’t possible for the interrupt tasks until now.

0
69

Comments 0

No comments yet. Be the first!