# Chapter 7: LED Matrix Visualization :::{admonition} What this chapter answers: how do `top` and Task Manager count CPU %? :class: tip Linux's `top` and Windows Task Manager show CPU usage per program. The OS in this chapter counts a similar number itself and shows it on the LED matrix and in the shell's `ps`. It counts by adding time, on every timer interrupt, to whichever task was running at that moment. Linux works on the same basic idea. The details are in {ref}`the column "top and htop" `. ::: ## How the LED matrix is wired The 96 LEDs are **charlieplexed** on 11 lines (ROW0–ROW10). Each pair of lines carries two LEDs in opposite directions, so driving one line HIGH and the other LOW lights exactly one LED. The table at the top right of the schematic below maps the LED numbers in the schematic to positions on the matrix. The bundled `Arduino_LED_Matrix` library lights one LED per tick of a 10 kHz timer interrupt, stepping through all 96 in turn (about 104 Hz per full frame). ([`Arduino_LED_Matrix.h` L182](https://github.com/arduino/ArduinoCore-renesas/blob/1.5.1/libraries/Arduino_LED_Matrix/src/Arduino_LED_Matrix.h#L182), [L346–L349](https://github.com/arduino/ArduinoCore-renesas/blob/1.5.1/libraries/Arduino_LED_Matrix/src/Arduino_LED_Matrix.h#L346-L349), ArduinoCore-renesas 1.5.1) ```{figure} ../_static/uno_r4_wifi_led_matrix_schematic.png :name: fig-ch07-led-matrix-schematic :width: 100% LED matrix schematic. Source: Arduino, [ABX00087 Schematics](https://docs.arduino.cc/resources/schematics/ABX00087-schematics.pdf), page 2 "LED MATRIX" (Silvio Navaretti). [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/) ``` ## Sample code - Directory: `docs/os-on-arduino/code/07_led_matrix` ```bash uv run pio run -d 07_led_matrix -t upload uv run pio device monitor -b 115200 ``` ## Source code The sketch for this chapter. Click a file name to expand. The code is mirrored automatically from the manuscript repository, so what you see here is always current. :::{dropdown} `src/main.cpp` — 72 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/src/main.cpp) ```{literalinclude} ../../../../code/07_led_matrix/src/main.cpp :language: cpp :linenos: ``` ::: :::{dropdown} `include/os_display.h` — 18 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/include/os_display.h) ```{literalinclude} ../../../../code/07_led_matrix/include/os_display.h :language: cpp :linenos: ``` ::: :::{dropdown} `include/os_fault.h` — 46 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/include/os_fault.h) ```{literalinclude} ../../../../code/07_led_matrix/include/os_fault.h :language: cpp :linenos: ``` ::: :::{dropdown} `include/os_kernel.h` — 75 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/include/os_kernel.h) ```{literalinclude} ../../../../code/07_led_matrix/include/os_kernel.h :language: cpp :linenos: ``` ::: :::{dropdown} `include/shell.h` — 23 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/include/shell.h) ```{literalinclude} ../../../../code/07_led_matrix/include/shell.h :language: cpp :linenos: ``` ::: :::{dropdown} `src/os_display.cpp` — 162 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/src/os_display.cpp) ```{literalinclude} ../../../../code/07_led_matrix/src/os_display.cpp :language: cpp :linenos: ``` ::: :::{dropdown} `src/os_fault.cpp` — 170 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/src/os_fault.cpp) ```{literalinclude} ../../../../code/07_led_matrix/src/os_fault.cpp :language: cpp :linenos: ``` ::: :::{dropdown} `src/os_kernel.cpp` — 241 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/src/os_kernel.cpp) ```{literalinclude} ../../../../code/07_led_matrix/src/os_kernel.cpp :language: cpp :linenos: ``` ::: :::{dropdown} `src/shell.cpp` — 190 lines :icon: code [Open on GitHub](https://github.com/iory/learning-os-from-arduino/blob/main/docs/os-on-arduino/code/07_led_matrix/src/shell.cpp) ```{literalinclude} ../../../../code/07_led_matrix/src/shell.cpp :language: cpp :linenos: ``` ::: ## On the board ```{figure} ../_static/ch07_kill.gif :name: fig-ch07-board :width: 80% The LED matrix on a real UNO R4 WiFi. The top half is the CPU load over the last 1.2 s, rewritten one column at a time from the left every 0.1 s; the rightmost two columns of the bottom half show the state of tasks 0–3. `kill 3` stops `Heavy` and the graph drops; `exec 3` brings it back. (Filmed with a webcam.) ``` ## In the simulator Without the board, the same firmware running in the {doc}`simulator <../getting-started/simulator>` looks like this. ```{figure} ../_static/sim_ch07_kill.gif :name: fig-ch07-sim :width: 80% The top half of the matrix is the CPU load over the last 1.2 s (rewritten one column at a time from the left every 0.1 s), and the rightmost two columns of the bottom half show the state of tasks 0–3. `kill 3` stops the busy `Heavy` task and the graph drains away; `exec 3` brings it back. (Recorded in the simulator.) ``` ## Improved version: `07_led_matrix_v2` (not in the book) In the book's code the load history in the top half is not drawn in time order and the bottom half barely lights up, so the display is hard to read. [`code/07_led_matrix_v2`](https://github.com/iory/build-your-own-arduino-rtos/tree/main/code/07_led_matrix_v2) redraws it as **bars of the current value**, like Linux's `htop` (a tool that shows CPU load as bars and lists each process's CPU usage; see the column below). The kernel is the same as the book's; only the display, the shell and the tasks change. It is not in the book's repository (learning-os-from-arduino), so get it from this site's repository and flash it: ```bash git clone https://github.com/iory/build-your-own-arduino-rtos.git cd build-your-own-arduino-rtos/code uv sync uv run pio run -d 07_led_matrix_v2 -t upload uv run pio device monitor -b 115200 ``` | Rows | Content (12 dots = 100%, updated every 0.5 s) | |---|---| | 0–1 | Total CPU load (everything except idle) | | 2 | Blank | | 3–7 | CPU% of LED / Light / Heavy / Shell / Display | Type `top` in the shell and the same numbers are redrawn in the terminal every 0.5 s (type `top` again to stop; you can still type `kill 3` and so on). Light now uses CPU too, so you can see the per-task bars add up to the total. ```{raw} html ``` Values read from `top` on a real UNO R4 WiFi: | Action | Total CPU | What you see | |---|---|---| | After boot | ~60% | Bars on Light (16%) and Heavy (40%) | | `kill 3` | ~24% | Heavy's bar disappears and the total shrinks | | `kill 2` | ~4% | Only Shell, drawing `top`, is left | | `exec 2` / `exec 3` | Back to normal | | ```{note} Heavy never sleeps, yet it only gets 40–50%. The book's scheduler runs idle in turn like any other task, so Heavy shares the CPU with idle. ``` (ch07-top-column)= ~~~{admonition} Column: `top` and `htop` :class: tip On Linux, `top` (procps-ng) lists the CPU usage of each running process, and `htop` is a friendlier take on it. The v2 matrix and its `top` command imitate the two. **What they show.** `htop`'s CPU meter at the top of the screen is a bar that stacks kinds of CPU time (nice, user, system, ...) in different colors ([htop(1), METERS](https://github.com/htop-dev/htop/blob/1daf6d9312d13f6edb4593340b9c3f65a7eadad4/htop.1.in)). In the list below it, `%CPU` is the process's share of the elapsed time since the last screen update ([top(1), %CPU](https://man7.org/linux/man-pages/man1/top.1.html)). In v2, rows 0–1 are the CPU meter and rows 3–7 are the list. **How often they redraw.** `htop` redraws every 1.5 s by default (`-d` is in tenths of a second and defaults to 15; [Settings.h](https://github.com/htop-dev/htop/blob/1daf6d9312d13f6edb4593340b9c3f65a7eadad4/Settings.h#L21)). procps-ng `top` also defaults to 1.5 s, or 3 s when built with its original defaults ([top.h](https://gitlab.com/procps-ng/procps/-/blob/55b7d01d2c6006362b1d221a41594c40b6d77640/src/top/top.h#L124-129)). v2 uses 0.5 s. **How CPU time is counted.** Linux basically adds one tick of CPU time to whichever task was running at each timer interrupt ([`account_process_tick()`](https://github.com/torvalds/linux/blob/f0100363d8c374bd8e9ea7c9ba02744f0b802ca4/kernel/sched/cputime.c#L683-L712)). The totals appear in `/proc/stat` ([proc_stat(5)](https://man7.org/linux/man-pages/man5/proc_stat.5.html)) and elsewhere, and `top` computes percentages from the difference between two reads. The book's OS does the same: on every SysTick interrupt it adds 10 ms to the task that was running (`SysTick_Handler` in `os_kernel.cpp`). v2 reads those counters every 0.5 s and takes the difference. Counting by ticks misses short jobs that finish between two ticks. That is why the LED task shows 0% (Linux can count by other means, depending on its configuration). **Stopping a process.** Press `k` in `top`, or `F9` or `k` in `htop`, to send a signal to the selected process ([top(1)](https://man7.org/linux/man-pages/man1/top.1.html), [htop(1)](https://github.com/htop-dev/htop/blob/1daf6d9312d13f6edb4593340b9c3f65a7eadad4/htop.1.in)). The book's shell `kill N` only suspends the task, so `exec N` resumes it. ~~~ (ch07-break)= ## Break it These experiments use the improved `07_led_matrix_v2` above. Its kernel is the same as the book's, and the matrix bars and `top` make the change in CPU % visible. **Predict before** you open each answer. The first one needs no code change: try it directly in the improved version in the browser. For the second, change the code, build it, and {ref}`drop firmware.elf into the browser version `. v2 lives in this site's repository, so work inside the `code/` you cloned with the steps in the improved-version section above. ```bash uv run pio run -d 07_led_matrix_v2 # drop 07_led_matrix_v2/.pio/build/uno_r4_wifi/firmware.elf into the browser version # to undo: git checkout -- 07_led_matrix_v2 ``` :::{note} The outputs in the answers were captured by actually running the code in QEMU, the same emulator the browser version uses. They have not been checked on the real board yet. The browser version runs heavy computation slower than the board, so the CPU % values may look slightly different there. ::: ### 1. The shell stops itself `kill 0` is refused with `Error: Cannot kill idle task (ID 0)`. What about `kill 4`? ID 4 is the shell itself, the one reading your commands. **Predict:** after `kill 4`, can you bring it back with `exec 4`? :::{dropdown} Answer **No.** After `kill 4`, not even the usual `Task 4 (Shell) suspended.` appears, and from then on nothing you type (`ps`, `exec 4`, `top`) gets a reply. ```text Mini OS Shell Type 'help' for commands. > kill 4 ``` When the task to suspend is the caller itself, `os_suspend_task()` calls `os_schedule()` on the spot and switches to another task. The shell stops before printing `suspended.` and never gets a turn again. `exec 4` never arrives, because the shell that would read it is the one that is stopped. To recover, reset the board (in the browser version, reload the page). Making `kill` refuse its own ID, as it already does for `kill 0`, prevents this. ::: ### 2. Take idle out of the rotation Type `ps` and Heavy, which runs without ever sleeping, gets only 41% of the CPU (in QEMU: idle 41%, Light 16%, Heavy 41%). The book's scheduler puts idle in the same rotation as every other task, so Heavy and idle share the CPU. So change `os_schedule()` in `src/os_kernel.cpp` to pick idle only when no other task can run. ```diff int next = g_current_task; do { next = (next + 1) % g_task_count; - if (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING) break; + if (next != 0 && (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING)) break; } while (next != g_current_task); + if (g_tasks[next].state != TASK_READY && g_tasks[next].state != TASK_RUNNING) next = 0; ``` **Predict:** what happens to Heavy's row on the matrix, and to the total bar at the top? :::{dropdown} Answer **The total bar fills all 12 dots, and Heavy's bar nearly doubles.** `ps` shows Heavy at 79% and idle at 0% (76% for Heavy in the browser version). ```text > ps ID NAME STATE CPU% -- ---- ----- ---- 0 idle READY 0% 1 LED BLOCKED 0% 2 Light BLOCKED 20% 3 Heavy READY 79% 4 Shell RUNNING 0% 5 Display BLOCKED 0% ``` The total bar is the share of everything except idle, so idle at 0% means 100%. While the other tasks sleep, idle and Heavy used to take turns; now only Heavy runs. Heavy also gets the time Light spends asleep, so apart from Light's 20%, almost everything goes to Heavy. If you leave `top` running in the browser version, Heavy drops to about 50%, because redrawing the table on the slow emulator costs the Shell about 30%. To compare Heavy's numbers, stop `top` and use `ps`. After `kill 3` stops Heavy and a few seconds pass, `ps` showed idle 74% and Light 25%. Idle now runs only when no other task can. LED shows 0% because it finishes its work in less than 10 ms (one tick) and goes back to sleep, which counting on every tick misses ({ref}`the column "top and htop" `). ::: ## Pitfalls (Collected from reader questions — ask via [GitHub Issues](https://github.com/iory/build-your-own-arduino-rtos/issues).)