Skip to content

Latest commit

 

History

493 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TamgaOS (Yula)

Bare-metal RTOS written in C and ARM assembly.
Started as a learning project — now focused on deterministic scheduling, memory protection, and debuggability.

TamgaOS Ethernet
*STM32H753ZI ethernet test

TamgaOS CAN
STM32H753ZI (FDCAN) ↔ FRDM-K64F (FlexCAN) — real two-node bus, SN65HVD230 transceivers, verified ACK on both sides

arm compiler version:

arm-none-eabi-gcc -v
..
..
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 15.3.1 20260627 (Arm GNU Toolchain 15.3.Rel1 (Build arm-15.149)) 

Kernel core (shared across all ARM boards)

Kernel

  • Preemptive scheduler, PendSV context switch, PSP per-task isolation
  • FPU context switching (lazy stacking via EXC_RETURN; FPU variant differs per board — see below)

Sync

  • Mutex (LDREX/STREX) with priority inheritance — elevates the lock owner's priority to match a higher-priority blocked waiter, preventing priority inversion; restores original priority on unlock
  • Semaphore with priority-based wait queues
  • Queue — fixed-size circular buffer, priority-ordered wait list on both the send side and the receive side (a task blocked on a full/empty queue is served in priority order, not FIFO order)
  • Event flags (event groups) — bitmask-based EVENT_WAIT_ANY / EVENT_WAIT_ALL waiting with optional auto-clear on wake
  • Timeout support across the board — mutex_lock_timeout, queue_send_timeout, queue_receive_timeout, event_wait_timeout all bound their wait on an absolute deadline (via systick_get_ms()), so a task that wakes and loses a race re-arms only the remaining budget instead of the full timeout again
  • ISR-Safe primitives — queue_send_from_isr/queue_receive_from_isr/event_set_from_isr/event_clear_from_isr, verified from a real interrupt context (not simulated), including a deliberate flood test where 977/1000 ISR-context sends correctly returned an immediate error instead of ever hanging the interrupt
  • Critical section (cpsid/cpsie)
  • Software Timer — fixed-size static pool (no dynamic allocation) of one-shot and auto-reload timers
  • Tickless Idle — Already not checking every 1ms tick. Verified on both boards with zero measured drift: a task requesting sched_delay_ms(500) woke up at exactly 500ms elapsed.
  • Deadline/Response Time Monitor — per-task execution-time tracking (min/avg/max, overrun count vs. budget), independent of scheduler internals — wraps any periodic task's work with begin()/end() timing calls. The kind of timing evidence a DO-178C-style certification process would expect — currently measures response time (wall-clock, including preemption), not WCET. (note: STM32H753ZI's SysTick is clocked from AHB/8, not the core clock directly )
  • Task Notification the fastest signal path in the kernel tested on m4 and m7
  • Stack High-Water Mark — M4 and M7 showed consistent ~56-57 bytes per call level, and the deep task measured identically at 900 bytes on both STM32 and K64F.
  • Jitter Monitor — measures how consistently a periodic task wakes up relative to its expected schedule, independent of per-cycle execution time (that's what Deadline Monitor tracks). tested with 2 task one regular one irregular regular one was everything 2ms anoyher was changing as expected.

Actuators (shared abstraction, board-specific PWM backend)

Servo/ESC

  • actuators/servo.c — generic hobby servo/ESC abstraction over each board's PWM driver. Maps a 0-180 degree angle (or raw microsecond pulse, for ESC throttle) to the standard 1000-2000us hobby servo/ESC convention (1000=0deg/min-throttle, 1500=90deg/center, 2000=180deg/max-throttle)
  • Continuous 0↔180 sweep helper (servo_sweep_step()), independent per channel — verified on both boards with a logic analyzer showing a clean, correctly-timed 20ms/50Hz signal

ARM port — STM32H753ZI (Cortex-M7)

TamgaOS STM32H7

Boot

  • Hand-written startup with I-Cache/D-Cache invalidate + enable sequence (PM0253)
  • Linker script (DTCM, AXI, SRAM1-4, BKPSRAM)
  • RCC init — HSI 64MHz and HSE/PLL1 480MHz

Kernel

  • Preemptive scheduler (Cortex-M7 port)
  • PendSV context switch
  • PSP per-task isolation
  • FPU context switching (fpv5-d16, double-precision, lazy stacking via EXC_RETURN)
  • MPU-based stack overflow guard (hardware-enforced, MemManage on violation — verified end-to-end: a real stack overflow inside a running task trips the guard and lands in the fault handler with the correct MMFAR)

Fault handling & safety

  • Unified fault handler for HardFault, MemManage, BusFault, and UsageFault — all four vectors route through one handler that dumps PC/LR/xPSR/R0-R3/R12/SP, decodes every CFSR bit (DIVBYZERO, DACCVIOL, IACCVIOL, STKERR, IBUSERR, etc.), and reports MMFAR/BFAR when valid
  • Crash record persisted to Backup SRAM (survives a normal reset, not just visible over UART at the moment of the crash) and automatically re-reported on the next boot, then cleared — both the write and the clear are D-cache–flushed explicitly, since a plain cache invalidate-without-clean on the next boot would otherwise silently discard an unflushed write (or an unflushed clear, causing the same crash to be re-reported forever)
  • Independent Watchdog (IWDG) — verified to actually reset the board when a task stops kicking it, with the reset cause (IWDGRSTF in RCC_RSR) correctly distinguished from a normal reset on the next boot
  • FAULT_HANDLER_DEBUG_HALT build flag: defaults to 0 (production — logs then hangs safely for inspection; pair with IWDG for auto-recovery), set to 1 only on an explicit debug build to halt instead, for live GDB inspection

Sync

  • Mutex (LDREX/STREX) with priority inheritance — elevates the lock owner's priority to match a higher-priority blocked waiter, preventing priority inversion; restores original priority on unlock
  • Semaphore with priority-based wait queues
  • Queue — priority-ordered wait on send and receive, with a bounded (_timeout) variant
  • Event flags — ANY/ALL/auto_clear, with a bounded (_timeout) variant
  • Critical section (cpsid/cpsie)
  • Software Timer + Tickless Idle — same shared kernel implementation, verified independently on this board

Drivers

  • SysTick (1ms tick, AHB/8) — also the shared kernel's monotonic time source (systick_get_ms()) used by every _timeout primitive above
  • RCC (HSI 64MHz and HSE/PLL1 480MHz)
  • UART (USART3, 115200, Virtual COM via ST-Link)
  • I2C (I2C1 PB8/PB9 AF4)
  • FDCAN1 (PB8=RX (AF9), PB9=TX (AF9) — PA11/PA12 they're tied to USB OTG FS) — verified on a real two-node bus: internal loopback passes, and with an SN65HVD230 transceiver on each board, STM32 and K64F exchange frames continuously with zero ACK failures on either side, plus an ARINC825-inspired application-layer message format on top (categorized CAN ID ranges by criticality, a standard node/sequence/data-type header, and a periodic per-node heartbeat so a lost link is detected as a health event rather than inferred from a missing specific message type)
  • IWDG (Independent Watchdog, LSI-clocked, software-configurable timeout)
  • PWM (TIM2_CH1, PA0 / Arduino D32, 50Hz/1-2ms hobby servo & ESC convention) — verified with a logic analyzer: clean 20.0ms period, pulse width tracking the requested 1000-2000us value exactly.
  • ADC1 (PA3 / Arduino A0, ADC1_INP15, single-conversion polling mode, 16-bit resolution) — verified end-to-end
  • Ethernet MAC+DMA (RMII, register-level, no HAL) — PHY link detection via MDIO (LAN8742, PHY ID confirmed), TX and RX both fully verified end-to-end on real hardware: TX payload confirmed byte-for-byte in Wireshark, RX confirmed with 50/50 zero-loss unicast test frames sent from a PC. Went through seven distinct root causes during bring-up (DTCM being DMA-unreachable, tail-pointer semantics, D-Cache coherency + MPU memory-type selection, linker alignment, unset RX buffer size, unset MTL store-and-forward, unset MAC address filter) — full writeup at https://auctra.app

Sensors

  • MPU6050 (I2C, accel/gyro, feeds the shared Kalman filter)
  • BMP280 (I2C, address 0x76 — misidentified as a BMP180 for most of a debugging session before a bus scan caught it) — pressure/temperature/zero-referenced relative altitude

Tests

  • tests/stm/test_mutex_priority_inheritance.c — validates elevate/restore behavior under LOW/HIGH/MED priority contention
  • tests/stm/test_queue_priority_order.c — validates priority-ordered wake on both the send side and the receive side of a queue
  • tests/stm/test_event_flags.c — validates ANY/ALL wake, auto_clear, and both timeout-expiry/timeout-success paths
  • tests/stm/test_timeout.c — validates mutex_lock_timeout/queue_send_timeout/queue_receive_timeout against both an expiring wait and a wait satisfied just before the deadline
  • tests/stm/imu_kalman_fdcan.c — imu kalman fdcan test
  • tests/stm/test_fdcan1.c — internal loopback: send/receive round-trip, byte-for-byte
  • tests/stm/test_fdcan_real_bus.c — real-bus test (loopback disabled): periodic TX + continuous RX polling, paired with the matching K64F test below
  • tests/stm/test_fault_handler.c — deliberately triggers UsageFault/BusFault/MemManage/HardFault and verifies UART dump + Backup SRAM persistence across a real reset
  • tests/stm/test_mpu_stack_guard.c — runs a real scheduled task to destruction to verify the MPU stack-overflow guard fires with the correct faulting address
  • tests/stm/test_iwdg.c — arms IWDG, proves kicking prevents reset, then deliberately starves it to confirm the board actually resets and the cause is correctly reported
  • tests/stm/pwm_test.c — sweeps TIM2_CH1 pulse width 1000-2000us continuously, printing each step over UART
  • tests/stm/servo_sweep_test.c — continuous 0-180 degree servo sweep via the actuators/servo.c abstraction
  • tests/stm/adc_test.c — continuously reads PA3/ADC1_INP15 and prints the value :)
  • tests/stm/test_software_timer.c — validates one-shot and auto-reload software timers: exact fire counts, timer_stop() correctly halting an active periodic timer, and pool-exhaustion behavior
  • tests/test_tickless_idle.c — verifies a task delayed via sched_delay_ms(500) wakes up at exactly 500ms elapsed with zero measured drift over dozens of cycles
  • tests/stm/test_deadline_monitor.c — simulated periodic task with a 10ms max time, 30 cycles, 4 deliberately-heavy cycles to verify overrun detection
  • tests/stm/test_task_notify.c — validates give-then-wait, timeout, wait-then-give, and ISR-context notification scenarios
  • tests/stm/test_stack_monitor.c — stack monitor test for stm
  • tests/stm/test_eth_link_monitor.c — PHY link up/down + speed/duplex, real cable plug/unplug
  • tests/stm/test_eth_tx_only.c — continuous TX, confirmed byte-for-byte in Wireshark on a connected PC
  • tests/stm/test_eth_rx_only.c — real unicast RX, 50/50 test frames received with zero loss
  • tests/stm/test_adc_op.c — adc and conected opamp test file use volatge divider for this test x8 using

Still improving — development notes at https://auctra.app


ARM port — K64F (Cortex-M4)

TamgaOS K64F

Boot

  • Hand-written startup
  • Linker script
  • MCG clock init (120MHz)

Kernel

  • Preemptive scheduler
  • PendSV context switch
  • PSP per-task isolation
  • FPU context switching (fpv4-sp-d16, single-precision, lazy stacking via EXC_RETURN)
  • Stack overflow guard: software canary only (K64F MPU registers use a non-standard memory layout vs. the STM32H753ZI port; hardware guard planned for a future update)
  • Tick source is selectable at build time: native ARM SysTick by default (Cortex-M4 shares the same 0xE000E010 SysTick block as the STM32H753ZI port's Cortex-M7 — identical technique), or TICK_SOURCE=pit to drive the scheduler tick from the PIT peripheral instead. Either way, board-agnostic kernel code always sees the same systick_init()/systick_get_ms().

Sync

  • Mutex (LDREX/STREX) with priority inheritance — elevates the lock owner's priority to match a higher-priority blocked waiter, preventing priority inversion; restores original priority on unlock
  • Semaphore with priority-based wait queues
  • Queue — priority-ordered wait on send and receive, with a bounded (_timeout) variant
  • Event flags — ANY/ALL/auto_clear, with a bounded (_timeout) variant
  • Critical section (cpsid/cpsie)
  • Software Timer + Tickless Idle — same shared kernel implementation, verified independently on this board

Drivers

  • SysTick (default tick source — see Kernel above) or PIT (32-bit, opt-in via TICK_SOURCE=pit)
  • UART
  • MCG
  • FlexCAN0 (PTB18=TX (ALT2), PTB19=RX (ALT2)) — verified on a real two-node bus: internal loopback passes (5 sequential frames, no drops), and with an SN65HVD230 transceiver, K64F and STM32 exchange frames continuously with zero ACK failures on either side.
  • PWM (FTM0 + FTM3, 4 channels: PTC1=FTM0_CH0/ALT4, PTC5=FTM0_CH2/ALT7, PTC8=FTM3_CH4/ALT3, PTC9=FTM3_CH5/ALT3 — 50Hz/1-2ms hobby servo & ESC convention) — verified on all 4 channels with a logic analyzer.
  • ADC0 (PTB2 / Arduino A0, ADC0_SE12, 12-bit) — full-range sweep verified with a potentiometer

Tests

  • tests/k64f/test_queue_priority_order.c — K64F port of the queue priority-order test
  • tests/k64f/test_event_flags.c — K64F port of the event-flags test (all 5 scenarios)
  • tests/k64f/test_flexcan_loopback.c — internal loopback: single frame + 5 sequential frames, plus a non-blocking rx_pending() check
  • tests/k64f/test_flexcan_real_bus.c — real-bus test (loopback disabled), paired with the STM32 test above
  • tests/k64f/pwm_test.c — sweeps FTM0_CH0 (PTC1, Motor 1) pulse width 1000-2000us, printing each step over UART
  • tests/k64f/servo_sweep_test.c — continuous 0↔180 degree sweep on all 4 channels simultaneously (PTC1/PTC5/PTC8/PTC9), each channel tracking independent sweep state
  • tests/k64f/test_software_timer.c — K64F port of the software timer test (same three scenarios as the STM32 version)
  • tests/k64f/test_tickless_idle.c — K64F port of the tickless idle drift test
  • tests/k64f/test_deadline_monitor.c — simulated periodic task with a 10ms max time, 30 cycles, 4 deliberately-heavy cycles to verify overrun detection
  • tests/k64f/test_task_notify.c — K64F port of the task notification test, same four scenarios
  • tests/k64f/test_stack_monitor.c — stack monitor test for k64f

x86 port — Zig & C kernel

Experimental dual implementation for comparing low-level Zig vs C. Boots via Limine with a Multiboot2 header. Active development paused on x86 PORT — focus is on ARM.

Implemented

  • GDT setup
  • Serial monitor
  • ISO via xorriso
  • Zig + C comparison

Boot output

Zig -> TamgaOS
       KERNEL OK
C   -> TamgaOS __C__
       GDT OK __C__
       Kernel OK __C__

TamgaOS x86


Build

ARM — K64F

make clean
make BOARD=k64f
make flash BOARD=k64f

Use the PIT-based tick source instead of native SysTick:

make clean BOARD=k64f
make BOARD=k64f TICK_SOURCE=pit
make flash BOARD=k64f

ARM — STM32H753ZI

make clean
make BOARD=stm32h753zi
make flash BOARD=stm32h753zi

Debug build (halts on fault instead of resetting, for GDB inspection):

make clean BOARD=stm32h753zi
make BOARD=stm32h753zi DEBUG=1
make flash BOARD=stm32h753zi

x86 — Zig

zig build -Doptimize=ReleaseFast
.\mkiso.ps1
qemu-system-i386 -cdrom .\TamgaOS.iso -boot d -serial stdio

x86 — C

.\mkiso_c.ps1
qemu-system-i386 -cdrom .\TamgaOS_C.iso -boot d -serial stdio

Ethernet deep dive

Full writeup of the seven-bug DMA/cache/MAC-filtering behind the Ethernet driver above (DTCM being DMA-unreachable, tail-pointer semantics, D-Cache coherency + MPU memory-type selection, linker alignment, unset RX buffer size, unset MTL store-and-forward, unset MAC address filter): https://auctra.app (part 10 and prt 11)

Related projects

  • tamga-koruk a secure bootloader for the STM32H753ZI port, built as an independent project rather than folded into this repo. Verifies firmware integrity/authenticity (HMAC-SHA256, hardware-accelerated via STM32H753ZI's HASH peripheral) before jumping to the application image, with planned rollback protection and A/B partition support. Very early — currently just the basic toolchain (RCC, SysTick, UART) confirmed working on real hardware (ı put simple blink code on main...)

  • tamga-yanki — an acoustic modem transmitter built on TamgaOS's RTOS (real scheduler tasks + queue), sending digital data as an audible tone via OOK modulation.


Not a production OS. Development log: https://auctra.app

About

Experimental BareMetal RTOS written in Zig and C and Arm Assembly

Topics

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages