stardance has been extended another month! the new deadline is october 31 :)

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

Guagyyy

@Guagyyy

Joined June 1st, 2026

  • 21Devlogs
  • 3Projects
  • 2Ships
  • 33Votes
I'm a sophomore in high school, interested in all fields of computer science. My goal with these hackathons is to see what parts of CS interest me the most so I can major in something which I find interesting in college!
Open comments for this post

4h 13m 22s logged

I finished the screen class rendering the actual projects. This was mostly just trial and error, which is why it took so long, but now, I have a basic layout which I can expand upon. There are still a few issues with the image downloading, so I will have to fix that, but so far the actual project information getting pipeline seems to be flawless. I also created the detailed project widget, and made pseudocode for the rendering of it, however it is not fully implemented. Since the individual project pages will be a simple get request from the Modrinth API, it the detailed widget will just be constructing a different URL to perform the get request (as I will not be using the ‘search?’ part of the API). I am also planning to add Curseforge support, but that will only be done once the full Modrinth pipeline is done. I will likely commit the code tomorrow, as right now I am experiencing issues with committing to a different branch in GitHub, as I don’t want to commit to main, since that is only for releases.

0
0
15
Open comments for this post

2h 52m 7s logged

Simple Mod Manager is a lightweight utility that allows players to easily enable, disable, and manage their installed mods without manually moving files. For Stardance, I am trying to add the two most complicated features-creating an in game mod, resourcepack, and shader downloader, as well as a mod hot-swap to update mods in real time to avoid a full game restart. These will be difficult to perfect mostly because I have barely any experience in JVM class loading, and I have done little to no work with file downloading. Before Stardance, I had planned to make a separate mod called ModGuard with these things added, however I realized there wasn’t many features to add to it, and since I already had a decent user base to Simple Mod Manager, I decided to merge the two instead, so that users wouldn’t ever have to leave the game, or even restart it when installing any modifications (super helpful for modpack creators and streamlining the creation process)!

1. Download System:

I fixed the download pipeline. For whatever reason, before fetching stuff from the api wasn’t working, and today I fixed that. It came down to a mix of a few issues both involving my internet connection, as well as a broken enum.toString() method getting called, which caused a crash. The old download system also didn’t support resourcepacks and shaders, so I added a few fields to the widget class, and made an enum to represent that (ProjectType). Now whenever fetching from the API, I must specify the type which I am getting (e.g. ProjectType.MOD), and it will filter for mods in the facets. After that was done and tested, I had the whole pipeline for downloading done, meaning I just needed something to represent the mods.

2. Screen System:

I made a screen which had the facet buttons, project widgets, search bar, and tab bar. Since these were all stored inside of ArrayLists, I would only have to call the render method for each button within a for loop, making the rendering process super easy. After getting that to work, I added a few background containers, as well as a method to auto detect when a button/search query was changed and fetch the project details for the user. Now I just have to add automatic scrolling detection which will automatically call the API when I am out of projects to see. In order to avoid overlap, I will use the offset and limiters inside the Modrinth API query to skip the first set of mods which the user already saw, and then display the next set, by simply adding them to the ArrayList..

Next Steps:

After I add those small features, the next step will be to extend the project widget class in order to make a more detailed version for when you actually click on the project to download it.

0
0
12
Open comments for this post

8h 16m 42s logged

Although I haven’t been able to make much progress on the project due to school over the past weeks I did manage to make some substantial project mostly with the scheduler, allowing for fairly good performance on my STM Nucleo boards, rather than it just crashing instantly.

1. Issues with the Old Design

The flight controller’s RTOS scheduler wasn’t working under real hardware timing because global variables like currTaskIndex, topOfStack, and excReturn were being modified from multiple entry points simultaneously. Yield calls, interrupt handlers, and the main task-selection logic were all making independent writes to the same shared task control blocks. Whenever a hardware interrupt fired mid-write, stack pointers and exception return values were saved into the wrong taskControlBlocks slot. This created edge cases where preempted tasks were silently double-queued, state tracking completely desynchronized, and time-critical hardware events were dropped during flight.

2. Centralizing Context Switches in PendSV

