Audience: This guide is written for firmware engineers, RTOS architects and OEM program managers who need to understand how a reading pen schedules its audio player, OID decoder, button handler, BLE stack and OTA agent — and how that scheduling affects user-visible latency, battery life and field-service tickets. It assumes you know what a thread or task is, but you do not need to be an expert in any one RTOS.
Every reading pen runs more than one task at a time, even the smallest P2 with its 64 KB of Flash. On a typical T2 you might find 24–38 active tasks: an OID decoder task, an audio mixer task, a UI task, a Wi-Fi supervisor task, a BLE GATT server task, an OTA staging task, a battery gauge task, a power-management task and several sensor-poll tasks. The scheduler decides which one runs on the CPU each microsecond.
This article walks you through the four scheduler families used across our 11 SKUs (B1, B2, F8, K7, KF1, L5, P2, Q6, Q8, T2, Y6) — FreeRTOS, Zephyr, AliOS Things and Linux CFS — and shows the priority assignment, pre-emption latency, priority-inversion mitigation and load-shedding policy we use on each.
1. What a Reading Pen Scheduler Actually Does
A scheduler has three jobs:
- Choose which task runs on the CPU at each instant.
- Enforce priority — higher-priority tasks pre-empt lower-priority ones.
- Bound the latency between an event (interrupt, signal) and the response task waking up.
On a reading pen, the third job is the most important. A user taps the pen on an OID dot, the OID sensor fires a DMA-complete interrupt, the OID decoder task must wake up within 1–4 ms to decode the page, and the audio player task must start streaming within 50 ms. If the scheduler cannot bound that latency, the user hears a lag.
2. The Four Scheduler Families We Ship
| Scheduler | OS | SKUs that use it | Pre-emption model | Tick rate |
|---|---|---|---|---|
| FreeRTOS tick-based | FreeRTOS 10.4 | B1, B2, P2 | Priority-based pre-emptive | 1 kHz |
| Zephyr tickless | Zephyr 3.4 LTS | K7, KF1 | Priority + deadline | 100 Hz baseline, tickless idle |
| AliOS Things | AliOS Things 3.3 | Q6, Q8 | Priority-based pre-emptive | 1 kHz |
| Linux CFS | Linux 5.10 / 5.15 | F8, L5, T2, Y6 | Completely Fair Scheduler with RT-group | 250 Hz / 1 kHz |
The two-thirds RTOS, one-third Linux split mirrors the broader reading pen market. The scheduler choice is downstream of the OS choice discussed in TC-097, but the priority policy is downstream of the SKU's feature set.
3. FreeRTOS Scheduler on B1, B2, P2
FreeRTOS uses a fixed-priority pre-emptive scheduler with optional time slicing. Each task has a priority from 0 (lowest) to configMAX_PRIORITIES - 1 (highest, typically 32 or 56). The scheduler always runs the highest-priority ready task; ties are broken by time slice.
On B2 firmware 2.4.7, our priority table is:
| Priority | Task | Purpose | Stack |
|---|---|---|---|
| 7 (highest) | audio_dma_isr |
DMA-complete handler (ISR-deferred) | 256 bytes |
| 6 | oid_decode |
OID decode page | 1,024 bytes |
| 5 | audio_player |
MP3 decode + DAC feed | 2,048 bytes |
| 4 | button_handler |
Button debounce + long-press | 512 bytes |
| 3 | battery_monitor |
Battery gauge poll | 512 bytes |
| 2 | led_pattern |
RGB LED animation | 256 bytes |
| 1 | idle_hook |
Tickless idle entry | 128 bytes |
| 0 | ota_staging |
OTA receive + write | 1,024 bytes |
The ISR is deferred to a high-priority task via xQueueSendFromISR + taskNOTIFY so the audio stream never blocks on long ISR code. Priority 7 means the audio DMA handler runs before the OID decoder for the next 200 µs — important because the audio DMA buffer must be refilled within 2 ms to avoid a glitch.
Buyer tip: If you see a reading pen datasheet that lists 4 or fewer priorities, run. Real reading pens need at least 6.
4. Zephyr Scheduler on K7 and KF1
Zephyr uses a multi-queue bitmap scheduler with optional deadline scheduling. Tasks have a priority from -16 (lowest) to 15 (highest). On K7 firmware 1.9.5 and KF1 firmware 1.9.5, our priority table is:
| Priority | Thread | Purpose |
|---|---|---|
| 15 | audio_stream_isr |
Audio DMA done |
| 14 | oid_decode |
OID decode |
| 13 | audio_player |
MP3 decode |
| 12 | ble_gatt |
BLE GATT server |
| 11 | ble_adv |
BLE advertising |
| 10 | button_handler |
Buttons + long-press |
| 9 | battery_monitor |
Battery poll |
| 8 | led_pattern |
LED animation |
| 7 | sensor_calib |
Sensor calibration drift |
| 6 | fs_commit |
Filesystem commit |
| 5 | ota_staging |
OTA staging |
| 4 | shell |
Debug shell |
| 3 | sys_workq |
System work queue |
| 2 | idle |
Tickless idle |
Zephyr's tickless idle on K7 drops current draw from 4.8 mA to 1.6 mA in idle. That is the 3.2 mA delta that gives K7 its 6.0 h battery life on the 800 mAh Li-ion 14500 cell.
K7 also has a deadline-mode thread (audio_player_deadline) used when the OEM wants sub-millisecond audio scheduling. We enable this in the firmware build flag CONFIG_DEADLINE=y, which is on by default on KF1 and off on K7 to save 32 KB of Flash.
5. AliOS Things Scheduler on Q6 and Q8
AliOS Things 3.3 uses a priority-based pre-emptive scheduler very similar to FreeRTOS. It supports up to 32 priorities, and has a built-in krhino kernel that we have been using since 2022. The Q6 / Q8 priority table looks like:
| Priority | Task | Purpose |
|---|---|---|
| 7 (highest) | audio_isr |
Audio DMA done |
| 6 | oid_decode |
OID decode |
| 5 | audio_player |
MP3 decode + DAC |
| 4 | btn_event |
Button debounce |
| 3 | ble_gatt |
BLE GATT |
| 3 | ble_adv |
BLE advertising |
| 2 | pwr_mgr |
Power manager |
| 2 | batt_poll |
Battery gauge |
| 1 | led_pat |
LED animation |
| 0 | ota_recv |
OTA receiver |
The AliOS scheduler has a slightly faster context switch than FreeRTOS on Cortex-M4 because AliOS uses fewer registers in the saved context. Our measurement is 1.4 µs on Q8 vs 2.0 µs on B2 — meaningful for high-priority audio interrupts.
6. Linux CFS Scheduler on F8, L5, T2, Y6
Linux uses the Completely Fair Scheduler (CFS) on F8, L5, T2 and Y6. CFS does not have fixed priorities in the RTOS sense. Instead it has:
- Nice values from -20 (highest) to 19 (lowest).
- Realtime group (
SCHED_RR,SCHED_FIFO) with priorities 1–99. - cgroups for grouping tasks.
- Deadline scheduling (
SCHED_DEADLINE) for hard-realime tasks.
On L5 firmware 4.2.0, our priority table is:
| Linux priority | Process | Description |
|---|---|---|
SCHED_FIFO 90 |
audio-daemon |
Audio mixer + DAC |
SCHED_FIFO 80 |
oid-decoder |
OID decode |
SCHED_FIFO 70 |
wifi-supervisor |
Wi-Fi roaming |
SCHED_FIFO 60 |
ble-gatt |
BLE GATT |
SCHED_RR 50 |
button-handler |
Button + long-press |
SCHED_RR 40 |
battery-monitor |
Battery gauge |
nice -10 |
led-pattern |
LED animation |
nice -5 |
ui-thread |
UI render |
nice 0 |
asr-engine |
On-device ASR |
nice 5 |
logging-daemon |
Telemetry |
nice 10 |
cache-cleanup |
Cache cleanup |
nice 19 |
analytics |
Anonymous analytics |
The audio daemon runs at SCHED_FIFO 90 to guarantee it pre-empts everything except kernel tasks. The OID decoder runs at SCHED_FIFO 80 so it can pre-empt the ASR engine. The ASR engine runs at nice 0 because it is throughput-oriented, not latency-oriented.
7. Priority Inversion: The Trap
Priority inversion is what happens when a low-priority task holds a mutex that a high-priority task needs. The high-priority task blocks waiting for the low-priority task; meanwhile, a medium-priority task pre-empts the low-priority task. The high-priority task is stuck until the medium-priority task yields.
We have seen priority inversion in the field once. On Q8 firmware 2.6.4 (the predecessor of 3.3.1), a UI mutex was held by a low-priority logging task while the audio player tried to acquire it; the audio player stalled for 12 ms — audible as a glitch.
The mitigation we ship on every RTOS SKU is priority inheritance mutexes (also called priority-ceiling mutexes):
- FreeRTOS uses
xSemaphoreCreateMutex()(which supports priority inheritance in recent versions). - Zephyr has
k_mutexwith priority inheritance enabled by default. - AliOS has
krhino_mutex_create()with theRHINO_MUTEX_PRIO_INHERITflag. - Linux uses
pthread_mutexattr_setprotocol(... PTHREAD_PRIO_INHERIT ...).
The L5 audio daemon uses priority-inherited POSIX mutexes around the ALSA PCM handle. The T2 video decoder does the same around the VPU handle.
8. Tickless Idle and Power Savings
The biggest power saving a scheduler can deliver on an RTOS SKU is tickless idle. When there is nothing to do, the scheduler disables the SysTick interrupt and the CPU enters a low-power wait. When the next event fires (a timer expiry, an interrupt), the scheduler wakes the CPU and re-programs the SysTick.
Tickless idle savings on our line-up (measured at 25 °C, 3.7 V):
| SKU | Idle current with tick | Idle current tickless | Saving |
|---|---|---|---|
| B1 | 6.4 mA | 2.1 mA | 67 % |
| B2 | 5.9 mA | 2.1 mA | 64 % |
| K7 | 8.2 mA | 2.8 mA | 66 % |
| KF1 | 8.0 mA | 2.7 mA | 66 % |
| P2 | 4.2 mA | 1.4 mA | 67 % |
| Q6 | 6.6 mA | 2.4 mA | 64 % |
| Q8 | 6.4 mA | 2.4 mA | 62 % |
Linux SKUs do not have tickless idle in the RTOS sense. They have cpuidle governors that pick a deeper C-state when the runqueue is empty. F8 reaches 14 mA idle, L5 reaches 18 mA, T2 reaches 22 mA — still 4–10× more than RTOS idle.
9. Load Shedding and Overload Behaviour
When a reading pen's CPU is overloaded — for example, the OID decoder fires 5 pages in 200 ms because the user is paging through a book — the scheduler must drop work to keep the audio stream healthy. Our load-shedding policy:
| Condition | Action |
|---|---|
| OID queue depth > 8 | Drop oldest, log a counter |
| Audio buffer underrun imminent | Increase OID task priority briefly |
| BLE stack queue depth > 16 | Disconnect, reconnect |
| Wi-Fi TX queue depth > 32 | Drop low-priority frames |
| Linux loadavg > 4 | Throttle ASR engine to 50 % CPU |
| Battery < 8 % | Disable Wi-Fi, dim LED, switch to mono audio |
The "increase OID task priority briefly" trick is a dynamic priority boost that we implement in the audio player task. When the audio buffer is more than 75 % empty, the audio task sends a notification to the OID decoder to bump its priority for the next 50 ms. This buys 20–40 ms of headroom on B2 in worst-case scenarios.
10. Scheduler Inspection and Debugging
Every SKU exposes a scheduler inspection API over USB or BLE. The API returns:
typedef struct {
uint32_t task_id;
char name[16];
uint8_t priority;
uint32_t stack_high_watermark;
uint32_t runtime_us;
uint32_t cpu_pct;
} task_stats_t;
OEMs can poll this API during factory QA to capture a "scheduler fingerprint" — a snapshot of CPU usage by task. We have shipped over 47,000 such fingerprints since 2023. The fingerprint is invaluable for diagnosing field issues: a pen that shows battery_monitor consuming 28 % CPU instead of the expected 4 % has a polling bug somewhere.
For Linux SKUs, the same data is available through /proc/<pid>/stat and top -H. We bundle a scheduler-diag tool in the L5 firmware 4.2.0 that captures the fingerprint in a JSON file and ships it back over Wi-Fi on demand.
11. How to Choose the Right Scheduler for Your Reading Pen
If you are an OEM picking a scheduler for a new reading pen SKU, here is the rule of thumb:
| Buying scenario | Recommended scheduler | Suggested SKU |
|---|---|---|
| Sub-$5, single-language, tap-only | FreeRTOS tick-based | P2, B1 |
| Mid-range bilingual, BLE | Zephyr with tickless | K7, KF1 |
| Mid-range with custom OID, BLE | AliOS Things | Q6, Q8 |
| Wi-Fi + ASR + 720p video | Linux CFS with SCHED_FIFO for audio |
L5 |
| Defence or premium build, hard real-time | Linux with PREEMPT_RT patch | T2 |
| Premium K–12 with content streaming | Linux CFS with deadline scheduling | Y6 |
Buyer tip: Always ask the OEM for two scheduler metrics: worst-case pre-emption latency from a low-priority to a high-priority task (µs), and the idle current with tickless mode enabled (mA). Anything above 10 µs or 4 mA idle is a red flag for K–3 reading pen use cases.