第12章: ハードウェア制御の深層 — PWM生成とA/D変換#

PWM 生成と A/D 変換をレジスタレベルで理解します。

サンプルコード#

  • ディレクトリ: docs/os-on-arduino/code/12_hardware

  • ビルドと書き込み:

uv run pio run -d 12_hardware -t upload
uv run pio device monitor -b 115200

コードを見る: docs/os-on-arduino/code/12_hardware

ソースコード#

この章のスケッチです。ファイル名をクリックすると開きます。コードは原稿リポジトリから自動で同期されているので、ここに出ているものが常に最新です。

src/main.cpp — 122 行

GitHub で開く

  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 行

GitHub で開く

  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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

 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 行

GitHub で開く

  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 行

GitHub で開く

  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 行

GitHub で開く

  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}

実機での出力#

Arduino UNO R4 WiFi の実機で実際に取得した出力です(macOS / Windows の 両方で同じ結果を確認しています)。手元の出力と見比べてください。

A0 に可変抵抗をつなぐと値が動きます。servo は角度(度)です。

pot(14bit)=3233  pot(10bit)=201  servo=34deg
pot(14bit)=3252  pot(10bit)=202  servo=34deg
pot(14bit)=3243  pot(10bit)=201  servo=34deg
pot(14bit)=3254  pot(10bit)=202  servo=34deg
pot(14bit)=3239  pot(10bit)=201  servo=34deg
pot(14bit)=3221  pot(10bit)=200  servo=34deg
pot(14bit)=3238  pot(10bit)=201  servo=34deg
pot(14bit)=3222  pot(10bit)=200  servo=34deg
...

つまずきやすいポイント#

(読者からの質問に応じて随時追記します。質問は GitHub Issues へどうぞ)