To eliminate these race conditions, all state-selection decisions were moved out of individual execution contexts and consolidated into decideNextTask(), which now runs exclusively inside the PendSV assembly handler. Functions like yieldCurrentTask(), decideNextInterruptTask(), and decideNextScheduledTask() no longer touch global scheduler state or choose which task runs next; they simply update local status flags like TaskState::BLOCKED or TaskState::READY inside brief __disable_irq() critical sections and trigger PendSV using SCB_ICSR_PENDSVSET_Msk. When PendSV fires, decideNextTask() saves outgoingSP and outgoingExcReturn to the active block, sets RUNNING tasks to READY, and runs a two-pass check over taskControlBlocks[]—evaluating high-priority TaskType::INTERRUPT tasks first, then TaskType::SCHEDULED tasks, and falling back to index 0 for idle. Since PendSV is the only place allowed to modify currTaskIndex, concurrent state corruption is structurally impossible.

0
0
38
Ship

200 MPH Drone RTOS: How it went Simulation to Silicon!

When I published the first major update for this project at the 15-hour mark, I essentially had a proof-of-concept. I had managed to write a custom hybrid cooperative/preemptive scheduler and get an ARM Cortex-M7 (on the STM32f756VGTx) assembly context switcher functioning inside the Renode simulator. Although this looked like a massive leap, the project had barely started.

Over 100 hours of development have gone into this project since then. The transition from a simulated kernel to a bare-metal flight controller capable of keeping a 200 MPH drone in the air required stripping the system down to its bare components, rewriting the memory architecture, and debugging some of the most frustrating hardware faults I have ever encountered-especially whenever the project just crashed, leaving me no traces as to what went wrong.

Software Limitations

The biggest turning point was realizing that software simulation can only take you so far. Renode is an amazing tool, but it hid a massive amount of race conditions and timing flaws that only exposed themselves when I finally flashed the code to physical STM32F756 Nucleo board. Sometimes, the MCU would have to reboot every 2 seconds.

I spent dozens of hours tracking down massive architectural bugs. One of the worst was a context-switching fault where floating-point math was corrupting the return address in the CPU registers, causing the processor to execute raw garbage data.

To fix this, I had to completely restructure the boot sequence and overhaul the memory management. I moved away from static arrays to a dynamic per-task stack allocation system, and implemented a dual-guard memory system. Arguably, this was the most important change as it allowed me to debug tasks way quicker as I instantly could know when the canary values were corrupted.

Overhauling the Data Pipelines

In order to avoid the 10ms delay of my original blocking pipline (for data transfer), I entirely rewrote the hardware data pipelines multiple times to use a semi-blocking, circular Direct Memory Access (DMA) architecture. Instead of the processor handling the data transfer, the hardware itself moves the bytes from the sensors directly into memory in the background. The DMA streams now trigger precise interrupts at the halfway and full-transfer points, allowing the RTOS to safely parse incoming radio packets and sensor reads without ever risking data overwrites or blocking the main flight loop.

Building a Flight Controller

I integrated Betaflight’s Actual Rates algorithm with custom expo curves, ensuring that physical stick inputs map perfectly to target velocities. I built a custom SPI driver to pull 24-bit raw pressure and temperature data from a BMP390 barometer, passing it through hypsometric formulas to calculate altitude. For video and telemetry, I moved away from raw UART strings and implemented native MSP DisplayPort communication (for true digital Walksnail Avatar connection).

I also built an interactive USB command-line interface. Instead of having to recompile and re-flash the firmware every time a PID value needs to be adjusted, I can plug into the board, pull up a terminal, and manually tune gains, remap CRSF radio channels, or adjust IMU orientation offsets in real time.

Final Version

The last few weeks have been about refinement and consistency, removing race conditions and overall polishing the code. Now that the RTOS is fully functional, the only things to add are extra features, like a permanent + more expanded CLI, as well as finishing the external RM and linking the ESC drivers. As these are pretty insignificant, I have linked the source of this shipment to the Github project rather than a specific release so that I can update these during review!

  • 13 devlogs
  • 106h
  • 19.18x multiplier
  • 2024 Stardust
Try project → See source code →
Open comments for this post

7h 16m 34s logged

For the past couple weeks I have been mostly refining small bugs, which were originally rendering the RTOS usless, as well as refactoring the code for simplicity rather than elegancy.

1. Transition to Actual Hardware

