Chapter 8: A Tiny Interpreter#
Sample code#
Directory:
docs/os-on-arduino/code/08_interpreter
uv run pio run -d 08_interpreter -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.
src/main.cpp — 53 lines
1// 07_interpreter.ino
2// 簡易インタプリタのデモ
3
4#include "os_kernel.h"
5#include "shell.h"
6#include "interpreter.h"
7#include "os_display.h"
8
9// Arduino UNO R4 WiFi は基板上に "L" と印字された LED を持っています。
10// 追加部品なしで動かせるよう、これを主 LED として使います。
11#define LED_PIN LED_BUILTIN
12
13void task_led(void)
14{
15 pinMode(LED_PIN, OUTPUT);
16 while (1) {
17 digitalWrite(LED_PIN, !digitalRead(LED_PIN));
18 os_sleep(500);
19 }
20}
21
22void setup()
23{
24 Serial.begin(115200);
25 while (!Serial) { }
26
27 Serial.println("=== Motion Interpreter Demo ===");
28 Serial.println("Type 'run' to enter script mode.");
29 Serial.println("Type 'run <command>' for one-line execution.\n");
30
31 Serial.println("Available commands:");
32 Serial.println(" FORWARD <dist> - Move forward");
33 Serial.println(" BACKWARD <dist> - Move backward");
34 Serial.println(" TURN <angle> - Turn");
35 Serial.println(" SERVO <ch> <deg> - Set servo angle");
36 Serial.println(" LED <pin> <0/1> - Control LED");
37 Serial.println(" DELAY <ms> - Delay");
38 Serial.println(" PRINT <text> - Print message");
39 Serial.println(" LOOP <n> - Start loop");
40 Serial.println(" END - End loop");
41 Serial.println();
42
43 interp_init();
44 os_init();
45
46 os_create_task(task_led, "LED", 512);
47 os_create_task(shell_task, "Shell", 1024);
48 os_create_task(display_task, "Display", 512);
49
50 os_start();
51}
52
53void loop() { }
include/interpreter.h — 36 lines
1// interpreter.h
2
3#ifndef INTERPRETER_H
4#define INTERPRETER_H
5
6#include <stdint.h>
7
8#define MAX_SCRIPT_LINES 32
9#define MAX_LINE_LENGTH 32
10
11// 実行結果
12typedef enum {
13 EXEC_OK,
14 EXEC_ERROR,
15 EXEC_END
16} ExecResult;
17
18// インタプリタ初期化
19void interp_init(void);
20
21// スクリプトをロード(複数行を改行で区切って渡す)
22int interp_load(const char *script);
23
24// 1行実行
25ExecResult interp_step(void);
26
27// スクリプト全体を実行
28int interp_run(void);
29
30// 実行中かどうか
31bool interp_is_running(void);
32
33// インタプリタタスク
34void interpreter_task(void);
35
36#endif // INTERPRETER_H
include/os_display.h — 18 lines
1// os_display.h
2
3#ifndef OS_DISPLAY_H
4#define OS_DISPLAY_H
5
6#include "os_kernel.h"
7#include "Arduino_LED_Matrix.h"
8
9// 表示を初期化
10void display_init(void);
11
12// 表示を更新(定期的に呼ぶ)
13void display_update(void);
14
15// 表示タスク
16void display_task(void);
17
18#endif // OS_DISPLAY_H
include/os_fault.h — 46 lines
1/**
2 * os_fault.h - Fault Handler Header
3 * Chapter 6: Memory Protection
4 */
5
6#ifndef OS_FAULT_H
7#define OS_FAULT_H
8
9#include <stdint.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15// Fault information structure
16typedef struct {
17 uint32_t r0;
18 uint32_t r1;
19 uint32_t r2;
20 uint32_t r3;
21 uint32_t r12;
22 uint32_t lr;
23 uint32_t pc;
24 uint32_t xpsr;
25} FaultFrame;
26
27// Initialize fault handling
28void fault_init(void);
29
30// Get fault reason string
31const char* fault_get_reason(void);
32
33// Check if a fault occurred
34int fault_occurred(void);
35
36// Clear fault flag
37void fault_clear(void);
38
39// Print pending fault info (call from task context, not from ISR)
40void fault_print_pending(void);
41
42#ifdef __cplusplus
43}
44#endif
45
46#endif
include/os_kernel.h — 75 lines
1/**
2 * os_kernel.h - Mini OS Kernel Header
3 * Chapter 6: Memory Protection with HardFault Handler
4 */
5
6#ifndef OS_KERNEL_H
7#define OS_KERNEL_H
8
9#include <stdint.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15typedef enum {
16 TASK_READY,
17 TASK_RUNNING,
18 TASK_BLOCKED,
19 TASK_SUSPENDED,
20 TASK_TERMINATED
21} TaskState;
22
23typedef struct {
24 uint32_t *sp;
25 uint32_t *stack_base;
26 uint32_t stack_size;
27 TaskState state;
28 uint8_t id;
29 uint8_t priority;
30 char name[16];
31 void (*entry)(void);
32 uint32_t wake_tick;
33 uint32_t cpu_ticks;
34} TCB;
35
36#define MAX_TASKS 8
37#define DEFAULT_STACK_SIZE 512
38// タスク用スタックをまとめて置く領域。os_create_task() は
39// ここから要求されたぶんだけ切り出す。
40// 既定より大きなスタックを要求するタスク(シェルなど)があるため、
41// MAX_TASKS * DEFAULT_STACK_SIZE では足りない。余裕を持たせる。
42#define STACK_POOL_SIZE (MAX_TASKS * DEFAULT_STACK_SIZE + 2048)
43#define IDLE_STACK_SIZE 256
44#define TIME_SLICE_MS 10
45
46void os_init(void);
47int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size);
48void os_start(void);
49void os_yield(void);
50void os_sleep(uint32_t ms);
51
52void os_suspend_task(int id);
53void os_resume_task(int id);
54void os_terminate_task(int id);
55
56int os_get_task_count(void);
57const char* os_get_task_name(int id);
58TaskState os_get_task_state(int id);
59int os_get_current_task(void);
60int os_get_cpu_usage(int task_id);
61uint32_t os_get_task_cpu_ticks(int task_id);
62uint32_t os_get_tick(void);
63
64extern TCB g_tasks[MAX_TASKS];
65extern volatile int g_task_count;
66extern volatile int g_current_task;
67extern volatile int g_next_task;
68extern volatile uint32_t g_system_ticks;
69extern const uint32_t g_tcb_size;
70
71#ifdef __cplusplus
72}
73#endif
74
75#endif
include/servo_pwm.h — 61 lines
1/**
2 * Simple PWM-based Servo Control for Arduino Uno R4 WiFi
3 *
4 * Since the standard Servo library is not available in PlatformIO for Renesas,
5 * this provides a simple software PWM implementation.
6 */
7
8#ifndef SERVO_PWM_H
9#define SERVO_PWM_H
10
11#include <Arduino.h>
12
13class ServoPWM {
14private:
15 int pin;
16 int angle;
17 bool attached;
18
19 void writeMicroseconds(int us) {
20 if (!attached) return;
21 // Standard servo pulse: 500-2500us for 0-180 degrees
22 // Period: 20ms (50Hz)
23 digitalWrite(pin, HIGH);
24 delayMicroseconds(us);
25 digitalWrite(pin, LOW);
26 }
27
28public:
29 ServoPWM() : pin(-1), angle(90), attached(false) {}
30
31 void attach(int p) {
32 pin = p;
33 pinMode(pin, OUTPUT);
34 attached = true;
35 }
36
37 void detach() {
38 attached = false;
39 }
40
41 void write(int ang) {
42 if (!attached) return;
43 angle = constrain(ang, 0, 180);
44 // Map angle (0-180) to pulse width (500-2500us)
45 int us = map(angle, 0, 180, 500, 2500);
46 writeMicroseconds(us);
47 }
48
49 int read() {
50 return angle;
51 }
52
53 // Call this in loop() to maintain servo position
54 void refresh() {
55 if (!attached) return;
56 int us = map(angle, 0, 180, 500, 2500);
57 writeMicroseconds(us);
58 }
59};
60
61#endif // SERVO_PWM_H
include/shell.h — 23 lines
1/**
2 * shell.h - Interactive Shell Header
3 * Chapter 5: Interactive Shell
4 */
5
6#ifndef SHELL_H
7#define SHELL_H
8
9#ifdef __cplusplus
10extern "C" {
11#endif
12
13// Shell task entry point
14void shell_task(void);
15
16// Process a single command
17void shell_process_command(char *cmd);
18
19#ifdef __cplusplus
20}
21#endif
22
23#endif
src/interpreter.cpp — 288 lines
1// interpreter.cpp
2
3#include "interpreter.h"
4#include "os_kernel.h"
5#include <Arduino.h>
6#include <string.h>
7#include <stdlib.h>
8#include <Servo.h>
9
10// スクリプト格納
11static char g_script[MAX_SCRIPT_LINES][MAX_LINE_LENGTH];
12static int g_script_lines = 0;
13static int g_current_line = 0;
14static bool g_running = false;
15
16// ループスタック
17#define MAX_LOOP_DEPTH 4
18static int g_loop_stack[MAX_LOOP_DEPTH];
19static int g_loop_count[MAX_LOOP_DEPTH];
20static int g_loop_depth = 0;
21
22// ハードウェア
23static Servo g_servos[2];
24static bool g_servo_attached[2] = {false, false};
25
26// サーボピン
27#define SERVO_PIN_0 9
28#define SERVO_PIN_1 10
29
30// モーター制御(ダミー実装)
31static void motor_forward(int distance)
32{
33 Serial.print("[Motor] Forward ");
34 Serial.print(distance);
35 Serial.println(" units");
36 // 実際のモーター制御はここに実装
37 os_sleep(distance * 10); // 仮の待機
38}
39
40static void motor_backward(int distance)
41{
42 Serial.print("[Motor] Backward ");
43 Serial.print(distance);
44 Serial.println(" units");
45 os_sleep(distance * 10);
46}
47
48static void motor_turn(int angle)
49{
50 Serial.print("[Motor] Turn ");
51 Serial.print(angle);
52 Serial.println(" degrees");
53 os_sleep(abs(angle) * 10);
54}
55
56// サーボ制御
57static void servo_set(int channel, int angle)
58{
59 if (channel < 0 || channel > 1) {
60 Serial.println("[Error] Invalid servo channel");
61 return;
62 }
63
64 // 必要に応じてアタッチ
65 if (!g_servo_attached[channel]) {
66 int pin = (channel == 0) ? SERVO_PIN_0 : SERVO_PIN_1;
67 g_servos[channel].attach(pin);
68 g_servo_attached[channel] = true;
69 }
70
71 // 角度を制限
72 if (angle < 0) angle = 0;
73 if (angle > 180) angle = 180;
74
75 g_servos[channel].write(angle);
76
77 Serial.print("[Servo] Ch");
78 Serial.print(channel);
79 Serial.print(" = ");
80 Serial.print(angle);
81 Serial.println(" deg");
82}
83
84// LED制御
85static void led_set(int pin, int state)
86{
87 pinMode(pin, OUTPUT);
88 digitalWrite(pin, state ? HIGH : LOW);
89
90 Serial.print("[LED] Pin ");
91 Serial.print(pin);
92 Serial.print(" = ");
93 Serial.println(state ? "ON" : "OFF");
94}
95
96// 空白をスキップ
97static const char* skip_spaces(const char *cursor)
98{
99 while (*cursor == ' ' || *cursor == '\t') cursor++;
100 return cursor;
101}
102
103// 数値を解析
104static int parse_int(const char **cursor_ptr)
105{
106 const char *cursor = skip_spaces(*cursor_ptr);
107 int sign = 1;
108 int value = 0;
109
110 if (*cursor == '-') {
111 sign = -1;
112 cursor++;
113 }
114
115 while (*cursor >= '0' && *cursor <= '9') {
116 value = value * 10 + (*cursor - '0');
117 cursor++;
118 }
119
120 *cursor_ptr = cursor;
121 return sign * value;
122}
123
124// 1行を実行
125static ExecResult execute_line(const char *line)
126{
127 const char *cursor = skip_spaces(line);
128
129 // 空行やコメント
130 if (*cursor == '\0' || *cursor == '#') {
131 return EXEC_OK;
132 }
133
134 // コマンドを取得
135 char cmd[16] = {0};
136 int i = 0;
137 while (*cursor && *cursor != ' ' && *cursor != '\t' && i < 15) {
138 cmd[i++] = *cursor++;
139 }
140
141 // コマンド実行
142 if (strcmp(cmd, "FORWARD") == 0) {
143 int dist = parse_int(&cursor);
144 motor_forward(dist);
145 }
146 else if (strcmp(cmd, "BACKWARD") == 0) {
147 int dist = parse_int(&cursor);
148 motor_backward(dist);
149 }
150 else if (strcmp(cmd, "TURN") == 0) {
151 int angle = parse_int(&cursor);
152 motor_turn(angle);
153 }
154 else if (strcmp(cmd, "SERVO") == 0) {
155 int ch = parse_int(&cursor);
156 int angle = parse_int(&cursor);
157 servo_set(ch, angle);
158 }
159 else if (strcmp(cmd, "LED") == 0) {
160 int pin = parse_int(&cursor);
161 int state = parse_int(&cursor);
162 led_set(pin, state);
163 }
164 else if (strcmp(cmd, "DELAY") == 0) {
165 int ms = parse_int(&cursor);
166 Serial.print("[Delay] ");
167 Serial.print(ms);
168 Serial.println(" ms");
169 os_sleep(ms);
170 }
171 else if (strcmp(cmd, "PRINT") == 0) {
172 cursor = skip_spaces(cursor);
173 Serial.print("[Print] ");
174 Serial.println(cursor);
175 }
176 else if (strcmp(cmd, "LOOP") == 0) {
177 if (g_loop_depth >= MAX_LOOP_DEPTH) {
178 Serial.println("[Error] Loop too deep");
179 return EXEC_ERROR;
180 }
181 int count = parse_int(&cursor);
182 g_loop_stack[g_loop_depth] = g_current_line;
183 g_loop_count[g_loop_depth] = count;
184 g_loop_depth++;
185 }
186 else if (strcmp(cmd, "END") == 0) {
187 if (g_loop_depth <= 0) {
188 Serial.println("[Error] END without LOOP");
189 return EXEC_ERROR;
190 }
191 g_loop_count[g_loop_depth - 1]--;
192 if (g_loop_count[g_loop_depth - 1] > 0) {
193 // ループ継続
194 g_current_line = g_loop_stack[g_loop_depth - 1];
195 } else {
196 // ループ終了
197 g_loop_depth--;
198 }
199 }
200 else {
201 Serial.print("[Error] Unknown command: ");
202 Serial.println(cmd);
203 return EXEC_ERROR;
204 }
205
206 return EXEC_OK;
207}
208
209void interp_init(void)
210{
211 g_script_lines = 0;
212 g_current_line = 0;
213 g_running = false;
214 g_loop_depth = 0;
215}
216
217int interp_load(const char *script)
218{
219 interp_init();
220
221 const char *cursor = script;
222 int line = 0;
223
224 while (*cursor && line < MAX_SCRIPT_LINES) {
225 int i = 0;
226 while (*cursor && *cursor != '\n' && i < MAX_LINE_LENGTH - 1) {
227 g_script[line][i++] = *cursor++;
228 }
229 g_script[line][i] = '\0';
230
231 // 空行でなければカウント
232 if (i > 0) {
233 line++;
234 }
235
236 if (*cursor == '\n') cursor++;
237 }
238
239 g_script_lines = line;
240 g_current_line = 0;
241
242 Serial.print("[Interpreter] Loaded ");
243 Serial.print(g_script_lines);
244 Serial.println(" lines");
245
246 return g_script_lines;
247}
248
249ExecResult interp_step(void)
250{
251 if (g_current_line >= g_script_lines) {
252 g_running = false;
253 return EXEC_END;
254 }
255
256 g_running = true;
257 ExecResult result = execute_line(g_script[g_current_line]);
258 g_current_line++;
259
260 return result;
261}
262
263int interp_run(void)
264{
265 Serial.println("\n[Interpreter] Starting script...");
266
267 int executed = 0;
268 while (g_current_line < g_script_lines) {
269 ExecResult result = interp_step();
270 if (result == EXEC_ERROR) {
271 Serial.println("[Interpreter] Script error, aborting");
272 return -1;
273 }
274 executed++;
275 }
276
277 Serial.print("[Interpreter] Script completed: ");
278 Serial.print(executed);
279 Serial.println(" commands executed");
280
281 g_running = false;
282 return executed;
283}
284
285bool interp_is_running(void)
286{
287 return g_running;
288}
src/os_display.cpp — 162 lines
1// os_display.cpp
2
3#include "os_display.h"
4#include <Arduino.h>
5
6// LEDマトリクスオブジェクト
7static ArduinoLEDMatrix matrix;
8
9// フレームバッファ(8行×12列)
10static uint8_t frame[8][12];
11
12// CPU負荷履歴(12サンプル)
13static uint8_t cpu_history[12];
14static int history_index = 0;
15
16// タスク実行履歴(4タスク × 8サンプル)
17#define TASK_HISTORY_LEN 8
18static int task_history[MAX_TASKS][TASK_HISTORY_LEN];
19static int task_history_index = 0;
20
21void display_init(void)
22{
23 matrix.begin();
24
25 // フレームバッファをクリア
26 memset(frame, 0, sizeof(frame));
27 memset(cpu_history, 0, sizeof(cpu_history));
28 memset(task_history, -1, sizeof(task_history));
29}
30
31// CPU負荷をパーセントから高さ(0-4)に変換
32static int cpu_to_height(int cpu_percent)
33{
34 if (cpu_percent >= 80) return 4;
35 if (cpu_percent >= 60) return 3;
36 if (cpu_percent >= 40) return 2;
37 if (cpu_percent >= 20) return 1;
38 return 0;
39}
40
41// フレームバッファにCPU負荷グラフを描画
42static void draw_cpu_graph(void)
43{
44 // 上半分(行0-3)にCPU負荷を描画
45 for (int col = 0; col < 12; col++) {
46 int height = cpu_to_height(cpu_history[col]);
47
48 for (int row = 0; row < 4; row++) {
49 // 下から上に向かって描画(行3が底、行0が頂上)
50 frame[3 - row][col] = (row < height) ? 1 : 0;
51 }
52 }
53}
54
55// フレームバッファにタスク実行状態を描画
56static void draw_task_status(void)
57{
58 int task_count = os_get_task_count();
59
60 // 最大4タスク表示(行4-7)
61 int display_tasks = (task_count > 4) ? 4 : task_count;
62
63 for (int t = 0; t < display_tasks; t++) {
64 int row = 4 + t;
65
66 // 履歴を描画
67 for (int i = 0; i < TASK_HISTORY_LEN; i++) {
68 int hist_idx = (task_history_index + i + 1) % TASK_HISTORY_LEN;
69 int col = i;
70
71 if (task_history[t][hist_idx] == t) {
72 // このタスクが実行中だった
73 frame[row][col] = 1;
74 } else {
75 frame[row][col] = 0;
76 }
77 }
78
79 // 現在の状態を示すインジケータ(右端)
80 TaskState state = os_get_task_state(t);
81 switch (state) {
82 case TASK_RUNNING:
83 frame[row][10] = 1;
84 frame[row][11] = 1;
85 break;
86 case TASK_READY:
87 frame[row][10] = 1;
88 frame[row][11] = 0;
89 break;
90 case TASK_BLOCKED:
91 frame[row][10] = 0;
92 frame[row][11] = 1;
93 break;
94 default:
95 frame[row][10] = 0;
96 frame[row][11] = 0;
97 break;
98 }
99 }
100}
101
102// 区切り線を描画
103static void draw_separator(void)
104{
105 // CPU負荷領域とタスク状態領域の間に区切り線
106 // 列8, 9を区切りとして使用
107 for (int row = 0; row < 8; row++) {
108 frame[row][8] = 0;
109 frame[row][9] = (row == 3) ? 1 : 0; // 行3だけ点灯
110 }
111}
112
113void display_update(void)
114{
115 // CPU 使用率を更新(アイドル以外の割合)。
116 // os_get_cpu_usage() は呼ぶたびに基準時刻を更新するので、ここから呼ぶと
117 // シェルの ps の計測窓を壊してしまう。生カウンタから自前で差分を取る。
118 static uint32_t last_total = 0;
119 static uint32_t last_idle = 0;
120
121 uint32_t total = os_get_tick();
122 uint32_t idle = os_get_task_cpu_ticks(0);
123 uint32_t d_total = total - last_total;
124 uint32_t d_idle = idle - last_idle;
125 last_total = total;
126 last_idle = idle;
127
128 int total_cpu = (d_total == 0) ? 0
129 : (int)(100u - (d_idle * 100u) / d_total);
130
131 // 履歴に追加
132 cpu_history[history_index] = total_cpu;
133 history_index = (history_index + 1) % 12;
134
135 // 現在実行中のタスクを記録
136 int current = os_get_current_task();
137 if (current >= 0 && current < MAX_TASKS) {
138 task_history[current][task_history_index] = current;
139 }
140 task_history_index = (task_history_index + 1) % TASK_HISTORY_LEN;
141
142 // フレームバッファをクリア
143 memset(frame, 0, sizeof(frame));
144
145 // 各要素を描画
146 draw_cpu_graph();
147 draw_task_status();
148
149 // マトリクスに表示
150 matrix.renderBitmap(frame, 8, 12);
151}
152
153// 表示更新タスク
154void display_task(void)
155{
156 display_init();
157
158 while (1) {
159 display_update();
160 os_sleep(100); // 100msごとに更新
161 }
162}
src/os_fault.cpp — 170 lines
1/**
2 * os_fault.cpp - Fault Handler Implementation
3 * Chapter 6: Memory Protection
4 */
5
6#include "os_fault.h"
7#include "os_kernel.h"
8#include <Arduino.h>
9
10static volatile int g_fault_occurred = 0;
11static volatile const char* g_fault_reason = "";
12static volatile int g_faulted_task = -1;
13
14// Forward declarations for vector table override
15extern "C" __attribute__((naked)) void HardFault_Handler(void);
16extern "C" void PendSV_Handler(void);
17extern "C" void SysTick_Handler(void);
18
19void fault_init(void)
20{
21 // Enable divide by zero trap
22 SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
23
24 // Do NOT enable separate UsageFault/BusFault/MemFault handlers.
25 // By leaving them disabled, all faults escalate to HardFault,
26 // which we handle via the VTOR override above.
27
28 // The Arduino framework copies the vector table to RAM (VTOR points to RAM).
29 // We must overwrite the HardFault entry in the RAM vector table directly.
30 // Also override PendSV and SysTick entries for the OS scheduler.
31 uint32_t *vtor = (uint32_t *)SCB->VTOR;
32 vtor[3] = (uint32_t)HardFault_Handler; // Vector #3 = HardFault
33 vtor[14] = (uint32_t)PendSV_Handler; // Vector #14 = PendSV
34 vtor[15] = (uint32_t)SysTick_Handler; // Vector #15 = SysTick
35
36 g_fault_occurred = 0;
37 g_fault_reason = "";
38}
39
40const char* fault_get_reason(void)
41{
42 return (const char*)g_fault_reason;
43}
44
45int fault_occurred(void)
46{
47 return g_fault_occurred;
48}
49
50void fault_clear(void)
51{
52 g_fault_occurred = 0;
53 g_fault_reason = "";
54}
55
56static void analyze_fault(void)
57{
58 uint32_t cfsr = SCB->CFSR;
59
60 // Check Usage Fault Status Register (UFSR)
61 if (cfsr & SCB_CFSR_DIVBYZERO_Msk) {
62 g_fault_reason = "Divide by zero";
63 } else if (cfsr & SCB_CFSR_UNALIGNED_Msk) {
64 g_fault_reason = "Unaligned access";
65 } else if (cfsr & SCB_CFSR_UNDEFINSTR_Msk) {
66 g_fault_reason = "Undefined instruction";
67 } else if (cfsr & SCB_CFSR_INVSTATE_Msk) {
68 g_fault_reason = "Invalid state (Thumb bit)";
69 } else if (cfsr & SCB_CFSR_INVPC_Msk) {
70 g_fault_reason = "Invalid PC";
71 }
72 // Check Bus Fault Status Register (BFSR)
73 else if (cfsr & SCB_CFSR_PRECISERR_Msk) {
74 g_fault_reason = "Precise data bus error";
75 } else if (cfsr & SCB_CFSR_IMPRECISERR_Msk) {
76 g_fault_reason = "Imprecise data bus error";
77 } else if (cfsr & SCB_CFSR_IBUSERR_Msk) {
78 g_fault_reason = "Instruction bus error";
79 }
80 // Check Memory Management Fault Status Register (MMFSR)
81 else if (cfsr & SCB_CFSR_DACCVIOL_Msk) {
82 g_fault_reason = "Data access violation";
83 } else if (cfsr & SCB_CFSR_IACCVIOL_Msk) {
84 g_fault_reason = "Instruction access violation";
85 }
86 else {
87 g_fault_reason = "Unknown fault";
88 }
89
90 // Clear fault flags
91 SCB->CFSR = cfsr;
92}
93
94// Safe landing pad for terminated tasks after fault recovery
95extern "C" void _fault_task_sink(void)
96{
97 while (1) {
98 __WFI();
99 }
100}
101
102// Flag to defer fault message printing to a safe context
103static volatile int g_fault_pending_print = 0;
104static volatile uint32_t g_fault_pc = 0;
105
106extern "C" void HardFault_Handler_C(FaultFrame *frame)
107{
108 g_fault_occurred = 1;
109 g_faulted_task = g_current_task;
110 g_fault_pc = frame->pc;
111
112 analyze_fault();
113
114 // Terminate the faulted task and redirect PC to safe sink
115 if (g_faulted_task > 0) {
116 os_terminate_task(g_faulted_task);
117
118 // Rewrite the stacked PC to a safe infinite loop so that
119 // returning from the fault doesn't re-execute the faulting
120 // instruction. The SysTick scheduler will switch away from
121 // this terminated task on the next tick.
122 frame->pc = (uint32_t)_fault_task_sink;
123 frame->xpsr = 0x01000000; // Thumb bit set
124
125 // Defer printing to next safe context
126 g_fault_pending_print = 1;
127 return;
128 }
129
130 // If idle task or no task crashed, halt
131 while (1) { }
132}
133
134// Call this from a task (e.g., shell) to print deferred fault info
135void fault_print_pending(void)
136{
137 if (!g_fault_pending_print) return;
138 g_fault_pending_print = 0;
139
140 Serial.println();
141 Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
142 Serial.println("!!! FAULT DETECTED !!!");
143 Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
144 Serial.println();
145 Serial.print("Task ");
146 Serial.print(g_faulted_task);
147 Serial.print(" (");
148 Serial.print(os_get_task_name(g_faulted_task));
149 Serial.println(") crashed!");
150 Serial.print("Reason: ");
151 Serial.println((const char*)g_fault_reason);
152 Serial.print("PC at fault: 0x");
153 Serial.println(g_fault_pc, HEX);
154 Serial.println("Task terminated. Other tasks continue.");
155 Serial.println();
156}
157
158extern "C" __attribute__((naked)) void HardFault_Handler(void)
159{
160 __asm volatile (
161 " TST LR, #4 \n" // Check which stack was used
162 " ITE EQ \n"
163 " MRSEQ R0, MSP \n" // MSP if LR bit 2 is 0
164 " MRSNE R0, PSP \n" // PSP if LR bit 2 is 1
165 " PUSH {LR} \n" // Save EXC_RETURN
166 " BL HardFault_Handler_C \n" // Call C handler (preserves LR on stack)
167 " POP {LR} \n" // Restore EXC_RETURN
168 " BX LR \n" // Return from exception properly
169 );
170}
src/os_kernel.cpp — 241 lines
1/**
2 * os_kernel.cpp - Mini OS Kernel Implementation
3 * Chapter 6: Memory Protection
4 */
5
6#include "os_kernel.h"
7#include <Arduino.h>
8#include <string.h>
9
10TCB g_tasks[MAX_TASKS];
11volatile int g_task_count = 0;
12volatile int g_current_task = -1;
13volatile int g_next_task = -1;
14volatile uint32_t g_system_ticks = 0;
15
16const uint32_t g_tcb_size = sizeof(TCB);
17
18static uint32_t g_stack_pool[STACK_POOL_SIZE / 4];
19static uint32_t g_stack_used; // 切り出し済みのワード数
20
21static void idle_task(void) { while (1) __WFI(); }
22
23// タスク関数が return したときの受け皿。例外フレームの LR スロットは
24// ここを指す (第3章 3.4)。EXC_RETURN を置く場所ではない。
25static void task_exit(void)
26{
27 g_tasks[g_current_task].state = TASK_TERMINATED;
28 os_yield();
29 while (1) { } // ここには到達しない
30}
31
32static uint32_t* init_task_stack(uint32_t *stack_top, void (*entry)(void))
33{
34 uint32_t *sp = stack_top;
35 *(--sp) = 0x01000000;
36 *(--sp) = (uint32_t)entry;
37 *(--sp) = (uint32_t)task_exit; // LR: タスクが return したときの戻り先
38 *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
39 // FPU を使ったタスクは例外フレームが拡張される(FP レジスタ込み)。
40 // どちらのフレームで中断したかは EXC_RETURN の値に現れるため、
41 // タスクごとに EXC_RETURN も保存する。初期値は「PSP・基本フレーム」。
42 *(--sp) = 0xFFFFFFFD;
43 *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
44 *(--sp) = 0; *(--sp) = 0; *(--sp) = 0; *(--sp) = 0;
45 return sp;
46}
47
48void os_init(void)
49{
50 memset(g_tasks, 0, sizeof(g_tasks));
51 g_task_count = 0;
52 g_current_task = -1;
53 g_next_task = -1;
54 g_system_ticks = 0;
55 os_create_task(idle_task, "idle", IDLE_STACK_SIZE);
56}
57
58int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size)
59{
60 if (g_task_count >= MAX_TASKS) return -1;
61 int id = g_task_count;
62 TCB *task = &g_tasks[id];
63 task->id = id;
64 task->state = TASK_READY;
65 task->priority = id;
66 task->entry = entry;
67 task->wake_tick = 0;
68 task->cpu_ticks = 0;
69 strncpy(task->name, name, sizeof(task->name) - 1);
70 if (stack_size == 0) {
71 stack_size = DEFAULT_STACK_SIZE;
72 }
73 uint32_t words = (stack_size + 3) / 4;
74 if (g_stack_used + words > STACK_POOL_SIZE / 4) {
75 return -1; // プールが尽きた。黙って縮めず、失敗として返す
76 }
77 g_stack_used += words;
78 task->stack_size = stack_size;
79 task->stack_base = &g_stack_pool[g_stack_used]; // スタックは上端から下へ伸びる
80 task->sp = init_task_stack(task->stack_base, entry);
81 g_task_count++;
82 return id;
83}
84
85void os_schedule(void)
86{
87 for (int i = 0; i < g_task_count; i++) {
88 if (g_tasks[i].state == TASK_BLOCKED && g_system_ticks >= g_tasks[i].wake_tick) {
89 g_tasks[i].state = TASK_READY;
90 }
91 }
92
93 int next = g_current_task;
94 do {
95 next = (next + 1) % g_task_count;
96 if (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING) break;
97 } while (next != g_current_task);
98
99 if (next != g_current_task || g_current_task < 0) {
100 if (g_current_task >= 0 && g_tasks[g_current_task].state == TASK_RUNNING) {
101 g_tasks[g_current_task].state = TASK_READY;
102 }
103 g_tasks[next].state = TASK_RUNNING;
104 g_next_task = next;
105 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
106 }
107}
108
109void os_yield(void) { os_schedule(); }
110void os_sleep(uint32_t ms) {
111 if (g_current_task > 0) {
112 g_tasks[g_current_task].state = TASK_BLOCKED;
113 g_tasks[g_current_task].wake_tick = g_system_ticks + ms;
114 os_schedule();
115 }
116}
117void os_suspend_task(int id) {
118 if (id > 0 && id < g_task_count) {
119 g_tasks[id].state = TASK_SUSPENDED;
120 if (id == g_current_task) os_schedule();
121 }
122}
123void os_resume_task(int id) {
124 if (id > 0 && id < g_task_count && g_tasks[id].state == TASK_SUSPENDED) {
125 g_tasks[id].state = TASK_READY;
126 }
127}
128void os_terminate_task(int id) {
129 if (id > 0 && id < g_task_count) {
130 g_tasks[id].state = TASK_TERMINATED;
131 if (id == g_current_task) os_schedule();
132 }
133}
134
135uint32_t os_get_tick(void) { return g_system_ticks; }
136int os_get_task_count(void) { return g_task_count; }
137const char* os_get_task_name(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].name : ""; }
138TaskState os_get_task_state(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].state : TASK_TERMINATED; }
139int os_get_current_task(void) { return g_current_task; }
140
141void os_start(void)
142{
143 NVIC_SetPriority(PendSV_IRQn, 0xFF);
144 SysTick_Config(SystemCoreClock / 1000 * TIME_SLICE_MS);
145 g_current_task = -1;
146 g_next_task = 0;
147 // Do NOT switch CONTROL to PSP here — PSP is not yet initialized.
148 // PendSV returns with EXC_RETURN=0xFFFFFFFD, which automatically
149 // switches thread mode to PSP after loading the first task's context.
150 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
151 while (1) { }
152}
153
154extern "C" void SysTick_Handler(void) {
155 // 実際に経過した時間を、いま走っていたタスクの取り分として計上する。
156 // os_schedule() 側で数えると os_yield() の回数まで混ざってしまう。
157 if (g_current_task >= 0) {
158 g_tasks[g_current_task].cpu_ticks += TIME_SLICE_MS;
159 }
160 g_system_ticks += TIME_SLICE_MS;
161 os_schedule();
162}
163
164extern "C" __attribute__((naked)) void PendSV_Handler(void)
165{
166 __asm volatile (
167 " MRS R0, PSP \n"
168 " LDR R1, =g_current_task \n"
169 " LDR R2, [R1] \n"
170 " CMP R2, #0 \n"
171 " BLT _skip_save \n"
172 // FPU を使って中断したタスク(EXC_RETURN の bit4 が 0)は、
173 // ハードウェアが積まない S16-S31 もここで退避する
174 " TST LR, #0x10 \n"
175 " IT EQ \n"
176 " VSTMDBEQ R0!, {S16-S31} \n"
177 // R4-R11 と EXC_RETURN(LR) を保存する
178 " STMDB R0!, {R4-R11, LR} \n"
179 " LDR R3, =g_tasks \n"
180 " LDR R4, =g_tcb_size \n"
181 " LDR R4, [R4] \n"
182 " MUL R2, R2, R4 \n"
183 " ADD R3, R3, R2 \n"
184 " STR R0, [R3] \n"
185 "_skip_save: \n"
186 " LDR R1, =g_next_task \n"
187 " LDR R2, [R1] \n"
188 " LDR R1, =g_current_task \n"
189 " STR R2, [R1] \n"
190 " LDR R3, =g_tasks \n"
191 " LDR R4, =g_tcb_size \n"
192 " LDR R4, [R4] \n"
193 " MUL R2, R2, R4 \n"
194 " ADD R3, R3, R2 \n"
195 " LDR R0, [R3] \n"
196 " LDMIA R0!, {R4-R11, LR} \n"
197 // FPU を使って中断していたタスクなら S16-S31 も復元する
198 " TST LR, #0x10 \n"
199 " IT EQ \n"
200 " VLDMIAEQ R0!, {S16-S31} \n"
201 " MSR PSP, R0 \n"
202 " BX LR \n"
203 );
204}
205
206// CPU使用率を取得(パーセント)
207// 直前にこの関数を呼んだ時点からの差分で計算する。
208// ps のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
209// CPU 使用カウンタの生値。os_get_cpu_usage() と違って状態を持たないので、
210// 何度呼んでも他の呼び手の計測窓を壊さない (第4章 4.7)。
211uint32_t os_get_task_cpu_ticks(int task_id)
212{
213 if (task_id < 0 || task_id >= g_task_count) {
214 return 0;
215 }
216 return g_tasks[task_id].cpu_ticks;
217}
218
219int os_get_cpu_usage(int task_id)
220{
221 if (task_id < 0 || task_id >= g_task_count) {
222 return -1;
223 }
224
225 static uint32_t last_total[MAX_TASKS] = {0};
226 static uint32_t last_task_ticks[MAX_TASKS] = {0};
227
228 uint32_t total = g_system_ticks;
229 uint32_t delta_total = total - last_total[task_id];
230
231 if (delta_total == 0) {
232 return 0;
233 }
234
235 uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
236
237 last_total[task_id] = total;
238 last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
239
240 return (delta_task * 100) / delta_total;
241}
src/shell.cpp — 237 lines
1/**
2 * shell.cpp - Interactive Shell Implementation
3 * Chapter 5: Interactive Shell
4 */
5
6#include "shell.h"
7#include "os_kernel.h"
8#include "os_fault.h"
9#include "interpreter.h"
10#include <Arduino.h>
11#include <string.h>
12
13#define CMD_BUF_SIZE 64
14
15static char cmd_buf[CMD_BUF_SIZE];
16static int cmd_pos = 0;
17
18static void cmd_run(const char *arg);
19
20static const char* state_to_str(TaskState state)
21{
22 switch (state) {
23 case TASK_READY: return "READY";
24 case TASK_RUNNING: return "RUNNING";
25 case TASK_BLOCKED: return "BLOCKED";
26 case TASK_SUSPENDED: return "SUSPEND";
27 case TASK_TERMINATED: return "TERM";
28 default: return "?";
29 }
30}
31
32static void cmd_help(void)
33{
34 Serial.println("Available commands:");
35 Serial.println(" ps - List all tasks");
36 Serial.println(" kill N - Suspend task N");
37 Serial.println(" exec N - Resume task N");
38 Serial.println(" info - System information");
39 Serial.println(" reboot - Reboot system");
40 Serial.println(" help - Show this help");
41}
42
43static void cmd_ps(void)
44{
45 Serial.println();
46 Serial.println("ID NAME STATE CPU%");
47 Serial.println("-- ---- ----- ----");
48
49 for (int i = 0; i < os_get_task_count(); i++) {
50 Serial.print(i);
51 Serial.print(" ");
52
53 const char *name = os_get_task_name(i);
54 Serial.print(name);
55 for (int j = strlen(name); j < 15; j++) Serial.print(" ");
56
57 Serial.print(state_to_str(os_get_task_state(i)));
58 for (int j = strlen(state_to_str(os_get_task_state(i))); j < 10; j++) Serial.print(" ");
59
60 Serial.print(os_get_cpu_usage(i));
61 Serial.println("%");
62 }
63 Serial.println();
64}
65
66static void cmd_kill(int id)
67{
68 if (id <= 0) {
69 Serial.println("Error: Cannot kill idle task (ID 0)");
70 return;
71 }
72 if (id >= os_get_task_count()) {
73 Serial.println("Error: Invalid task ID");
74 return;
75 }
76
77 os_suspend_task(id);
78 Serial.print("Task ");
79 Serial.print(id);
80 Serial.print(" (");
81 Serial.print(os_get_task_name(id));
82 Serial.println(") suspended.");
83}
84
85static void cmd_exec(int id)
86{
87 if (id <= 0 || id >= os_get_task_count()) {
88 Serial.println("Error: Invalid task ID");
89 return;
90 }
91
92 os_resume_task(id);
93 Serial.print("Task ");
94 Serial.print(id);
95 Serial.print(" (");
96 Serial.print(os_get_task_name(id));
97 Serial.println(") resumed.");
98}
99
100static void cmd_info(void)
101{
102 Serial.println();
103 Serial.println("=== System Information ===");
104 Serial.print("System Ticks: ");
105 Serial.println(os_get_tick());
106 Serial.print("Uptime: ");
107 Serial.print(os_get_tick() / 1000);
108 Serial.println(" s");
109 Serial.print("Task Count: ");
110 Serial.println(os_get_task_count());
111 Serial.print("Time Slice: ");
112 Serial.print(TIME_SLICE_MS);
113 Serial.println(" ms");
114 Serial.println();
115}
116
117static void cmd_reboot(void)
118{
119 Serial.println("Rebooting...");
120 delay(100);
121 NVIC_SystemReset();
122}
123
124void shell_process_command(char *cmd)
125{
126 // Trim whitespace
127 while (*cmd == ' ') cmd++;
128 char *end = cmd + strlen(cmd) - 1;
129 while (end > cmd && *end == ' ') *end-- = '\0';
130
131 if (strlen(cmd) == 0) return;
132
133 // Parse command
134 if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
135 cmd_help();
136 } else if (strcmp(cmd, "ps") == 0) {
137 cmd_ps();
138 } else if (strncmp(cmd, "kill ", 5) == 0) {
139 int id = atoi(cmd + 5);
140 cmd_kill(id);
141 } else if (strncmp(cmd, "exec ", 5) == 0) {
142 int id = atoi(cmd + 5);
143 cmd_exec(id);
144 } else if (strncmp(cmd, "run", 3) == 0) {
145 const char *arg = cmd + 3;
146 while (*arg == ' ') arg++;
147 cmd_run(arg);
148 } else if (strcmp(cmd, "info") == 0) {
149 cmd_info();
150 } else if (strcmp(cmd, "reboot") == 0) {
151 cmd_reboot();
152 } else {
153 Serial.print("Unknown command: ");
154 Serial.println(cmd);
155 Serial.println("Type 'help' for available commands.");
156 }
157}
158
159// シリアルから 1 行読み取る(本書 8.4「シェルとの統合」)
160static void read_line(char *buf, int size)
161{
162 int pos = 0;
163 memset(buf, 0, size);
164
165 while (1) {
166 if (Serial.available()) {
167 char ch = Serial.read();
168
169 if (ch == '\r' || ch == '\n') {
170 Serial.println();
171 return;
172 } else if (ch == '\b' || ch == 127) {
173 if (pos > 0) {
174 pos--;
175 buf[pos] = '\0';
176 Serial.print("\b \b");
177 }
178 } else if (pos < size - 1) {
179 buf[pos++] = ch;
180 Serial.print(ch);
181 }
182 }
183 os_sleep(10);
184 }
185}
186
187// run コマンド: 引数があれば 1 行スクリプト、無ければ複数行の入力モード
188static void cmd_run(const char *arg)
189{
190 static char script[MAX_SCRIPT_LINES * MAX_LINE_LENGTH];
191 static char line[MAX_LINE_LENGTH];
192
193 if (arg && *arg) {
194 interp_load(arg);
195 interp_run();
196 return;
197 }
198
199 Serial.println("Enter script (empty line to execute):");
200 script[0] = '\0';
201
202 while (1) {
203 Serial.print(". ");
204 read_line(line, MAX_LINE_LENGTH);
205 if (line[0] == '\0') break;
206
207 if (strlen(script) + strlen(line) + 2 >= sizeof(script)) {
208 Serial.println("Error: script too long");
209 return;
210 }
211 strcat(script, line);
212 strcat(script, "\n");
213 }
214
215 if (script[0] != '\0') {
216 interp_load(script);
217 interp_run();
218 }
219}
220
221void shell_task(void)
222{
223 Serial.println();
224 Serial.println("Mini OS Shell");
225 Serial.println("Type 'help' for commands.");
226
227 interp_init();
228
229 while (1) {
230 // 保留中のフォルト情報をタスク文脈で表示する (第6章 6.3)
231 fault_print_pending();
232
233 Serial.print("> ");
234 read_line(cmd_buf, CMD_BUF_SIZE);
235 shell_process_command(cmd_buf);
236 }
237}
On the board#
Typing commands into the interpreter: PRINT reaches [Print] and FORWARD reaches [Motor].#
In the simulator#
Without the board, the same firmware running in the simulator looks like this.
The LED task keeps inverting L every 500 ms, so it is stopped with kill 1 first; then the interpreter runs run LED 13 1 (on) and run LED 13 0 (off). Without stopping the LED task, its next inversion would overwrite the command. (Recorded in the simulator.)#
Improved version: 08_interpreter_v2 (not in the book)#
Chapter 8 uses the same LED matrix display as chapter 7, and it is just as hard to
read (see the improved version in Chapter 7: LED Matrix Visualization).
code/08_interpreter_v2 adds the display and the shell top command of
chapter 7’s
07_led_matrix_v2.
The interpreter and the kernel are the same as the book’s.
It is not in the book’s repository, so get it from this site’s repository and flash
it (if you already cloned it for chapter 7’s improved version, only the upload line
is needed, in build-your-own-arduino-rtos/code):
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 08_interpreter_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 / Shell / Display / Light / Heavy |
Chapter 8’s own tasks (LED, Shell, Display) barely use the CPU: the LED task and the
interpreter’s FORWARD / TURN / DELAY all wait with os_sleep(), which hands
the CPU to other tasks. To make the bars move, v2 adds chapter 7’s Light (uses
20 ms of CPU, then sleeps 60 ms) and Heavy (computes without sleeping) after the
book’s three tasks. LED, Shell and Display keep their task numbers, so
kill 1 → run LED 13 1 works as in the book.
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%) |
|
~24% |
Heavy’s bar disappears and the total shrinks |
|
~4% |
Only Shell, drawing |
|
Back to normal |
Note
While a script runs with run, the top screen stops updating: it is redrawn
while the shell waits for input, so it resumes when the script ends. The matrix
keeps updating because the display task draws it.
Pitfalls#
(Collected from reader questions — ask via GitHub Issues.)