Playground · research instrument
On-device AIRTOS Scheduling Simulator
Preemptive RMS vs EDF playground: edit tasks (T,C,D,mutex), watch Gantt of Running/Ready/Blocked states, priority inheritance chains, ISR nesting, and live schedulability analysis. Tension audio (220Hz vs 231Hz beating) rises with deadline miss ratio.
Independent research instrument — not claimed as MakerPortal shipped product code. Methods, equations, assumptions, and limitations are disclosed so you can inspect what the page does and does not establish.
Tasks — T period, C WCET, D deadline
Auto prio RMS: shorter T = higher. Manual 1..99 (99 high) overrides. Mutex M1/M2/M3 shows blocking. cs 20% offset 40% dur default.
Blocking chains
Gantt timeline — preemptive + inheritance
t=0 msHow to read: Each row is a task timeline. Segment colors: solid Running (task color), amber Ready waiting for CPU, red Blocked on mutex (label e.g., M1 held by T2). Top darker row is global running. Vertical dashed lines are per-job deadlines. Red markers are misses. Priority inheritance bubbles pulse when low-prio task inherits high priority. ISR row (purple) preempts always.
Anatomy of an RTOS
TCB, ready queue, scheduler, mutex
- TCB (Task Control Block): id, T period, C WCET, D relative deadline, basePrio, mutex M, csStart/Dur, stack pointer, holding set, blockedOn, effective prio/deadline.
- Ready queue: intrusively linked list sorted by effective priority (RMS) or effective deadline (EDF). O(n) insert, O(1) pop top. Tick interrupt 1ms moves released jobs from sleep list to ready.
- Scheduler: preemptive: if top ready effPrio > running effPrio → context switch. ISR entry pushes context, raises BASEPRI mask.
- Mutex & PI: each mutex has holder + FIFO/prio queue. On block, holder.effPrio = max(holder.effPrio, waiter.effPrio). Chain propagates via fixpoint. Release wakes highest waiter.
- ISR nesting: ISR job taskId -1, prio 1e9, T_isr period, C_isr ~2ms. If new ISR arrives while one runs, queues (same priority FIFO) showing nested purple burst.
Effective priority inheritance
Fixpoint iteration propagates along blocking chains, bounding blocking to one CS under PCP.
Visual-audio coupling
- Gantt per-task rows: 36px high, 1ms = 3-5px depending on horizon. Running = solid task color, Ready = amber @45% alpha, Blocked = red stripe.
- Mutex hold indicator: lock glyph 🔒M1 drawn on Running segment where CS active, with small duration bar.
- Deadline dashed vertical per job per task; miss renders red X and tints row background pink transient.
- Blocking chain list: each block event logs waiter → holder (M). Canvas draws bezier arrow between rows for current cursor time if blocking active.
- Audio: two oscillators f1=220Hz, f2=330−m·99Hz (m=miss ratio). Beating f_beat=|f1−f2|. At m=1 →110Hz→11Hz roughness → perceptual tension. Gain 0.02+m·0.14.
Beating roughness
m ∈ [0,1] miss ratio. 11Hz beating at m=1 sits in maximal dissonance ~15Hz per Helmholtz.
Equations & schedulability
Utilization
Necessary condition for any scheduler: U ≤1 (EDF optimal). RMS needs stricter bound.
Response Time Analysis
Iterate R_i(k+1) until convergence. If R_i ≤ D_i for all i, schedulable. B_i is worst blocking from lower priority tasks.
EDF & SRP ceiling
Stack Resource Policy: preemption level π_i, system ceiling Π = max locked resource ceiling. Job blocks if π_i ≤ Π.
Worst blocking chain example
T_high(5ms/1ms) needs M1 held by T_low(100ms/20ms, CS 8ms). Without PI, T_med(20ms/5ms) preempts T_low for 60ms → H blocks 68ms → miss. With PI, T_low inherits H prio → blocks only 8ms. Simulator shows red blocked segment length difference.
ISR nesting
ISR period T_isr with WCET C_isr. Utilization U_isr = C_isr/T_isr adds to total U. If T_isr < C_isr, instances queue (nest depth >1). Best practice: ISR defers to task via semaphore, keep C_isr ≤ 0.2·T_task_min.
Gear behind this build
RTOS stack · 14 picks
Hardware picks14
$15.00KitESP32-S3-DevKitC-1-N8R8 Development Board
Official Espressif S3 devkit with 8MB flash + 8MB PSRAM — FreeRTOS playground hardware.
$12.69DiagnosticHiLetgo USB Logic Analyzer Device with EMI Ferrite Ring USB Cable 24MHz 8CH 24MHz 8 Channel UART IIC SPI Debug
Budget 8-ch USB logic analyzer (sigrok/PulseView). Useful for RTOS GPIO timing and SI digital demos — not a Saleae substitute in bandwidth.
$29.10BookReal-Time Systems
Schedulability Theory: hyperbolic bound, SRP, and response-time analysis R_i = C_i + Σ⌈R_i/T_j⌉ C_j — formulas this scheduler evaluates to predict deadline misses before Gantt draws them.
$99.95KitSparkFun Inventor's Kit - v4.1.2
SparkFun Original SIK — full starter lab kit for circuit + code fundamentals. 10% Originals commission. (SparkFun does not stock Prusa/Bambu printers; this is the education path into hardware.)
$18.50KitSparkFun MicroMod Main Board - Single
SparkFun Original MicroMod carrier — swap processor blades for FPGA/MCU experiments next to Verilog Live Sculptor. 10% Originals.
$17.50ToolSparkFun OpenLog
SparkFun Original OpenLog — serial data logger for FOC / RTOS capture sessions. 10% Originals commission.
$16.95MicrocontrollerSparkFun Pro Micro - RP2350
SparkFun Original Pro Micro RP2350 — compact dual-core for RTOS task demos. 10% Originals commission.
$169.99SBCiRasptek Basic Starter Kit for Raspberry Pi 5 — 4GB RAM, 27W PD PSU, Active Cooler
Pi 5 4GB board — Amazon verified ASIN B0CK3L9WD3. Was SparkFun third-party (no Originals commission); now Amazon affiliate (engineersport-20) for proper tracking.
$259.95SBCCanaKit Raspberry Pi 5 Starter Kit PRO — Turbine Black, 8GB RAM, 128GB
Flagship Pi 5 8GB board — Amazon verified ASIN B0CK2FCG1K (via DuckDuckGo Amazon search). SparkFun third-party gave no commission; now Amazon affiliate.
$29.50MicrocontrollerSparkFun RedBoard Plus
SparkFun Original Uno-compatible RedBoard Plus — Qwiic-ready teaching MCU for FreeRTOS demos and sensor labs. 10% Originals commission.
$23.80MicrocontrollerTeensy 4.0
600 MHz Cortex-M7 — high-rate control loops and DSP on the RTOS / FOC benches. Carried by SparkFun (third-party PJRC; tracked referral).
$29.95MicrocontrollerSparkFun Thing Plus - ESP32 WROOM (USB-C)
SparkFun Original ESP32 Thing Plus — Wi-Fi/BLE flight-firmware and RTOS target. 10% Originals commission.
$30.95MicrocontrollerSparkFun Thing Plus - RP2350
SparkFun Original RP2350 Thing Plus (Feather) — dual-core lab MCU with Qwiic. 10% Originals commission.
$26.99KitSTM32 Nucleo Development Board with STM32F446RE MCU NUCLEO-F446RE
180MHz Cortex-M4 running FreeRTOS cases from this scheduler — flash task set with periods 10/50/100ms, measure actual jitter vs simulated WCET/CET and deadline miss rate under RMS vs EDF.
Prices shown were retrieved from the Amazon Product Advertising API on 19 July 2026 and are indicative only — the price and availability on Amazon at the time of purchase apply.
Prices shown were checked against the Amazon product listing on 9 August 2026 and are indicative only — the price and availability on Amazon at the time of purchase apply.
More gear across every app: the full Gear list →
Hardware Kit Builder
Build the physical hardware platform. Select your components below to generate a live, real-time bill of materials and build instructions.
Build this lab
RTOS lab stack (SparkFun-first)
Teensy 4.0 + RedBoard Plus + logic analyzer — schedule real tasks after you tune the simulator. Prefer SparkFun Originals (RedBoard) for the 10% path.
- $24
- $30
- $13
- $17
- $27
Prices shown were retrieved from the Amazon Product Advertising API on 19 July 2026 and are indicative only — the price and availability on Amazon at the time of purchase apply.
Estimated total
$66
Prices from Amazon catalog cache · may change
Core solver — TypeScript
// RTOS core model: TCB, ready queue, PI inheritance, RMS/EDF
type TaskDef = {
id:number; name:string;
T:number; C:number; D:number; // period, WCET, deadline ms
basePrio:number; // RMS: 1/T, manual: 1..99 mapped
mutex:string|null;
csStart:number; // fraction 0..1 where lock attempted
csDur:number; // fraction held
color:string;
};
type Job = {
jobId:number; taskId:number;
release:number; absDeadline:number;
executed:number; remaining:number;
state:'Ready'|'Running'|'Blocked'|'Done';
blockedOn:string|null;
holds:Set<string>;
basePrio:number; effPrio:number;
effDeadline:number;
blockedTime:number;
firstStart:number|null;
completion:number|null;
missed:boolean;
};
function basePriority(t:TaskDef, mode:'RMS'|'EDF'){
if(t.basePrio>=100) return t.basePrio; // manual mapped 100..9900
return mode==='RMS' ? (1_000_000 / t.T) : (1_000_000 / t.T); // rate monotonic base
}
function simulate(tasks:TaskDef[], horizon:number, mode:'RMS'|'EDF', isrPeriod:number, isrC:number){
const mutexState = new Map<string,{holder:Job|null, queue:Job[]}>();
let jobs:Job[]=[]; let jid=0;
const log:number[] = new Array(horizon).fill(-1);
const perTask: Record<number, Job['state'][]> = {};
tasks.forEach(t=> perTask[t.id]= new Array(horizon).fill('Idle' as any));
perTask[-1] = new Array(horizon).fill('Idle' as any); // the ISR's own row
const events:{t:number, waiter:number, holder:number, mutex:string}[]=[];
let totalMiss=0;
for(let t=0; t<horizon; t++){
// releases
tasks.forEach(td=>{
if(t % td.T===0){
const j:Job={
jobId:jid++, taskId:td.id, release:t, absDeadline:t+td.D,
executed:0, remaining:td.C, state:'Ready',
blockedOn:null, holds:new Set(), basePrio:basePriority(td,mode),
effPrio:basePriority(td,mode), effDeadline:t+td.D,
blockedTime:0, firstStart:null, completion:null, missed:false
};
jobs.push(j);
}
});
if(isrPeriod>0 && t % isrPeriod===0){
const ij:Job={
jobId:jid++, taskId:-1, release:t, absDeadline:t+isrPeriod,
executed:0, remaining:isrC, state:'Ready', blockedOn:null,
holds:new Set(), basePrio:1e9, effPrio:1e9, effDeadline:t+isrPeriod,
blockedTime:0, firstStart:null, completion:null, missed:false
};
jobs.push(ij);
}
// active jobs
const active = jobs.filter(j=> j.release<=t && j.remaining>0);
// try unblock / release expired CS
active.forEach(j=>{
if(j.taskId===-1) return;
const td = tasks.find(x=>x.id===j.taskId);
if(!td || !td.mutex) return;
const m = td.mutex;
// release check
if(j.holds.has(m)){
const endFrac = td.csStart+td.csDur;
if(j.executed >= endFrac*td.C){
j.holds.delete(m);
const st = mutexState.get(m);
if(st) st.holder=null;
}
}
});
// blocked jobs attempting acquisition after release
active.filter(j=> j.state==='Blocked' && j.blockedOn).forEach(j=>{
const st = mutexState.get(j.blockedOn!);
if(st && !st.holder){
// arbitration: highest effPrio waiter gets lock
const candidates = st.queue.slice().sort((a,b)=> b.effPrio-a.effPrio);
if(candidates[0]===j){
st.holder=j; j.holds.add(j.blockedOn!); j.blockedOn=null; j.state='Ready';
st.queue = st.queue.filter(x=>x!==j);
}
}
});
// new lock attempts
active.filter(j=> j.state!=='Blocked' && j.remaining>0 && !j.blockedOn).forEach(j=>{
if(j.taskId===-1) return;
const td = tasks.find(x=>x.id===j.taskId);
if(!td?.mutex) return;
if(j.holds.has(td.mutex)) return;
if(j.executed < td.csStart*td.C) return;
if(j.executed >= (td.csStart+td.csDur)*td.C) return;
let st = mutexState.get(td.mutex);
if(!st){ st={holder:null, queue:[]}; mutexState.set(td.mutex,st); }
if(!st.holder){
st.holder=j; j.holds.add(td.mutex);
}else if(st.holder!==j){
if(!st.queue.includes(j)) st.queue.push(j);
j.state='Blocked'; j.blockedOn=td.mutex;
events.push({t, waiter:j.taskId, holder:st.holder.taskId, mutex:td.mutex});
}
});
// priority / deadline inheritance fixpoint
let changed=true;
for(let iter=0; iter<10 && changed; iter++){
changed=false;
for(const [mname, st] of mutexState){
if(!st.holder || st.queue.length===0) continue;
if(mode==='RMS'){
const maxWait = Math.max(...st.queue.map(q=> q.effPrio));
if(maxWait > st.holder.effPrio){ st.holder.effPrio=maxWait; changed=true; }
}else{
const minDead = Math.min(...st.queue.map(q=> q.effDeadline));
if(minDead < st.holder.effDeadline){ st.holder.effDeadline=minDead; changed=true; }
}
}
}
// deadline miss detection
active.forEach(j=>{
if(!j.missed && t >= j.absDeadline && j.remaining>0){
j.missed=true; totalMiss++;
}
if(j.state==='Blocked') j.blockedTime++;
});
// scheduling selection
const cand = active.filter(j=> j.state!=='Blocked');
if(cand.length>0){
cand.sort((a,b)=>{
if(mode==='RMS') return b.effPrio - a.effPrio;
const d = a.effDeadline - b.effDeadline;
return d!==0? d : b.effPrio - a.effPrio;
});
const run = cand[0];
run.state='Running';
if(run.firstStart==null) run.firstStart=t;
cand.slice(1).forEach(j=>{ if(j.state==='Running') j.state='Ready'; if(j.state!=='Blocked') j.state='Ready'; });
run.executed++; run.remaining--;
if(run.remaining<=0){
run.remaining=0; run.state='Done'; run.completion=t+1;
for(const m of [...run.holds]){ const st=mutexState.get(m); if(st?.holder===run) st.holder=null; }
run.holds.clear();
}
log[t]=run.taskId;
}else{
log[t]=-1;
}
// per-task aggregate timeline for drawing
tasks.forEach(td=>{
const js = active.filter(j=> j.taskId===td.id);
let st:'Idle'|'Ready'|'Running'|'Blocked' = 'Idle';
if(js.some(j=> j.state==='Running')) st='Running';
else if(js.some(j=> j.state==='Blocked')) st='Blocked';
else if(js.some(j=> j.state==='Ready')) st='Ready';
else if(js.length>0) st='Ready'; // a job that finished THIS tick is still occupying the row
perTask[td.id][t]=st as any;
});
// ISR row
{
const isrActive = active.filter(j=> j.taskId===-1);
let st:'Idle'|'Ready'|'Running'|'Blocked' = 'Idle';
if(isrActive.some(j=> j.state==='Running')) st='Running';
else if(isrActive.some(j=> j.state==='Blocked')) st='Blocked';
else if(isrActive.some(j=> j.state==='Ready')) st='Ready';
perTask[-1][t]=st as any;
}
}
const U = tasks.reduce((s,t)=> s + t.C/t.T, 0);
const bound = tasks.length * (Math.pow(2,1/tasks.length)-1);
return {log, perTask, jobs, events, U, bound, totalMiss};
}
// Utilization: U = Σ C_i / T_i
// Liu & Layland: U ≤ n(2^{1/n}-1)
// Response time iteration: R_i^{k+1}= C_i + B_i + Σ ceil(R_i^k/T_j) C_jExport · Soft gate
Export FreeRTOSConfig + task set
Download your tuned task periods (T), WCET (C), deadlines (D) and mutex map — free with watermark, clean after email unlock. Great for heading to SparkFun RedBoard / Teensy 4.0 bench with hiletgo logic analyzer.
File · FreeRTOSConfig.h
FreeRTOSConfig.htext/x-c-header+ watermark line on free pathFree download adds a small footer: // Export from makerportal.ai — free watermarked build. Unlock c…Clean export removes footer. Both are generated fresh from your current sim tuning.
Privacy: email stays in your browser localStorage (mp_export_email_rtos-scheduler) + unlock flag (mp_export_unlock_rtos-scheduler). If Buttondown username is configured, we also POST to Buttondown (privacy-first mode, no tracking pixels per D-014). See privacy → affiliates & email.
Unlock clean export
Soft gate — no hard paywall, no Clerk. Email stays local unless you explicitly check the newsletter box. Unsubscribe anytime. RSS at /rss.xml.
✓ Lab Pro — clean export on every lab
Your licence unlocks this and every other gated simulator, so there is nothing to enter here. Manage or sign out on the shop page.
✓ Unlocked — clean exports enabled
Stored in mp_export_unlock_rtos-scheduler. Clean file omits watermark. Re-lock via browser devtools → localStorage.
Frequently asked questions
What does RMS guarantee and when does it fail?
Rate Monotonic Scheduling assigns fixed priority by period: shorter T → higher priority. Liu & Layland bound U ≤ n(2^{1/n}-1) is sufficient but not necessary; if U exceeds bound, tasks may still be schedulable but must pass exact response time analysis R_i = C_i + Σ ceil(R_i/T_j)C_j for all higher priority j. Failure mode is deadline miss of low priority tasks even when U<1 if harmonicity poor.
How does priority inheritance fix unbounded blocking?
When high priority job H blocks on mutex held by low L, L inherits H priority (or earliest deadline in EDF). This bounds blocking to one critical section. Without it, medium tasks can preempt L indefinitely, causing chain blocking. SRP (Stack Resource Policy) and PCP build on this with ceiling: lock only if priority > system ceiling.
EDF vs RMS under mutexes?
EDF is optimal for independent tasks: U≤1 schedulable. With mutexes, it needs deadline inheritance: holder inherits min(deadline) of waiters, called DIPP or SRP. RMS uses priority inheritance. EDF generally yields lower jitter for mixed periods but needs more dynamic queue; RMS is simpler for static analysis and certification.
What is response time analysis RTA?
Iterative equation R_i^{k+1}= C_i + B_i + Σ_{j∈hp(i)} ceil(R_i^k/T_j)*C_j where B_i is worst blocking time from lower priority tasks holding mutex. Converges if R_i≤D_i. Our simulator logs actual response vs analytic bound; jitter = std(R_i) across jobs.
Why audio tension mapping?
Deadline misses correlate with urgency. We map miss ratio m = misses/jobs to beating frequency: two oscillators f1=220Hz, f2 = 330 - m·99Hz. At m=0, f2=330Hz perfect fifth consonant. At m=1, f2=231Hz → 11Hz beating roughness (Helmholtz dissonance curve peaks ~15-30Hz beating). Gain also scales with m, so lock-ups sound tense.
ISR nesting how?
ISR task has priority 1e9, preempts all. If ISR period < C_ISR, multiple ISR jobs queue (nesting). We show purple bars always on top. ISR holding mutex can cause priority inversion even for kernel tasks; our mutex manager allows it to demonstrate why ISR should use lock-free queues.
Shareable still
The instrument, captured—not illustrated.
This 16:9 frame is rendered from the real browser instrument above. It is the page's canonical preview for image search, link unfurls, and posts that need to show what the tool actually does.
Download 1280 × 720 JPEG
Continue the experiment