Renode is great for simulation, but it ended up hiding a bunch of race conditions in the population methods and the main scheduler that led to no code actually running during the IRQ handler calls. I spent a lot of time securing those resources and finally got the scheduler mostly finished up. Also, I decided to just take out the double preempts entirely since they were adding way too much complexity for absolutely zero speed benefits in practice (plus they were creating detrimental but rare race conditions).

2. Code Refactor

To make things run a bit more consistent, I restructured how the control loops operate. I ended up merging the dShotGeneration task with the parent IMU control loop, so now everything from reading the sensors to pushing motor commands happens in one clean sequence (rather than including an unnecessary preempt). I also built out a brand new CRSF control loop to handle all the radio parsing and state machine updates in its own dedicated space (just for consistency). Lastly, while doing a final pass on the code, I realized that in my dShotGeneration helper, I had forgot to scale the values by a factor of 1/2000, as the inputs require the value to be between 0 and 1 (not 0 and 2000).

3. Final DMA Iteration

The biggest relief was finally getting rid of the old blocking USART system. I finally transitioned away from manually handling the streaming by polling flags in a loop, which was tying up the processor way more than it should have. Everything has been converted over to a blocking DMA system, so the hardware can just handle all the memory transfers in the background while the CPU focuses on actually keeping the drone in the air. Overall, I think that this will be the final iteration of the RTOS DMA pipeline, as now that it is solid and works with all the edge cases, theres no need to change it.

4. Timeline

With almost all the major bugs fixed, I am going to ship the project! While it is not 100% done, it will likely be as close as it will get during the timeframe of the Stardance hackathon, and with it so close, theres no reason not to ship. Anyway, I will keep the source as the main project rather than a release, so if I do end up fixing any major bugs, they will get patched either before or during project review (besides most of these bugs are only detectable on actual hardware).

I will most likely finish the RTOS in the next couple weeks (by the end of October), and will finish the drivers for the ESC and RM by latest end of November (for more timing refer to the README).

Thanks to everyone who supported me throughout this project (hopefully me and my friend can get this drone flying in another Hack Club Hackathon!).

0
0
52
Open comments for this post

9h 39m 38s logged

1. Scheduler Hardening and Task Discovery

Spent part of this session making the RTOS scheduler itself more robust. I added a findTaskByName() function so interrupt-driven task lookups happen dynamically instead of relying on hardcoded task indices, which was a fragile setup just waiting to break the moment task registration order changed. Alongside that, I wrapped every one of those lookups with lambdas that throw a proper warning if a task can’t be found, so a missing task fails loudly instead of silently dereferencing a null pointer somewhere downstream.

2. Blocking vs Non-Blocking Scheduled Tasks

After a lot of painful debugging, I traced a string of issues (including interrupt tasks silently failing to fire and scheduled tasks stepping on each other) back to non-blocking statements I’d introduced inside scheduled tasks in the previous devlog. I ended up taking those out and reverting scheduled tasks fully back to blocking, which is genuinely what the scheduled zone was designed for in the first place: tasks that don’t need to run in polynomial time and can safely block without putting the drone at risk of crashing. Once that was sorted, I converted the raw USART byte-pushing into a blocking DMA system instead, so streaming is handled by DMA hardware rather than manually pushed byte-by-byte over USART.

3. Flight Loop Refactor and VTX Communication

On the flight logic side, I refactored flightLoop() to use lambdas and a state machine so I would never have to yield in the middle of my tasks (which was used previousely for zeroing motor outputs). I also converted the error handling into a proper state machine, so different types of warnings can be caught and handled distinctly instead of lumping every issue into one path, and turned on full debug printing whenever the system is running. Last but not least, I built out a dedicated injector for respondToVTX so I can properly test and simulate VTX communication going forward.

4. Real Hardware Testing

Since, I am almost 100% done with the RTOS I bought two NUCLEO-F756ZG’s which will serve as testing the 100% production code rather than the simulation version. I bought two so that one of the boards will be running the real RTOS code, testing DMA reading from USART, SPI, and emitting real motor signals, while the second will be emulating all of my peripherals. When the boards come, if the code does work, it will also mean that the real PCB’s will also work when my friend finishes with them.

5. Next Steps

As of now there are a few issues with the double preempt system (for the interrupt tasks) I designed a while back, so I will need to debug that before they arrive. Right now before the boards arrive, I will be finishing up the RTOS blocking DMA pipelines (stripping out IRQ, removing NVIC etc. for scheduled tasks), and performing a bunch of baseline testing in order to prove that the RTOS is capable of flying an actual drone!

0
0
29
Open comments for this post

9h 38m 8s logged

1. CLI Upgrades and Channel Mapping

Over the past 5 days, I knocked out some major quality-of-life upgrades for the flight controller. I added customizable channel mapping for CRSF directly through the CLI. By utilizing a combination of references and pointers, I was able to point a volatile alias to certain channel mappings, allowing for completely customizable CRSF channels. While I was in the CLI logic, I also added the ability to configure custom IMU orientations around the z-axis, making it way easier to mount the board in different directions without having to recompile the firmware.

2. DMA Pipeline Fixes and Baro Optimization

A big chunk of this session went into debugging and refactoring the hardware data pipelines. I adjusted the ADC population pipeline in order to fix some stream parsing issues, and gave the barometer a massive overhaul. The baro now uses a non-blocking, non-circular DMA setup that relies on a semi-interrupt-driven task call rather than a continuous loop. This enabled me to yield in the middle of the function, and call back later as soon as DMA is finished transferring. I also fixed a massive bug in the DMA setup: the DMA_SxCR_HTIE and DMA_SxCR_TCIE flags were accidentally enabled for the GPS and OSD ring buffers, which would have crashed the whole RTOS by firing unhandled interrupts, causing default HAL and IRQ handlers to run.

3. Walksnail Two-Way Communication

Finally, I built out the respondToVTX task to get the drone properly communicating with the Walksnail VTX. Instead of just blindly sending video data out, this sets up true two-way communication over asynchronous UART. By writing a persistent byte-by-byte state machine, the flight controller can now safely parse incoming requests from the VTX without dropping half-packets, allowing me to send back live stick positions and armed status for the OSD menus.

The only thing left to do now is handle persistent CLI storage, the logToBlackBox task, integrating the semi-interrupt pipeline with telemetryTX and the OSD, and a few other system tweaks. After that the RTOS will be 100% ready to fly the actual drone.

0
0
44
Open comments for this post

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
0
70
Open comments for this post

9h 40m 34s logged

1. Reworking the DMA Flow for Speed

So, a lot has been going on lately, starting with a massive rework of the entire DMA flow. The functions are now entirely populated by circular DMA, which is a huge upgrade for the system’s overall performance. This architectural change allows for completely non-blocking, high-frequency calls, hitting response times as fast as 0.03 milliseconds. It basically means the whole data pipeline is significantly faster, and much more responsive than it was in previous versions.

2. Standardizing Ring Buffers and Mock Injectors

To make everything more reliable, the DMA now follows a very specific and predictable pattern for both scheduled and interrupt tasks. Everything relies on ring buffers now, whether they are being injected by actual hardware (or simulated) DMA or by my custom simulation scripts. I also reworked the mock injectors so they no longer just return a pre-formatted packet. Instead, they write directly into the ring buffer, which lets me test the exact same parsing pipeline in software, simulating the hardware flow almost exactly like Renode (if only they would ever fix that USART DMA bug…).

3. Assembly BX LR Bug + STMCubeMX Clock PLL

I also finally solved the long term BX LR return bug that I literally spent over 20 hours trying to find. Whenever tasks used the FPU or had slightly larger stacks, the return address in the r0 register would just get completely corrupted by the extra data. I fixed this by storing the return address in a dedicated uint value instead of blindly trusting the registers to hold it. That fix alone allowed me to safely re-add telemetryTX and make a bunch of other function logic much more robust. On top of that, I went into STM32CubeMX and reworked the clock PLL manually (it took over 3 hours to solve by hand). By feeding in a 25 MHz HSE clock, I managed to unlock the MCU’s internal clocks to run at their full 216 MHz.I also took some time to clean up the codebase to improve overall readability and commenting.

4. DMA Interrupts for Safe Parsing

