Chapter 12: Hardware Deep Dive — PWM and ADC#
Sample code#
Directory:
docs/os-on-arduino/code/12_hardware
uv run pio run -d 12_hardware -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 — 122 lines
1/**
2 * Chapter 12: Hardware PWM and ADC
3 *
4 * 本書 12.4「OS との統合」の After 版をそのまま動かすデモ。
5 *
6 * - task_servo : GPT のハードウェア PWM でサーボを駆動(レジスタ書き込みのみ)
7 * - task_sensor : ADC14 を直接叩いて 14bit で読む
8 * - task_heavy : わざと CPU を占有し、それでもサーボが揺れないことを見る
9 * - shell : ps / kill / exec でタスクを触る
10 *
11 * 配線:
12 * D9 -- サーボ信号線(サーボの電源は別系統から取ること)
13 * A0 -- ポテンショメータのワイパ
14 * D2 -- LED(ハートビート)
15 */
16
17#include <Arduino.h>
18#include "os_kernel.h"
19#include "os_fault.h"
20#include "shell.h"
21#include "hardware_pwm.h"
22#include "raw_adc.h"
23
24#define LED_PIN 2
25#define SERVO_PIN 9
26#define POT_CH 9 // A0 = P014 = AN009 (ch0 は A1)
27
28// タスク間共有データ(本書 9.2「タスク間共有データ」を参照)
29volatile uint16_t g_pot_raw = 0; // 14bit 生値
30volatile int g_pot_10bit = 0; // 10bit 換算
31
32static HardwarePWM servo1;
33
34// ハートビート: OS が生きていることを示す
35void task_led(void)
36{
37 pinMode(LED_PIN, OUTPUT);
38 while (1) {
39 digitalWrite(LED_PIN, !digitalRead(LED_PIN));
40 os_sleep(500);
41 }
42}
43
44// センサ: ADC14 を直接読む(12.3)
45void task_sensor(void)
46{
47 raw_adc_init();
48 while (1) {
49 g_pot_raw = raw_analog_read(POT_CH);
50 g_pot_10bit = raw_analog_read_10bit(POT_CH);
51 os_sleep(50);
52 }
53}
54
55// サーボ: ハードウェア PWM で駆動(12.2)
56// write() はレジスタに書くだけなので CPU を占有しない。
57// refresh() も不要で、波形は GPT が自動で出し続ける。
58void task_servo(void)
59{
60 if (!servo1.attach(SERVO_PIN)) {
61 Serial.println(F("[Error] servo attach failed"));
62 while (1) os_sleep(1000);
63 }
64 while (1) {
65 int angle = map(g_pot_10bit, 0, 1023, 0, 180);
66 servo1.write(angle);
67 os_sleep(20);
68 }
69}
70
71// 重い計算: CPU を食い続ける。
72// ソフトウェア PWM ならここでサーボが暴れるが、GPT なら影響を受けない。
73void task_heavy(void)
74{
75 while (1) {
76 volatile long sum = 0;
77 for (long i = 0; i < 200000; i++) {
78 sum += i;
79 }
80 os_sleep(1);
81 }
82}
83
84// 状態表示
85void task_status(void)
86{
87 while (1) {
88 Serial.print(F("pot(14bit)="));
89 Serial.print(g_pot_raw);
90 Serial.print(F(" pot(10bit)="));
91 Serial.print(g_pot_10bit);
92 Serial.print(F(" servo="));
93 Serial.print(servo1.read());
94 Serial.println(F("deg"));
95 os_sleep(1000);
96 }
97}
98
99void setup()
100{
101 Serial.begin(115200);
102 while (!Serial) { }
103
104 Serial.println(F("=== Chapter 12: Hardware PWM / ADC ==="));
105 Serial.println(F("Turn the pot on A0. The servo on D9 should follow"));
106 Serial.println(F("smoothly even while 'Heavy' is hogging the CPU."));
107 Serial.println(F("Try 'kill 4' to stop Heavy and compare.\n"));
108
109 os_init();
110 fault_init();
111
112 os_create_task(task_led, "LED", 512);
113 os_create_task(task_sensor, "Sensor", 512);
114 os_create_task(task_servo, "Servo", 512);
115 os_create_task(task_heavy, "Heavy", 512);
116 os_create_task(task_status, "Status", 512);
117 os_create_task(shell_task, "Shell", 1024);
118
119 os_start();
120}
121
122void loop() { }
include/hardware_pwm.h — 159 lines
1// hardware_pwm.h
2// GPTタイマによるハードウェアPWM(サーボ制御用)
3
4#ifndef HARDWARE_PWM_H
5#define HARDWARE_PWM_H
6
7#include <Arduino.h>
8
9// RA4M1 GPTレジスタ定義
10// Renesas FSP ヘッダから利用可能
11#include <Arduino.h> // R_GPT0 / R_ADC0 などのレジスタ定義はここから来る
12
13class HardwarePWM {
14private:
15 R_GPT0_Type *gpt; // GPTチャネルのレジスタブロックへのポインタ
16 int pin;
17 int ch_num; // GPTチャネル番号 (0〜7)
18 int channel; // 0 = GTIOCA 出力, 1 = GTIOCB 出力
19 bool active;
20
21public:
22 HardwarePWM() : gpt(nullptr), pin(-1), ch_num(-1), channel(-1), active(false) {}
23
24 /**
25 * GPTチャネルを使ってPWM出力を開始する
26 *
27 * Arduino UNO R4 WiFi のGPTピン対応:
28 * D3 → GPT1 (GTIOC1A)
29 * D5 → GPT0 (GTIOC0A)
30 * D6 → GPT3 (GTIOC3A)
31 * D9 → GPT7 (GTIOC7B)
32 * D10 → GPT2 (GTIOC2A)
33 * D11 → GPT6 (GTIOC6A)
34 */
35 bool attach(int p) {
36 pin = p;
37
38 // ピン番号からGPTチャネルを決定
39 switch (pin) {
40 case 3: gpt = R_GPT1; ch_num = 1; channel = 0; break; // GTIOC1A
41 case 5: gpt = R_GPT0; ch_num = 0; channel = 0; break; // GTIOC0A
42 case 6: gpt = R_GPT3; ch_num = 3; channel = 0; break; // GTIOC3A
43 case 9: gpt = R_GPT7; ch_num = 7; channel = 1; break; // GTIOC7B
44 case 10: gpt = R_GPT2; ch_num = 2; channel = 0; break; // GTIOC2A
45 case 11: gpt = R_GPT6; ch_num = 6; channel = 0; break; // GTIOC6A
46 default:
47 Serial.println("[PWM] Error: unsupported pin");
48 return false;
49 }
50
51 // ---- GPT モジュールの電源ON ----
52 // AVRと違いARMの周辺モジュールはデフォルトで電源OFFの場合がある
53 // Module Stop Control Register でGPTモジュールを有効化
54 // RA4M1 は ch0〜1 (GPT320/321) が MSTPD5、ch2〜7 (GPT162〜167) が MSTPD6
55 if (ch_num <= 1) {
56 R_MSTP->MSTPCRD_b.MSTPD5 = 0;
57 } else {
58 R_MSTP->MSTPCRD_b.MSTPD6 = 0;
59 }
60
61 // ---- ピンのマルチプレクサ設定 ----
62 // AVRではピンの多機能割り当ては限られていたが、
63 // ARMでは PFS (Pin Function Select) レジスタで明示的に設定する
64 // ここではArduinoコアの pinPeripheral() でピンを GPT 出力へ切り替える
65 // (実際のレジスタは R_PFS->PORT[port].PIN[pin].PmnPFS)
66 pinPeripheral((uint32_t)pin,
67 (uint32_t)(IOPORT_CFG_PERIPHERAL_PIN | IOPORT_PERIPHERAL_GPT1));
68
69 // ---- GPTタイマの設定 ----
70 // Step 1: タイマ停止
71 // GTSTP/GTSTR は全チャネル共有のビットマップ。自分のビットだけ立てる
72 gpt->GTSTP = (1u << ch_num);
73
74 // Step 2: 動作モード設定
75 // Saw-wave (片斜面) PWMモード
76 // プリスケーラ: PCLKD/64 → 48MHz / 64 = 750kHz
77 gpt->GTCR = (0x0 << 16) // MD[2:0] = 000 : ノコギリ波PWMモード
78 | (0x3 << 24); // TPCS[2:0] = 011 : PCLKD/64
79
80 // Step 3: 周期設定
81 // 50Hz (20ms) のPWM周期
82 // 750kHz × 20ms = 15000 カウント
83 // → AVRで「プリスケーラ1024、OCR0A = 155」としていたのと同じ考え方
84 gpt->GTPR = 15000 - 1; // 周期 = (GTPR + 1) / 750kHz = 20ms
85
86 // Step 4: 出力制御
87 // 周期先頭でHigh、比較一致でLowに設定
88 if (channel == 0) {
89 // GTIOCA: 周期先頭でHigh、比較一致でLow (初期値はLow)
90 gpt->GTIOR_b.GTIOA = 0x09;
91 gpt->GTIOR_b.OAE = 1; // GTIOCA出力有効
92 } else {
93 // GTIOCB: 同様
94 gpt->GTIOR_b.GTIOB = 0x09;
95 gpt->GTIOR_b.OBE = 1; // GTIOCB出力有効
96 }
97
98 active = true;
99
100 // Step 5: 初期デューティ比(90° = 中央)
101 writeMicroseconds(1500);
102
103 // Step 6: カウンタクリア & タイマ開始
104 gpt->GTCNT = 0;
105 gpt->GTSSR_b.CSTRT = 1; // ソフトウェアスタートを起動要因として許可
106 gpt->GTSTR = (1u << ch_num); // カウント開始
107
108 return true;
109 }
110
111 void detach() {
112 if (gpt) {
113 gpt->GTSTP = (1u << ch_num); // タイマ停止
114 }
115 active = false;
116 }
117
118 /**
119 * パルス幅をマイクロ秒で指定
120 * 750kHz → 1カウント = 1.333μs
121 * us マイクロ秒 → us × 750000 / 1000000 = us × 0.75 カウント
122 */
123 void writeMicroseconds(int us) {
124 if (!active) return;
125 uint32_t counts = (uint32_t)us * 3 / 4; // μs → カウント変換
126
127 if (channel == 0) {
128 gpt->GTCCR[0] = counts; // GTCCRA更新 → デューティ比変更
129 } else {
130 gpt->GTCCR[1] = counts; // GTCCRB更新
131 }
132 // ★ ここがポイント: レジスタに値を書くだけ
133 // CPUはすぐ次の処理に進める
134 // 波形生成はハードウェアが自動で行う
135 }
136
137 /**
138 * 角度(0〜180°)で指定
139 */
140 void write(int angle) {
141 angle = constrain(angle, 0, 180);
142 int us = map(angle, 0, 180, 500, 2500);
143 writeMicroseconds(us);
144 }
145
146 int read() {
147 if (!active) return 0;
148 uint32_t counts;
149 if (channel == 0) {
150 counts = gpt->GTCCR[0];
151 } else {
152 counts = gpt->GTCCR[1];
153 }
154 int us = counts * 4 / 3;
155 return map(us, 500, 2500, 0, 180);
156 }
157};
158
159#endif // HARDWARE_PWM_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 — 74 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_tick(void);
62
63extern TCB g_tasks[MAX_TASKS];
64extern volatile int g_task_count;
65extern volatile int g_current_task;
66extern volatile int g_next_task;
67extern volatile uint32_t g_system_ticks;
68extern const uint32_t g_tcb_size;
69
70#ifdef __cplusplus
71}
72#endif
73
74#endif
include/raw_adc.h — 75 lines
1// raw_adc.h
2// レジスタ操作によるA/D変換
3
4#ifndef RAW_ADC_H
5#define RAW_ADC_H
6
7#include <Arduino.h>
8#include <Arduino.h> // R_GPT0 / R_ADC0 などのレジスタ定義はここから来る
9
10/**
11 * ADCの初期化
12 * AVRでの ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
13 * に相当する処理
14 */
15void raw_adc_init(void) {
16 // ---- ADCモジュールの電源ON ----
17 R_MSTP->MSTPCRD_b.MSTPD16 = 0; // ADC140 モジュールストップ解除
18
19 // ---- ADC設定 ----
20 // Step 1: ADCを停止(設定変更前に必要)
21 R_ADC0->ADCSR = 0;
22
23 // Step 2: 変換精度の設定
24 // ADPRC (ADCER のビット2:1) = 11 → 14ビット分解能
25 // (RA4M1 で有効なのは 00=12bit と 11=14bit のみ)
26 // AVRでは固定10ビットだったが、RA4M1では選択可能
27 R_ADC0->ADCER = 0x0006; // ADPRC=11: 14bit, 右詰め
28
29 // Step 3: サンプリング時間の設定
30 // AVRのプリスケーラ(ADPS)に相当する概念
31 // サンプリングステート数を設定
32 for (int i = 0; i < 16; i++) {
33 R_ADC0->ADSSTR[i] = 0x0D; // 13ステート(デフォルト)
34 }
35}
36
37/**
38 * 指定チャネルのA/D変換を実行(ポーリング方式)
39 *
40 * AVRでの以下のコードに相当:
41 * ADMUX = (ADMUX & 0xF0) | channel;
42 * ADCSRA |= _BV(ADSC);
43 * loop_until_bit_is_clear(ADCSRA, ADSC);
44 * return ADC;
45 */
46uint16_t raw_analog_read(uint8_t channel) {
47 // Step 1: チャネル選択
48 // AVR: ADMUX = (ADMUX & 0xF0) | channel;
49 R_ADC0->ADANSA[0] = (1 << channel); // 指定チャネルのみ有効
50
51 // Step 2: 変換開始
52 // AVR: ADCSRA |= _BV(ADSC);
53 R_ADC0->ADCSR |= R_ADC0_ADCSR_ADST_Msk; // ADST = 1 で変換開始
54
55 // Step 3: 変換完了を待機(ポーリング)
56 // AVR: loop_until_bit_is_clear(ADCSRA, ADSC);
57 while (R_ADC0->ADCSR & R_ADC0_ADCSR_ADST_Msk) {
58 // ADSTが自動クリアされるまで待機
59 // AVRの ADSC と同じ動作
60 }
61
62 // Step 4: 結果を読む
63 // AVR: return ADC; (ADCH:ADCLの16bit結合値)
64 return R_ADC0->ADDR[channel]; // 14ビット値(0〜16383)
65}
66
67/**
68 * 14ビット値を10ビット値(0〜1023)に変換
69 * Arduino標準のanalogRead()と同じ範囲にする
70 */
71uint16_t raw_analog_read_10bit(uint8_t channel) {
72 return raw_analog_read(channel) >> 4; // 14bit → 10bit
73}
74
75#endif // RAW_ADC_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/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 — 231 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 のようにタスクごとにループで呼ぶため、基準時刻もタスクごとに持つ。
209int os_get_cpu_usage(int task_id)
210{
211 if (task_id < 0 || task_id >= g_task_count) {
212 return -1;
213 }
214
215 static uint32_t last_total[MAX_TASKS] = {0};
216 static uint32_t last_task_ticks[MAX_TASKS] = {0};
217
218 uint32_t total = g_system_ticks;
219 uint32_t delta_total = total - last_total[task_id];
220
221 if (delta_total == 0) {
222 return 0;
223 }
224
225 uint32_t delta_task = g_tasks[task_id].cpu_ticks - last_task_ticks[task_id];
226
227 last_total[task_id] = total;
228 last_task_ticks[task_id] = g_tasks[task_id].cpu_ticks;
229
230 return (delta_task * 100) / delta_total;
231}
src/shell.cpp — 190 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 <Arduino.h>
10#include <string.h>
11
12#define CMD_BUF_SIZE 64
13
14static char cmd_buf[CMD_BUF_SIZE];
15static int cmd_pos = 0;
16
17static const char* state_to_str(TaskState state)
18{
19 switch (state) {
20 case TASK_READY: return "READY";
21 case TASK_RUNNING: return "RUNNING";
22 case TASK_BLOCKED: return "BLOCKED";
23 case TASK_SUSPENDED: return "SUSPEND";
24 case TASK_TERMINATED: return "TERM";
25 default: return "?";
26 }
27}
28
29static void cmd_help(void)
30{
31 Serial.println("Available commands:");
32 Serial.println(" ps - List all tasks");
33 Serial.println(" kill N - Suspend task N");
34 Serial.println(" exec N - Resume task N");
35 Serial.println(" info - System information");
36 Serial.println(" reboot - Reboot system");
37 Serial.println(" help - Show this help");
38}
39
40static void cmd_ps(void)
41{
42 Serial.println();
43 Serial.println("ID NAME STATE CPU%");
44 Serial.println("-- ---- ----- ----");
45
46 for (int i = 0; i < os_get_task_count(); i++) {
47 Serial.print(i);
48 Serial.print(" ");
49
50 const char *name = os_get_task_name(i);
51 Serial.print(name);
52 for (int j = strlen(name); j < 15; j++) Serial.print(" ");
53
54 Serial.print(state_to_str(os_get_task_state(i)));
55 for (int j = strlen(state_to_str(os_get_task_state(i))); j < 10; j++) Serial.print(" ");
56
57 Serial.print(os_get_cpu_usage(i));
58 Serial.println("%");
59 }
60 Serial.println();
61}
62
63static void cmd_kill(int id)
64{
65 if (id <= 0) {
66 Serial.println("Error: Cannot kill idle task (ID 0)");
67 return;
68 }
69 if (id >= os_get_task_count()) {
70 Serial.println("Error: Invalid task ID");
71 return;
72 }
73
74 os_suspend_task(id);
75 Serial.print("Task ");
76 Serial.print(id);
77 Serial.print(" (");
78 Serial.print(os_get_task_name(id));
79 Serial.println(") suspended.");
80}
81
82static void cmd_exec(int id)
83{
84 if (id <= 0 || id >= os_get_task_count()) {
85 Serial.println("Error: Invalid task ID");
86 return;
87 }
88
89 os_resume_task(id);
90 Serial.print("Task ");
91 Serial.print(id);
92 Serial.print(" (");
93 Serial.print(os_get_task_name(id));
94 Serial.println(") resumed.");
95}
96
97static void cmd_info(void)
98{
99 Serial.println();
100 Serial.println("=== System Information ===");
101 Serial.print("System Ticks: ");
102 Serial.println(os_get_tick());
103 Serial.print("Uptime: ");
104 Serial.print(os_get_tick() / 1000);
105 Serial.println(" s");
106 Serial.print("Task Count: ");
107 Serial.println(os_get_task_count());
108 Serial.print("Time Slice: ");
109 Serial.print(TIME_SLICE_MS);
110 Serial.println(" ms");
111 Serial.println();
112}
113
114static void cmd_reboot(void)
115{
116 Serial.println("Rebooting...");
117 delay(100);
118 NVIC_SystemReset();
119}
120
121void shell_process_command(char *cmd)
122{
123 // Trim whitespace
124 while (*cmd == ' ') cmd++;
125 char *end = cmd + strlen(cmd) - 1;
126 while (end > cmd && *end == ' ') *end-- = '\0';
127
128 if (strlen(cmd) == 0) return;
129
130 // Parse command
131 if (strcmp(cmd, "help") == 0 || strcmp(cmd, "?") == 0) {
132 cmd_help();
133 } else if (strcmp(cmd, "ps") == 0) {
134 cmd_ps();
135 } else if (strncmp(cmd, "kill ", 5) == 0) {
136 int id = atoi(cmd + 5);
137 cmd_kill(id);
138 } else if (strncmp(cmd, "exec ", 5) == 0) {
139 int id = atoi(cmd + 5);
140 cmd_exec(id);
141 } else if (strcmp(cmd, "info") == 0) {
142 cmd_info();
143 } else if (strcmp(cmd, "reboot") == 0) {
144 cmd_reboot();
145 } else {
146 Serial.print("Unknown command: ");
147 Serial.println(cmd);
148 Serial.println("Type 'help' for available commands.");
149 }
150}
151
152void shell_task(void)
153{
154 Serial.println();
155 Serial.println("Mini OS Shell");
156 Serial.println("Type 'help' for commands.");
157
158 while (1) {
159 // 保留中のフォルト情報をタスク文脈で表示する (第6章 6.3)
160 fault_print_pending();
161
162 Serial.print("> ");
163
164 cmd_pos = 0;
165 memset(cmd_buf, 0, CMD_BUF_SIZE);
166
167 while (1) {
168 if (Serial.available()) {
169 char ch = Serial.read();
170
171 if (ch == '\r' || ch == '\n') {
172 Serial.println();
173 break;
174 } else if (ch == '\b' || ch == 127) {
175 if (cmd_pos > 0) {
176 cmd_pos--;
177 cmd_buf[cmd_pos] = '\0';
178 Serial.print("\b \b");
179 }
180 } else if (cmd_pos < CMD_BUF_SIZE - 1) {
181 cmd_buf[cmd_pos++] = ch;
182 Serial.print(ch);
183 }
184 }
185 os_sleep(10);
186 }
187
188 shell_process_command(cmd_buf);
189 }
190}
Pitfalls#
(Collected from reader questions — ask via GitHub Issues.)