16 KiB
Paired FC02/K02 Plan — First FreeRTOS Task through A1–A8
Status: agreed paired design; FC02 C and K02 C++ now have local implementations that follow the shared E01–E12 contract.
This plan defines one controlled teaching experiment rendered twice:
- FC02 / FreeRTOS C: raw
TaskFunction_t,void *pvParameters,TaskHandle_tandxTaskCreate; - K02 / FreeRTOS C++: the existing header-only
Task<Derived>CRTP wrapper, explicitstart()and static trampoline.
The design remains the comparison contract. FC02 now has a local executable, HTML and TeX implementation; PDF generation and publication remain separate operations.
Learning question
The student must be able to answer one question with source, binary and runtime evidence:
What remains identical when a FreeRTOS task is expressed through raw C or a small C++ wrapper, and what new compile-time and runtime boundaries does the wrapper introduce?
The C and C++ cards therefore use the same calculation and kernel behaviour. Only the language-facing task abstraction changes.
Curriculum position
C pointers
→ C structures and memory regions
→ heap_4 mechanics in C
→ FC02 raw FreeRTOS task in C
→ C++ language/OOP prerequisites
→ K02 FreeRTOS task wrapper in C++
→ later lifetime, RAII, communication and synchronization cards
FC02 establishes the raw kernel boundary first. K02 reuses that known behaviour and makes the abstraction delta observable instead of teaching the kernel and C++ wrapper as two simultaneous unknowns.
Controlled experiment contract
Both cards must share these invariants:
| Property | Required value |
|---|---|
| Kernel | FreeRTOS-Kernel V11.3.0, same pinned source revision |
| Target | RV32I/Hazard3 simulator, cycle-exact teaching profile |
| Workers | two equal-priority CPU-bound workers, created before the scheduler |
| Work | sum integers 1..20000 independently |
| Expected result | 200010000 for each worker |
| Supervisor | one lower-priority task validates both workers |
| Scheduling proof | elapsed work crosses at least two ticks and records A -> B -> A |
| Allocation | dynamic xTaskCreate backed by the same heap_4 configuration |
| Completion | diagnostic state/result is published before self-delete: NULL in C, nullptr in C++ |
| Replay | clean RAM and a new generation for every requested RUN checkpoint |
The comparison is valid only when the two builds also use byte-identical
FreeRTOSConfig.h, the same tick rate, time-slicing setting, priorities, stack
depths, worker creation order and checkpoint-sink signature. CI must compare
those inputs rather than relying on prose.
Language flags differ only where the lesson requires it:
- FC02: freestanding C11;
- K02: freestanding C++17, no exceptions, no RTTI and no hosted C++ runtime.
The workload must remain present in disassembly. Bounds, trace writes and published results are observable; a compiler-folded constant is a failed teaching build.
A1–A8 is a palette, not a quota
The stable viewer keeps all eight positions. A card enables only viewpoints that answer a real question. An unavailable tab remains visible with a short reason, and the print index repeats that reason.
| Viewpoint | FC02 C | K02 C++ | Reason |
|---|---|---|---|
| A1 CONTEXT | yes | yes | system and language boundaries |
| A2 STRUCTURE | yes | yes | C contexts/handles versus C++ classes/fields |
| A3 DISPATCH | yes | yes | C function pointer versus CRTP trampoline |
| A4 APPLICATION | no | yes | raw C module already is the application; C++ specializes a generic wrapper |
| A5 FLOW | yes | yes | common E01–E12 scenario |
| A6 STATE | yes | yes | diagnostic/language lifetime separated from kernel task state |
| A7 RUNTIME | yes | yes | addresses, TCB, stack, registers, PC and ticks |
| A8 PATTERNS | no | yes | a separate C pattern view would be decorative; K02 provides the comparison |
FC02 may label local idioms such as context object, opaque handle and C callback ABI in A2/A3. It must not manufacture an A8 diagram merely to fill the tab.
Teaching hierarchy
Each applicable viewpoint is a normal Block. This preserves the existing
card tree and does not add a new hierarchy level.
Series
└── Card
└── Task01 · First FreeRTOS task
├── Block · A1 CONTEXT
├── Block · A2 STRUCTURE
├── Block · A3 DISPATCH
├── Block · A4 APPLICATION K02 only
├── Block · A5 FLOW
│ ├── Phase · CREATE / START
│ └── Phase · DISPATCH / FINISH
├── Block · A6 STATE
│ ├── Phase · LANGUAGE / DIAGNOSTIC
│ └── Phase · KERNEL
├── Block · A7 RUNTIME
│ ├── Phase · MEMORY
│ └── Phase · CPU / SCHEDULER
├── Block · A8 PATTERNS K02 only
└── Exercise · Paired timestamped trace
The A1–A8 top strip jumps to its block. Up/Down walks steps inside a block;
Ctrl+Up/Down crosses blocks. Cursor movement is inert. Enter in the viewer
and F2 from Neovim explicitly activate the selected item.
Common semantic event vocabulary
Snapshot identifiers remain local to each card and container. A shared
event_id makes the observations comparable without pretending that one
debugger checkpoint can be reused across two binaries.
| Event | Shared meaning | FC02 C boundary | K02 C++ boundary |
|---|---|---|---|
| E01 | application representation prepared | static worker contexts initialized | static SumTask objects constructed |
| E02 | launch interface entered | first raw create request | first workerA.start() entry |
| E03 | kernel create boundary | xTaskCreate(entry, ctx) |
xTaskCreate(trampoline, this) |
| E04 | task ready and handle stored | context handle is non-null | wrapper state ready, handle non-null |
| E05 | scheduler start | vTaskStartScheduler() |
vTaskStartScheduler() |
| E06 | C callback entry | sum_task_entry(void *) |
static trampoline(void *) |
| E07 | calculation running | typed WorkerContext * body |
SumTask::run() and state running |
| E08 | deterministic time slice | worker A observes A -> B -> A |
worker A observes A -> B -> A |
| E09 | result published | context/global result stored | g_results[id] stored, run() returns |
| E10 | diagnostic completion | context state complete, public handle cleared | wrapper state complete, public handle cleared |
| E11 | current task self-deletes | vTaskDelete(NULL) |
vTaskDelete(nullptr) |
| E12 | lower-priority supervisor passes | raw context invariants pass | wrapper/object invariants pass |
E02 is the first launch hit and is guarded by prior checkpoint state. E08 is pinned to worker A. Per-instance conditions must prevent worker B from accidentally satisfying worker A checkpoints.
The supervisor executes before the idle task necessarily reclaims deleted TCBs. Heap reclamation is therefore not an E01–E12 acceptance event. A7 may measure allocator deltas, but it must describe them as observations at a defined instant.
CODE and RUN semantics
An interactive anchor has one of two modes:
- CODE selects source or a deterministic binary query. It never resets or
runs the target and does not require
snapshot_ref. - RUN opens the associated source and performs a clean deterministic
replay to its card-local
snapshot_ref.
Compile-time facts—types, inheritance, friend, field layout, absence of
virtual dispatch and ABI restrictions—are CODE facts. Scheduling, registers,
task states and memory values are RUN facts. A class diagram must never fake a
runtime event.
Repeated visible step numbers are permitted in different blocks, but tooling
must address a step by its full task/block/phase/step key. A bare ambiguous
--step request is rejected.
Debug strategy attached to every step
Every selectable UML anchor owns one default debugging strategy. The strategy is keyed by the full navigation position rather than embedded as unstructured prose in the step.
strategy
├── id and kind: code | run
├── prerequisites: card, profile, target and required artifact
├── code_ref: repository-validated file and line/range
├── action: open | replay
├── layout: source, asm, registers, stack, memory, kernel state
├── commands: deterministic GDB or artifact queries
├── expected_observations
├── assertions
└── evidence_fields
RUN assertions reuse debug_checkpoints.verify.expressions. CODE assertions
are exact command/pattern pairs over an identified ELF or source blob, for
example nm, readelf or a bounded disassembly query. A screenshot of an
unidentified terminal is not sufficient CODE evidence.
The minimum Termdebug observation set is chosen per step, not displayed by habit:
- source and assembly at the boundary being taught;
a0only where the RV32 ABI still carries the callback argument;spand task stack interval where stack ownership is the question;- object/context address, public handle and TCB address where identity is the question;
- PC, tick and trace when scheduling is the question;
- result and diagnostic state when completion is the question.
pxCurrentTCB may be inspected by the debugger and is marked kernel
internal. Card code uses public FreeRTOS APIs and does not make this symbol a
program contract.
Visual and code evidence contract
Each UML element has a visible numbered anchor and a stable authored
svg_target. Selection by mouse and keyboard controls the same cursor.
- disabled content remains readable and uses a neutral outline plus an explicit unavailable marker/reason;
- selectable inactive neighbours alternate two restrained treatments so their hit areas remain distinguishable;
- active selection adds a stronger outline, small positional marker and light fill;
- focus, selectability and completion remain distinguishable in grayscale and do not rely on hue alone;
- non-selected elements remain legible rather than disappearing.
Code shown beside a diagram is read-only evidence. Neovim remains the editor
and live debugger. The component receives a validated code_ref, source blob
hash and explicit highlighted line range; it never fetches an arbitrary file
path supplied by the browser.
The planned CodeEvidence pipeline uses Shiki at generator/build time, not
as a browser editor or runtime highlighter. A fine-grained, version-pinned
highlighter loads only c, cpp, riscv, shellscript and json, produces
flat per-line token arrays, and the React viewer renders that allow-listed
data. This keeps the browser bundle free of the regex/WASM engine while
retaining an actual RISC-V TextMate grammar and exact line/range classes.
Rendering never relies on dangerouslySetInnerHTML.
A custom Shiki theme emits CSS-variable values rather than stock-theme hex
colours. Light, dark and print treatments therefore remain viewer CSS
decisions. Assembly source uses the riscv grammar directly. Objdump .lst
input first passes through a small deterministic column parser: address, byte
and symbol fields receive fixed semantic classes, while only the instruction
tail is tokenized as RISC-V. A non-matching listing line falls back to plain
text. Disassembly keeps white-space: pre and horizontal scrolling rather
than soft wrapping.
Audit chain and later evidence capture
The intended chain is:
goal and scope
→ applicable A1–A8 viewpoints
→ UML anchor
→ checkpoint debugging strategy
→ CODE proof or RUN replay
→ Neovim + Termdebug + Hazard3 measurement
→ approved checkpoint
→ later composite screenshot
→ later lesson report
Approval must be bound to snapshot_ref, replay generation and verification
result. A later capture must reject stale identity/generation, retain the raw
PNG, store an image SHA-256 and record backend timestamp, card/version/source
hash, target, event, checkpoint and viewer state. Metadata is rendered from
the database rather than burned irreversibly into the original image.
This plan does not implement screenshot capture or report generation.
Page plan
FC02 — seven sheets
- goal, scope, experiment invariants and viewpoint index;
- compact A1 plus main A2;
- A3 callback/parameter dispatch; 4–5. joined A5 CREATE/START and DISPATCH/FINISH spread;
- A6 diagnostic and kernel state lanes;
- A7 runtime evidence plus exercise.
A4 and A8 are printed in the index as unavailable with their reasons.
K02 — eight sheets
- goal, scope, experiment invariants and viewpoint index;
- compact A1 plus main A2;
- main A3 plus compact A4; 4–5. joined A5 CREATE/START and DISPATCH/FINISH spread;
- A6 object/wrapper and kernel state lanes;
- A7 runtime evidence;
- A8 C-versus-wrapper role table plus exercise and rubric.
One diagram must fit the content box. A long logical diagram is split only at
a phase/group boundary and shares a spread_group; the viewer may join those
sheets side by side without changing print pagination.
Acceptance gates for a future implementation
- Parity check proves common kernel/config/workload/priority/stack inputs.
- Every enabled viewpoint has purpose, model kind and ordered anchors.
- Every disabled viewpoint has a non-empty reason.
- Every
svg_targetresolves after rendering and anchor order passes lint. - Every CODE step has a validated code/artifact proof and no snapshot.
- Every RUN step has a card-local snapshot and non-empty assertions.
- E01–E12 are unique, ordered and comparable across the two cards.
- A clean replay produces two sums of
200010000and theA -> B -> Atrace. - FC02 has C-only ABI evidence; K02 has no exceptions, RTTI, vtables or hosted runtime.
- Navigation never mutates the simulator until explicit
Enter/F2. - No program assertion depends on undocumented kernel internals.
- Print remains meaningful without viewer controls or colour.
Decisions from the Claude review
Accepted corrections:
- treat viewpoints as blocks and A1–A8 as an optional palette;
- enforce the paired-experiment parity gates in tooling;
- add shared semantic
event_idwhile keeping snapshots card-local; - validate viewpoint availability, anchors, RUN snapshots and strategies;
- make assertions mandatory rather than merely displaying observations;
- bind approval and later capture to a verified replay generation;
- support deterministic text evidence for CODE steps;
- pre-tokenize source with fine-grained Shiki grammars and keep the React component display-only;
- pin the highlighter/grammar versions and test the objdump-column parser with fixed listing fixtures;
- distinguish diagnostic state from
eTaskStateand mark kernel internals; - keep idle-task reclamation outside E01–E12.
Rejected for these cards:
- replacing K02 CRTP with the upstream virtual
TaskBasedesign; - forcing A4/A8 into FC02;
- automatic replay on cursor movement;
- claiming RAII or ownership semantics before the later lifetime card;
- using
pxCurrentTCBas application code; - treating a class relationship as a runtime checkpoint.
Deferred curriculum envelope — not part of this implementation
For Year 2, a later curriculum pass will attach three coordinated layers to a lesson:
- one FreeRTOS mechanism/concurrent algorithm being learned, for example a task or queue;
- one algorithm selected from the course book;
- one examination task selected from the current matura schedule/material.
The current technology is the execution medium, not a decorative appendix:
- a bare-C card implements and debugs the work in bare C;
- a FreeRTOS-C task card places suitable work inside raw C task(s);
- a FreeRTOS-C++ card expresses the same responsibility through the wrapper under study.
Later work may capture approved UML/debugger evidence, compose a lesson report, generate a PDF and prepare it for Teams. None of that is implemented or assigned to FC02/K02 by this plan.
Primary references
- FreeRTOS task creation: https://www.freertos.org/Documentation/02-Kernel/04-API-references/01-Task-creation/01-xTaskCreate
- FreeRTOS task states: https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/02-Task-states
- FreeRTOS task implementation and self-deletion: https://www.freertos.org/Documentation/02-Kernel/02-Kernel-features/01-Tasks-and-co-routines/05-Implementing-a-task
- Shared A1–A8 convention:
tools/card-layouts/docs/UML-VIEWPOINTS.md - Shiki bundled languages and transformers: https://shiki.style/languages, https://shiki.style/packages/transformers