Another feature is that DMA now triggers interrupts when the ring buffers hit exactly halfway and when they are fully populated. This gives me the time to parse all the incoming values safely without having the risk about data getting overwritten by the next DMA pass before I am done reading it. I set up the IRQ handlers in the code to call the interrupt methods automatically, which ensures the CPU stays completely unblocked and free to handle other flight control tasks almost all the time (overall decreasing headroom).

5. Current Status: NVIC Bug

Sadly, the code is actually not functional right now because the NVIC and IRQ pipeline is malfunctioning and just refusing to call the interrupts for reasons I cannot figure out yet. Because of this, I am holding off on pushing the code until I can track down the bug, give everything a final polish, and run some benchmark testing (since I don’t want to commit broken code). Also, sorry for promising a commit a day or two after my last devlog. I got super busy, especially with vacation, but I will try my best to get this sorted. Depending on how long it takes to figure out this NVIC issue, the next commit might drop anywhere from tomorrow to a few weeks from now.

0
0
27
Open comments for this post

9h 32m 1s logged

This week was heavily focused on the fine tuning and organization of the RTOS. I spent numerous hours of research and untangling environment setups. I originally tried to build out a complete framework for the drone code without relying on any simulation code, but Renode’s restrictions, specifically with its ability to perform DMA requests, quickly made it clear that path was impossible. To fix this I’m looking into picking up a dev board and a Raspberry Pi so I can test and debug everything on an actual MCU before finally moving the codebase over to the drone. Even though the git won’t look a whole lot different and not a lot of actual code changed, getting these configs finalized (especially in STM32CubeMX) in was a huge step forward for the project. I expect to commit the next version of the code either today or tomorrow, with improvements specifically in TelemetryTX, BlackBoxLogging, and a few other minor changes!

0
0
36
Open comments for this post

8h 40m 32s logged

Major Architecture Upgrade : OSD, Betaflight Rates & Build System Refactor

1. OSD, DMA + SYS

First off on the hardware side, I added native MSP-based OSD support for Walksnail video transmitters, built the SPI barometer driver for altitude tracking, and spun up a low-level health task that constantly monitors MPU registers and stack usage in the background. On top of that, I finally fixed the DMA stream conflicts (mainly in STMCubeMX) between TIM1 DShot motor outputs , ADC1, and SPI peripherals, so everything runs without collisions now.

2. SYS Health + Debugging

I also implemented a few other low level tasks, allowing me to monitor system health, based upon ram usage and storage used. This task prints out the amount at which each tasks stack is being used, allowing me to effectively debug whether a task was actually overflowing, vs an unrestricted read/write. Furthermore, the original canary values that I used, were completely replaced as they in fact were causing some of the “stack overflow” bugs (via unrestricted reads/write every 1 ms in SysTick_Handler).

3. Betaflight Rates + CLI Update

For flight feel, I implemented Betaflight’s Actual Rates algorithm with custom expo curves, so stick inputs map to target angular velocities with center-stick precision. I also completely separated serial communications by moving the interactive CLI to USART1 (accessible via nc -C localhost 1234 in terminal) and pushing system logs to USART3. Additionally, I removed the old telemetryTX task because its unauthorized reads/writes were completely breaking the RTOS scheduler, and expanded the CLI with a ton of commands for live sensor readings and more PID tuning. Speaking of which, I am planning to create a persistent file in /bin/, allowing for rates to be stored between runs, rather than being reset each during runtime, and having to be manually configured through the CLI.

4. Overhauled Building Workflow

Finally, I overhauled the whole developer workflow and build automation setup. The build.sh and build.bat scripts now auto-configure custom CMake profiles and generate a full disassembly file in /bin/ after every build for fast low-level debugging, while run.sh and run.bat automatically launch the right terminal windows on launch.

5. Future Additions:

I’m planning to build a closed-loop Renode simulation matrix to stream hardware data into every peripheral externally, letting me run 100% real production code during SIL testing! For this, I will utilize the currently blank simulate.py as well as certain commands in simulate.resc to create properly formatted dynamic buffers(which are not directly injected into the code), allowing me to properly test the DMA drivers, and many of the memory management helpers.

0
0
39
Open comments for this post

9h 26m 24s logged

Over the last few days, I wrapped up two major tasks for the flight controller’s core sensor and telemetry stack: the on-screen display (OSD) renderer and the BMP390 barometer subsystem. The OSD engine is now fully hooked up to the state system, giving real-time visual downlinks for critical metrics like flight battery voltage, active flight modes, and height statistics over UART. To keep the execution completely non-blocking, all frame updates are dispatched asynchronously, preventing rendering delays from hindering the performance of the high-frequency control loops.

On the barometer side, I implemented altitude tracking using Bosch’s BMP390 sensor over SPI with non-blocking DMA burst transfers. In the 50Hz task loop, incoming 24-bit raw pressure and temperature bytes are assembled and passed through the hypsometric formula to calculate smooth, relative height above ground level (AGL).

Although the barometer task is a bit flawed due to floating point register stack errors with the header and end canary values, they don’t seem to be affecting the system in anyway (nor is any data being corrupted). I will find a way to fix these issues before the next commit, as well as the race condition in the interrupt tasks in the next Github commit. After that, it is just system polish and extensive testing before the RTOS will be complete!

0
0
10
Open comments for this post

7h 10m 51s logged

1. Fixing the Stack Overflows at Boot

After an entire 25-hour debugging period across 4 days, the RTOS kernel is finally done (for the scheduled tasks). I tracked down a silent bug where several critical scheduled tasks—like powerManagement, lowLevelFailSafe, and telemetryTX—were crashing the MCU on boot. The issue came down to a boot-time dependency lock: tasks were trying to poll hardware registers (like UART TX flags) and read shared drone state variables before the peripherals or producer tasks had time to initialize on Tick 0. By adding an initial yieldCurrentTask() right at the top of these methods, Task Phase Alignment was officially added. This gives the hardware a full cycle to stabilize, preventing buffer overflows and eliminating the cascading task state corruptions that were locking up the scheduler.

2. Custom Stack Sizing & Real-Time Guard Checks

Alongside the scheduler fix, I pushed major upgrades to memory safety and task management. Instead of assigning a static stack array to every task, I transitioned to dynamic per-task stack allocations during initialization (new (std::nothrow)). Heavy string-parsing and math tasks get larger stacks, while simple toggle tasks stay lightweight. To catch memory issues before they corrupt the heap, I implemented a custom dual guarding system: 0xDEADBEEF is appended at the bottom of the stack to catch standard stack overflows, while a new 0xBEEFCAFE canary value is written to the very top index (stackSizeWords) to instantly flag memory breaches in the SysTick_Handler. Furthermore, I refactored powerManagement to use ADC2 in Circular DMA Mode, writing battery telemetry directly to RAM in the background for zero CPU overhead.

3. Transitioning to Walksnail Avatar HD

Looking ahead, my and my friend are officially transitioning our existing analog video setup over to a fully integrated Walksnail Avatar HD digital ecosystem. Rather than keeping my custom telemetry task (telemetryTX) with heavy string formatting over raw UART, Walksnail allows me to stream standard MSP (MultiWii Serial Protocol) DisplayPort frames straight to the VTX. The Walksnail system will serve as our primary onboard logging source, giving us full OSD telemetry, high-framerate HD video, and precise blackbox data logging. With the RTOS core now stable and non-blocking, our next focus shifts toward “premium” flight controller features, e.g. including custom RC stick expo curves, an expanded USB CLI, and an integrated simulator interface.

0
0
15
Open comments for this post

9h 4m 46s logged

I just finished expanding the RTOS architecture, filling out all of the scheduled background tasks to handle real-time flight telemetry, peripheral hardware, and terminal configuration. On the telemetry side, I built out telemetryTX to package battery, status, and GPS data into standard CRSF frames over UART, alongside an NMEA gpsParser that decodes $GNGGA and $GNRMC sentences for live location fixes. I also made powerManagement into its own standalone task so ADC voltage and current metrics can feed into the failsafe logic and telemetry concurrently. To make bench testing and tuning significantly easier, I added usbCLI over USART1, giving me a full interactive shell to tweak PID gains (Kp, Ki, Kd), dump raw IMU data, and inspect hardware states on the fly without needing to reflash firmware.

To keep the physical quad safe and responsive during flight, I added updatePeripherals for LED status patterns and buzzer warnings, along with a dedicated Independent Watchdog (IWDG) task running as a low-priority fail-safe to trigger hardware MCU resets if the scheduler ever freezes. On the project structure side, CMake now routes all output binaries straight into a clean ./bin/ directory so nobody has to compile the source manually just to run the .elf target in Renode. I also introduced a -DENABLE_DEBUG flag, allowing me to cleanly toggle between complete USART log prints across every loop or a blank terminal environment meant purely for the CLI shell.

