From Blink to FreeRTOS on the SG2000 Little Core — Reference Notes
2026-11-04
Motivation
Mechanisms
Platform & Practice
0x9fe0_0000.setup() once, loop() forever; delay() is a busy-wait.pinMode/digitalWrite hide pinmux and registers.| Question | OS service |
|---|---|
| Do two things at once | tasks + scheduler |
| Meet a deadline without spinning | timers, blocking delays, tick |
| Run the important thing first | priority + preemption |
| Share data safely | critical sections, mutexes, queues |
| Write portable code | drivers, HAL, standard APIs |
| Cooperate with Linux | message queues / IPC |
An OS is a bundle of reusable answers to coordination problems.
| Requirement | Little C906L |
|---|---|
| MMU / page tables | none (M-mode, physical) |
| Privilege separation | none configured |
| Memory | 2 MB carveout |
| Cache | none |
| SMP | nproc = 1 |
Linux assumes an MMU and ample RAM. The only OS that fits is a real-time kernel: FreeRTOS.
main_cvirtos(); CMDQU task at priority 5.delay(ms) — spins; CPU consumed; nothing else runs.vTaskDelay(ticks) — blocks; CPU released; scheduler runs others.vTaskDelayUntil(&last, period) — fixed-period task, drift-free.
delay()consumes time;vTaskDelay()surrenders it.
mtvec = trap handler (set before scheduler start).mie.MTIE enables the timer interrupt.mtimecmp is two 32-bit halves, low word written last; if mtime is not memory-mapped, read the time CSR.configMTIME_BASE_ADDRESS, configMTIMECMP_BASE_ADDRESS, portasmHAS_MTIME (not the old SiFive CLINT constants).mepc (resume PC) + mstatus → task stack/TCB.mepc, mstatus → mret.pxPortInitialiseStack() builds a synthetic frame so task start looks like a context restore; xPortStartFirstTask() restores it and mrets.One real PC, many software contexts — the OS swaps register state.
| Primitive | Use |
|---|---|
| Queue | move data + signal a waiter |
| Binary / counting semaphore | signal events / count resources |
| Mutex | mutual exclusion + priority inheritance |
| Task notification | fast per-task signal |
| Stream/message buffer | byte/record stream task↔︎ISR |
...FromISR() variants and defer work to a task.heap_4 is the usual allocator here.uxTaskGetStackHighWaterMark().Concurrency is not free on a memory-starved core.
freertos/cvitek/ — CMake/Ninja → cvirtos.bin.0x9fe0_0000 — the same carveout as the Arduino sketches.main_cvirtos() → create tasks → vTaskStartScheduler().riscv64-unknown-elf- (bare-metal, --specs=nosys.specs).rtos_cmdqu = command queue over RPMsg.| Rung | Mechanism | Gains | Costs |
|---|---|---|---|
| 1 | delay() superloop |
simplicity | blocks; no concurrency |
| 2 | millis() state machine |
concurrency | cooperative; jitter; manual state |
| 3 | timer ISR | precision | short-ISR rule; races |
| 4 | FreeRTOS tasks | priority, blocking, structure | stacks; complexity |
Capability ↑, complexity ↑, footprint ↑.
Lab: Blink → BlinkMillis → timer ISR → FreeRTOS tasks → RPMsg command.
Discuss:
CS 4250 · Why an OS? — Reference Notes