Chapter 9: Integration and Robot Control#
Sample code#
Directory:
docs/os-on-arduino/code/09_integration
uv run pio run -d 09_integration -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 — 165 lines
1// 08_integrated_os.ino
2// 統合版自作OS
3
4#include "os_kernel.h"
5#include "os_fault.h"
6#include "os_display.h"
7#include "shell.h"
8#include "interpreter.h"
9#include <Servo.h>
10
11// ハードウェアピン定義
12// Arduino UNO R4 WiFi は基板上に "L" と印字された LED を持っています。
13// 追加部品なしで動かせるよう、これを主 LED として使います。
14#define LED1_PIN LED_BUILTIN
15#define LED2_PIN 3
16#define LED3_PIN 4
17#define SERVO1_PIN 9
18#define SERVO2_PIN 10
19#define BUTTON_PIN 7
20#define POT_PIN A0
21
22// グローバル変数(タスク間共有)
23volatile int g_pot_value = 0;
24volatile bool g_button_pressed = false;
25Servo servo1, servo2;
26
27// ===========================================
28// タスク定義
29// ===========================================
30
31// タスク1: システムハートビート
32void task_heartbeat(void)
33{
34 pinMode(LED1_PIN, OUTPUT);
35 while (1) {
36 digitalWrite(LED1_PIN, HIGH);
37 os_sleep(100);
38 digitalWrite(LED1_PIN, LOW);
39 os_sleep(900);
40 }
41}
42
43// タスク2: センサ読み取り
44// 注: ここではanalogRead()を使っているが、内部のレジスタ操作については
45// Chapter 12でADC14のレジスタを直接操作する方法を学ぶ。
46void task_sensor(void)
47{
48 pinMode(POT_PIN, INPUT);
49 pinMode(BUTTON_PIN, INPUT_PULLUP);
50
51 while (1) {
52 g_pot_value = analogRead(POT_PIN);
53 g_button_pressed = (digitalRead(BUTTON_PIN) == LOW);
54 os_sleep(50);
55 }
56}
57
58// タスク3: サーボ制御(ポテンショメータ連動)
59// 注: ここではServoライブラリ(またはServoPWM)を使っているが、
60// ソフトウェアPWMはコンテキストスイッチの影響でジッターする可能性がある。
61// Chapter 12でGPTタイマを使ったハードウェアPWMに改善する。
62void task_servo(void)
63{
64 servo1.attach(SERVO1_PIN);
65 servo2.attach(SERVO2_PIN);
66
67 while (1) {
68 int angle = map(g_pot_value, 0, 1023, 0, 180);
69 servo1.write(angle);
70 servo2.write(180 - angle); // 逆方向
71 os_sleep(20);
72 }
73}
74
75// タスク4: LED表示(センサ値を3段階表示)
76void task_led_indicator(void)
77{
78 pinMode(LED2_PIN, OUTPUT);
79 pinMode(LED3_PIN, OUTPUT);
80
81 while (1) {
82 int level = g_pot_value / 341; // 0-2の3段階
83
84 digitalWrite(LED2_PIN, level >= 1);
85 digitalWrite(LED3_PIN, level >= 2);
86
87 os_sleep(100);
88 }
89}
90
91// タスク5: ボタン監視(押すたびにイベント発生)
92void task_button(void)
93{
94 bool last_state = false;
95 int press_count = 0;
96
97 while (1) {
98 if (g_button_pressed && !last_state) {
99 // 押された瞬間
100 press_count++;
101 Serial.print("[Button] Pressed! Count: ");
102 Serial.println(press_count);
103 }
104 last_state = g_button_pressed;
105 os_sleep(20);
106 }
107}
108
109// ===========================================
110// セットアップ
111// ===========================================
112
113void setup()
114{
115 Serial.begin(115200);
116 while (!Serial) { }
117
118 // スタートアップメッセージ
119 Serial.println();
120 Serial.println("╔════════════════════════════════════════╗");
121 Serial.println("║ MiniOS for Arduino UNO R4 WiFi ║");
122 Serial.println("║ Version 1.0 ║");
123 Serial.println("╚════════════════════════════════════════╝");
124 Serial.println();
125
126 // 例外設定
127 fault_init();
128
129 // OS初期化
130 os_init();
131 interp_init();
132
133 Serial.println("Creating tasks...");
134
135 // タスク作成
136 os_create_task(task_heartbeat, "Heartbeat", 256);
137 os_create_task(task_sensor, "Sensor", 512);
138 os_create_task(task_servo, "Servo", 512);
139 os_create_task(task_led_indicator, "LED_Ind", 256);
140 os_create_task(task_button, "Button", 512);
141 os_create_task(shell_task, "Shell", 1024);
142 os_create_task(display_task, "Display", 512);
143
144 Serial.println();
145 Serial.println("Tasks created:");
146 for (int i = 0; i < os_get_task_count(); i++) {
147 Serial.print(" ");
148 Serial.print(i);
149 Serial.print(": ");
150 Serial.println(os_get_task_name(i));
151 }
152
153 Serial.println();
154 Serial.println("Type 'help' for available commands.");
155 Serial.println("Type 'run' to execute motion scripts.");
156 Serial.println();
157
158 // OS開始
159 os_start();
160}
161
162void loop()
163{
164 // 到達しない
165}
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 — 79 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
44// スタックを埋めておく目印。どこまで書き換わったかで使用量が分かる。
45#define STACK_FILL_PATTERN 0xA5A5A5A5u
46#define IDLE_STACK_SIZE 256
47#define TIME_SLICE_MS 10
48
49void os_init(void);
50int os_create_task(void (*entry)(void), const char *name, uint32_t stack_size);
51void os_start(void);
52void os_yield(void);
53void os_sleep(uint32_t ms);
54
55void os_suspend_task(int id);
56void os_resume_task(int id);
57void os_terminate_task(int id);
58
59int os_get_task_count(void);
60const char* os_get_task_name(int id);
61TaskState os_get_task_state(int id);
62int os_get_current_task(void);
63int os_get_cpu_usage(int task_id);
64uint32_t os_get_task_cpu_ticks(int task_id);
65uint32_t os_get_tick(void);
66uint32_t os_get_stack_unused(int id); // 一度も使われていないスタックのバイト数
67
68extern TCB g_tasks[MAX_TASKS];
69extern volatile int g_task_count;
70extern volatile int g_current_task;
71extern volatile int g_next_task;
72extern volatile uint32_t g_system_ticks;
73extern const uint32_t g_tcb_size;
74
75#ifdef __cplusplus
76}
77#endif
78
79#endif
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 — 266 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
81 // 目印で埋めておく。あとで書き換わっていない範囲を数えれば、
82 // そのタスクがスタックを何バイト使ったかが分かる。
83 for (uint32_t i = 0; i < words; i++) {
84 task->stack_base[-(int32_t)(i + 1)] = STACK_FILL_PATTERN;
85 }
86
87 task->sp = init_task_stack(task->stack_base, entry);
88 g_task_count++;
89 return id;
90}
91
92void os_schedule(void)
93{
94 for (int i = 0; i < g_task_count; i++) {
95 if (g_tasks[i].state == TASK_BLOCKED && g_system_ticks >= g_tasks[i].wake_tick) {
96 g_tasks[i].state = TASK_READY;
97 }
98 }
99
100 int next = g_current_task;
101 do {
102 next = (next + 1) % g_task_count;
103 if (g_tasks[next].state == TASK_READY || g_tasks[next].state == TASK_RUNNING) break;
104 } while (next != g_current_task);
105
106 if (next != g_current_task || g_current_task < 0) {
107 if (g_current_task >= 0 && g_tasks[g_current_task].state == TASK_RUNNING) {
108 g_tasks[g_current_task].state = TASK_READY;
109 }
110 g_tasks[next].state = TASK_RUNNING;
111 g_next_task = next;
112 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
113 }
114}
115
116void os_yield(void) { os_schedule(); }
117void os_sleep(uint32_t ms) {
118 if (g_current_task > 0) {
119 g_tasks[g_current_task].state = TASK_BLOCKED;
120 g_tasks[g_current_task].wake_tick = g_system_ticks + ms;
121 os_schedule();
122 }
123}
124void os_suspend_task(int id) {
125 if (id > 0 && id < g_task_count) {
126 g_tasks[id].state = TASK_SUSPENDED;
127 if (id == g_current_task) os_schedule();
128 }
129}
130void os_resume_task(int id) {
131 if (id > 0 && id < g_task_count && g_tasks[id].state == TASK_SUSPENDED) {
132 g_tasks[id].state = TASK_READY;
133 }
134}
135void os_terminate_task(int id) {
136 if (id > 0 && id < g_task_count) {
137 g_tasks[id].state = TASK_TERMINATED;
138 if (id == g_current_task) os_schedule();
139 }
140}
141
142uint32_t os_get_tick(void) { return g_system_ticks; }
143int os_get_task_count(void) { return g_task_count; }
144const char* os_get_task_name(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].name : ""; }
145TaskState os_get_task_state(int id) { return (id >= 0 && id < g_task_count) ? g_tasks[id].state : TASK_TERMINATED; }
146int os_get_current_task(void) { return g_current_task; }
147
148void os_start(void)
149{
150 NVIC_SetPriority(PendSV_IRQn, 0xFF);
151 SysTick_Config(SystemCoreClock / 1000 * TIME_SLICE_MS);
152 g_current_task = -1;
153 g_next_task = 0;
154 // Do NOT switch CONTROL to PSP here — PSP is not yet initialized.
155 // PendSV returns with EXC_RETURN=0xFFFFFFFD, which automatically
156 // switches thread mode to PSP after loading the first task's context.
157 SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
158 while (1) { }
159}
160
161extern "C" void SysTick_Handler(void) {
162 // 実際に経過した時間を、いま走っていたタスクの取り分として計上する。
163 // os_schedule() 側で数えると os_yield() の回数まで混ざってしまう。
164 if (g_current_task >= 0) {
165 g_tasks[g_current_task].cpu_ticks += TIME_SLICE_MS;
166 }
167 g_system_ticks += TIME_SLICE_MS;
168 os_schedule();
169}
170
171extern "C" __attribute__((naked)) void PendSV_Handler(void)
172{
173 __asm volatile (
174 " MRS R0, PSP \n"
175 " LDR R1, =g_current_task \n"
176 " LDR R2, [R1] \n"
177 " CMP R2, #0 \n"
178 " BLT _skip_save \n"
179 // FPU を使って中断したタスク(EXC_RETURN の bit4 が 0)は、
180 // ハードウェアが積まない S16-S31 もここで退避する
181 " TST LR, #0x10 \n"
182 " IT EQ \n"
183 " VSTMDBEQ R0!, {S16-S31} \n"
184 // R4-R11 と EXC_RETURN(LR) を保存する
185 " STMDB R0!, {R4-R11, LR} \n"
186 " LDR R3, =g_tasks \n"
187 " LDR R4, =g_tcb_size \n"
188 " LDR R4, [R4] \n"
189 " MUL R2, R2, R4 \n"
190 " ADD R3, R3, R2 \n"
191 " STR R0, [R3] \n"
192 "_skip_save: \n"
193 " LDR R1, =g_next_task \n"
194 " LDR R2, [R1] \n"
195 " LDR R1, =g_current_task \n"
196 " STR R2, [R1] \n"
197 " LDR R3, =g_tasks \n"
198 " LDR R4, =g_tcb_size \n"
199 " LDR R4, [R4] \n"
200 " MUL R2, R2, R4 \n"
201 " ADD R3, R3, R2 \n"
202 " LDR R0, [R3] \n"
203 " LDMIA R0!, {R4-R11, LR} \n"
204 // FPU を使って中断していたタスクなら S16-S31 も復元する
205 " TST LR, #0x10 \n"
206 " IT EQ \n"
207 " VLDMIAEQ R0!, {S16-S31} \n"
208 " MSR PSP, R0 \n"
209 " BX LR \n"
210 );
211}
212
213// CPU使用率を取得(パーセント)
214// 直前にこの関数を呼んだ時点からの差分で計算する。
215// ps のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
216// CPU 使用カウンタの生値。os_get_cpu_usage() と違って状態を持たないので、
217// 何度呼んでも他の呼び手の計測窓を壊さない (第4章 4.7)。
218uint32_t os_get_task_cpu_ticks(int task_id)
219{
220 if (task_id < 0 || task_id >= g_task_count) {
221 return 0;
222 }
223 return g_tasks[task_id].cpu_ticks;
224}
225
226int os_get_cpu_usage(int task_id)
227{
228 if (task_id < 0 || task_id >= g_task_count) {
229 return -1;
230 }
231
232 static uint32_t last_total[MAX_TASKS] = {0};
233 static uint32_t last_task_ticks[MAX_TASKS] = {0};
234
235 uint32_t total = g_system_ticks;
236 uint32_t delta_total = total - last_total[task_id];
237
238 if (delta_total == 0) {
239 return 0;
240 }
241
242 uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
243
244 last_total[task_id] = total;
245 last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
246
247 return (delta_task * 100) / delta_total;
248}
249
250// 一度も書き換えられていないスタック領域のバイト数を返す。
251// 0 に近いほど危険で、0 なら溢れている可能性が高い。
252uint32_t os_get_stack_unused(int id)
253{
254 if (id < 0 || id >= g_task_count) {
255 return 0;
256 }
257 TCB *task = &g_tasks[id];
258 uint32_t words = task->stack_size / 4;
259 uint32_t *low = task->stack_base - words;
260
261 uint32_t i = 0;
262 while (i < words && low[i] == STACK_FILL_PATTERN) {
263 i++;
264 }
265 return i * 4;
266}
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}
Pitfalls#
(Collected from reader questions — ask via GitHub Issues.)