While all the background subsystems and scheduled task priorities are fully mapped out, I’m currently figuring out a few lingering RTOS bugs—specifically around stack allocation, causing stack overflow flags on certain scheduled tasks, along with scheduling starvation where context switching isn’t releasing task blocks properly. Because I want to ensure the scheduler kernel is 100% complete before pushing broken stack frames to the main branch, the GitHub repository will be updated with the complete, bug-fixed code later today. Everything is moving in the right direction, and getting these background tasks finalized brings me one step closer to full flight hardware deployment!

0
0
7
Open comments for this post

2h 30m 50s logged

I’ve spent the last 20 hours of coding basically staring at bare-metal memory allocations and tracking down why the drone’s context switcher was triggering CPU drops under the Renode simulation environment. Moving away from manual Python injection scripts to a cleaner C++ conditional compilation framework using CMake was supposed to simplify the testing layout, but instead, it exposed structural design flaws in how the scheduler kernel boots up. Running a multi-sample sensorCalibration() routine at cold boot was forcing hidden task yields before the OS kernel was fully mapped out, which was throwing unmapped register address errors and wiping out index-0 entries.

To solve this, I decoupled the entire state machine sequence so that hardware allocations, peripheral registers, and core tracking counters now clear before any operational loops drop into the registry. I also added a critical rtosStarted gatekeeper flag to yieldCurrentTask() so calibration math runs cleanly in sequence without triggering early PendSV handler switches. On top of that, the task stack pointers are completely realigned. The assembly context swapper now maps a precise 32-word boundary allocation utilizing 0xFFFFFFFD as a basic exception return code, which completely eliminates the data corruption that was misaligning our Process Stack Pointer.

With the fundamental kernel stabilized, I shifted entirely into clean architecture organization. The massive, cluttered code blocks have been split up into independent tracking files (BufferPopulation.cpp, Tasks.cpp, and SimInjector.cpp). High-speed SPI reads for the IMU, UART ring buffers for decoding CRSF radio channel frames, battery ADC sampling, and DShot600 motor output mixers are now fully broken out and functional. There is still a nagging preemption lockout issue inside the simulator where a single interrupt task attempts to starve out the other concurrent loops during a millisecond window, but the baseline operating system is locked in, meaning I can confidently verify the PID flight mixing tables and safety code blocks safely before the code ever touches a physical quadcopter.

If you would like to run your own tasks, I also added support in the .bat and .sh files to build code for the simulation (flag=sim), user task code (flag=user), and actual drone upload code (no flags).

Example MacOS Commands:
./build.sh sim
./build.sh user
./build.sh

This makes it so that I don’t have to constantly modify code to run either the user tasks or simulation code, instead using a flag. Overall, this RTOS is not done yet, however, I plan to add the rest of the scheduled tasks later today, and fixing the sole bug keeping this simulation back!

I expect a full ship to be comming out in the next week or so, along with all ESC, RM, and TM code complete!

0
0
11
Open comments for this post

9h 14m 46s logged

I’ve been doing a lot of debugging for the RTOS scheduler inside the simulation environment, and it’s been very difficult for me to identify many bugs in teh core architecture of my tasks. Moving away from Python scripts to a cleaner C++ conditional compilation framework using CMake was supposed to simplify things, but instead, it exposed some nasty architectural bugs in how tasks and context switching interact. There has also been a massive priority inversion and preemption lockup where the CRSF parsing loop completely starves out the lower-priority threads. Also, running a raw sensor calibration loop at boot was forcing thread switches before the OS kernel was even fully initialized, completely corrupting task indexes, throwing null pointer warnings, and leaving half of my PID calculations at zero. Still working on many fixes, but it has been a lot of work (9 hours), but I hope to get the full RTOS with at least bare minimum flight tasks out by tomorrow! The GitHub repo still won’t be fully up-to-date until these structural bugs are figured out, but I plan to push the corrected scheduler files and mock drivers very soon.

The screenshot below shows an image of the CRSF task completely dominating, without any processes interrupting it.

0
0
11
Open comments for this post

9h 35m 28s logged

I just wrapped up the core architecture of the drone’s flight controller, including mapping out all 7 interrupt methods into a far more optimized RTOS scheduler. The updated version makes use of a FIFO stack, to keep track of nested interrupts. I also protected the main code from data corruption during pendSV switches by enabling and disabling irq in certain areas of vulnerable code. Other small changes to the assembly to save floating point registers as well as better memory allocation was also done in this update.

From high-speed IMU sensor integration and state estimation calculations to CRSF packet decoding and DShot motor generation, the essential tasks are fully written and compiled. I’ve officially shifted gears into the testing phase, working on simulated environments to debug DMA transfers, registers, and the flight state machine’s arming sequences. As of now, I am writing python scripts by hand, in order to inject “dummy” values into the GPIO, SPI, and USART registers in order to populate the buffers, and simulate the calculations.

As the code is becoming longer and longer, I will try to refactor a lot of the files into smaller more organized portions, to increase readability (often I have a hard time traversing everything).

Note: The current code pushed to GitHub isn’t quite the latest since I’m in the middle of troubleshooting a few simulation-specific register bugs, but I wanted to get this update out to meet the 10-hour devlog requirement on Stardance. Everything is moving in the right direction, and having a virtual environment all working means I can safely verify the PID mixer and safety flags before this code ever touches a physical quad. This officially marks the end of both phases 2 and 3, and now its a matter of finishing extra scheduled telemetry tasks (e.g. OSD and EdgeTX logging), but I estimate that the flight controller portion of the code (around 60% of all logic in the drone) will be done between now and Sunday, July 19th!

I will also post a much more in depth devlog about how the methods work, as well as an updated README.md on Github ASAP.

0
0
8
Ship

Phase 1 (The Custom RTOS Kernel)

I am building a custom FPV drone from scratch with my friend in an attempt to achieve 200 mph. At those speeds, standard super loops are far too slow, as a single microsecond of lag means the drone travels feet before making a PID correction. To solve this, I built a custom, hybrid cooperative/preemptive Real-Time Operating System (RTOS) from scratch for the ARM Cortex-M7 (STM32F7)!

What Was Challenging?

The absolute hardest part of this phase was writing the low-level assembly context switcher (PendSV_Handler.S). Hacking the ARM Cortex-M7 registers to physically hot-swap stack pointers (PSP) mid-execution without crashing the CPU or corrupting the program state was a massive headache.

I had to ensure that when an asynchronous preemption event fires, the assembly code perfectly preserves the execution registers (R4-R11 plus the hardware-stacked registers), jumps to the high-priority task, and then seamlessly restores the background task right where it left off. Getting the compiler-enforced task overloading working so the kernel cleanly separates periodic tasks from raw interrupts was another huge hurdle, but it makes the whole codebase infinitely more robust.

Want to Test It or Write Your Own Tasks?

You don’t need physical hardware to watch this run! I’ve packaged the entire project with a Renode simulation script. You can run the interactive emulator with a single command to see the preemption scheduler hijack our counter task live, or you can jump into the user sandbox file and write your own custom tasks.

Check out the GitHub Repository to Run the Demo!

The README has a quick 5-minute setup guide to get you compiling and simulating your own code in no time.

  • 5 devlogs
  • 15h
  • 16.44x multiplier
  • 243 Stardust
Try project → See source code →
Open comments for this post

1h 27m 37s logged

To make my hybrid RTOS accessible and easy to test, I designed a modular sandbox task registry system that isolates the core kernel from custom user code. Developers can write standard non-returning tasks in an isolated file (User_Tasks/user_tasks.cpp), while the kernel exposes a registration hook that allocates a dedicated 1024-word stack space. This decoupling means anyone can plug in their own cooperative or preemptive tasks, set execution periods or trigger bounds, and run them instantly on our virtual STM32F7 without risking memory corruption or touching the underlying context-switching assembly. For more information on how to make your own tasks refer to the README.md.

0
0
10
Open comments for this post

1h 5m 32s logged

I just updated a few build files and restructured the project, so code and simulation separation would be better. I also added a few executable files (build.sh, run.bat, and run.sh) to easily compile and run the project.

0
0
7
Loading more…

Followers

Loading…