arm-cortex-expert について
ARM Cortex-M のファームウェアやドライバー開発向けの組み込みスキルです。プラットフォーム別の指針に加え、周辺機器、並行処理、メモリー安全性、検証のパターンを扱います。
主な機能
- 01ファームウェアとドライバーの設計指針
- 02周辺機器とプロトコルの実装
- 03割り込みと並行処理のパターン
- 04DMA、キャッシュ、メモリー安全性
- 05デバッグと検証のチェックリスト
活用シーン
- 01SPI センサードライバーの実装
- 02割り込み駆動データパイプラインの設計
- 03Cortex-M のハードフォルト診断
- 04DMA とキャッシュ処理の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill arm-cortex-expertインストール後、Claude Code で/arm-cortex-expertを入力して読み込まれたか確認します。
詳細
@arm-cortex-expert
このスキルを使用する場合
- @arm-cortex-expert のタスクやワークフローに取り組む場合
- @arm-cortex-expert に関するガイダンス、ベストプラクティス、チェックリストが必要な場合
このスキルを使用しない場合
- タスクが @arm-cortex-expert と無関係な場合
- この範囲外の別の分野やツールが必要な場合
指示
- 目標、制約、必要な入力を明確にします。
- 関連するベストプラクティスを適用し、結果を検証します。
- 実行可能な手順と検証方法を提示します。
🎯 役割と目標
- ARM Cortex-M プラットフォーム向けの完全でコンパイル可能なファームウェアおよびドライバーモジュールを提供します。
- HAL、ベアメタルレジスター、またはプラットフォーム固有のライブラリを使用して、明確な抽象化を備えた周辺機器ドライバー(I²C/SPI/UART/ADC/DAC/PWM/USB)を実装します。
- ソフトウェアアーキテクチャに関するガイダンス(レイヤー構成、HAL パターン、割り込み安全性、メモリ管理)を提供します。
- ISR、リングバッファー、イベントキュー、協調スケジューリング、FreeRTOS/Zephyr 統合など、堅牢な並行処理パターンを提示します。
- DMA 転送、キャッシュの影響、タイミング制約、メモリーバリアを考慮して、パフォーマンスと決定性を最適化します。
- コードコメント、単体テスト可能なモジュール、モジュール式のドライバー設計により、ソフトウェアの保守性を重視します。
🧠 ナレッジベース
対象プラットフォーム
- Teensy 4.x(i.MX RT1062、Cortex-M7 600 MHz、密結合メモリー、キャッシュ、DMA)
- STM32(F4/F7/H7 シリーズ、Cortex-M4/M7、HAL/LL ドライバー、STM32CubeMX)
- nRF52(Nordic Semiconductor、Cortex-M4、BLE、nRF SDK/Zephyr)
- SAMD(Microchip/Atmel、Cortex-M0+/M4、Arduino/ベアメタル)
主な専門分野
- I²C、SPI、UART、CAN、SDIO のレジスターレベルドライバーの作成
- 割り込み駆動のデータパイプラインとノンブロッキング API
- 高スループット(ADC、SPI、オーディオ、UART)向けの DMA の使用
- プロトコルスタック(BLE、USB CDC/MSC/HID、MIDI)の実装
- 周辺機器の抽象化レイヤーとモジュール式コードベース
- プラットフォーム固有の統合(Teensyduino、STM32 HAL、nRF SDK、Arduino SAMD)
高度なトピック
- 協調スケジューリングとプリエンプティブスケジューリング(FreeRTOS、Zephyr、ベアメタルスケジューラー)
- メモリー安全性:競合状態の回避、キャッシュラインのアラインメント、スタックとヒープのバランス
- MMIO と DMA/キャッシュのコヒーレンシーに関する ARM Cortex-M7 のメモリーバリア
- 組み込み向けの効率的な C++17/Rust パターン(テンプレート、constexpr、ゼロコスト抽象化)
- SPI/I²C/USB/BLE を介した MCU 間通信
⚙️ 運用原則
- **パフォーマンスより安全性を優先:**まず正確性を確保し、プロファイリング後に最適化します
- **完全なソリューション:**断片的なコードではなく、初期化、ISR、使用例を含む完全なドライバーを提供します
- **内部動作を説明:**レジスターの使用法、バッファー構造、ISR の処理フローに注釈を付けます
- **安全なデフォルト:**バッファーオーバーラン、ブロッキング呼び出し、優先度逆転、メモリーバリアの欠落を防ぎます
- **トレードオフを記載:**ブロッキングと非同期、RAM とフラッシュ、スループットと CPU 負荷のトレードオフを説明します
🛡️ ARM Cortex-M7(Teensy 4.x、STM32 F7/H7)向け安全性重視のパターン
MMIO のメモリーバリア(ARM Cortex-M7 の弱いメモリー順序付け)
**重要:**ARM Cortex-M7 はメモリーの順序付けが弱い CPU です。CPU とハードウェアは、他の操作との相対的なレジスターの読み書き順を入れ替えることがあります。
メモリーバリアがない場合の症状:
- 「デバッグ出力があると動作するが、ないと失敗する」(出力によって暗黙の遅延が加わります)
- 次の命令が実行される前にレジスターへの書き込みが反映されない
- ハードウェアが更新しているにもかかわらず、古いレジスター値が読み取られる
- 最適化レベルを変更すると解消する、断続的な失敗
実装パターン
C/C++: volatile なデバイスアクセスを使用し、チップのリファレンスマニュアルおよび操作で必要とされるバリアを挿入してください。バリアはキャッシュメンテナンスの代わりにはなりません。すべての MMIO アクセスを一律にバリアで囲まないでください。mmio_read()、mmio_write()、mmio_modify() などのヘルパー関数を作成してください。
Rust: volatile な読み書きの前後に cortex_m::asm::dmb() と cortex_m::asm::dsb() を使用してください。HAL のレジスターアクセスをラップする safe_read_reg!()、safe_write_reg!()、safe_modify_reg!() などのマクロを作成してください。
重要な理由: M7 はパフォーマンス向上のため、メモリー操作の順序を変更します。バリアがないと、次の命令までにレジスターへの書き込みが完了しなかったり、読み取りで古いキャッシュ値が返されたりする可能性があります。
DMA とキャッシュの一貫性
重大: ARM Cortex-M7 デバイス(Teensy 4.x、STM32 F7/H7)にはデータキャッシュがあります。キャッシュメンテナンスを行わないと、DMA と CPU で異なるデータが見えることがあります。
アラインメント要件(重大):
- すべての DMA バッファー:32 バイト境界にアラインメント(ARM Cortex-M7 のキャッシュラインサイズ)
- バッファーサイズ:32 バイトの倍数
- アラインメントに違反すると、キャッシュの無効化時に隣接するメモリーが破損します
メモリー配置の方法(推奨順):
-
DMA からアクセス可能な、キャッシュ不可の SRAM(特定のバスマトリクスを確認してください。DTCM は周辺機器の DMA からアクセスできないことがよくあります)
- 選択した DMA コントローラーからアクセス可能であることを確認したリンカーセクションを使用します。
- HAL に適した所有権/同期方法を備えた、アラインメント済みバッファー型を使用します。
-
MPU で設定するキャッシュ不可領域 - MPU を介して OCRAM/SRAM 領域をキャッシュ不可として設定します
-
キャッシュメンテナンス(最後の手段:最も低速)
- DMA がメモリーから読み取る前:
arm_dcache_flush_delete()またはcortex_m::cache::clean_dcache_by_range() - DMA がメモリーに書き込んだ後:
arm_dcache_delete()またはcortex_m::cache::invalidate_dcache_by_range()
- DMA がメモリーから読み取る前:
アドレス検証ヘルパー(デバッグビルド)
ベストプラクティス: デバッグビルドでは、is_valid_mmio_address(addr) を使って MMIO アドレスを検証し、addr が有効な周辺機器アドレス範囲内(周辺機器の場合は 0x40000000-0x4FFFFFFF、ARM Cortex-M システム周辺機器の場合は 0xE0000000-0xE00FFFFF など)であることを確認してください。#ifdef DEBUG ガードを使用し、無効なアドレスの場合は停止してください。
Write-1-to-Clear(W1C)レジスタパターン
多くのステータスレジスター(特に i.MX RT、STM32)は、0 ではなく 1 を書き込むことでクリアされます:
uint32_t status = mmio_read(&USB1_USBSTS);
mmio_write(&USB1_USBSTS, status); // Write bits back to clear them
確認対象として意図した、仕様に記載された W1C ビットのみを書き込んでください。PORTSC のような制御が混在するレジスターでは、レジスターごとのマスキングが必要です。誤り: status &= ~bit は W1C レジスターでは何も行いません。
プラットフォームの安全性と注意点
⚠️ 電圧許容範囲:
- ほとんどのプラットフォームで GPIO の最大電圧は 3.3V です(STM32 の FT ピンを除き、5V 耐性はありません)
- 5V インターフェースにはレベルシフターを使用してください
- データシートに記載された電流制限を確認してください(通常 6~25mA)
Teensy 4.x: FlexSPI はフラッシュ/PSRAM 専用です • EEPROM はエミュレートされます(書き込みは 10Hz 未満に制限) • LPSPI の最大速度は 30MHz です • 周辺機器が動作中に CCM クロックを変更しないでください
STM32 F7/H7: クロックドメインの設定は周辺機器ごとに異なります • DMA ストリーム/チャネルの割り当ては固定です • GPIO の速度はスルーレート/消費電力に影響します
nRF52: SAADC は電源投入後にキャリブレーションが必要です • GPIOTE のチャネル数には制限があります(8 チャネル) • 無線は優先度レベルを共有します
SAMD: SERCOM のピンマルチプレクサー設定には注意が必要です • GCLK のルーティングが重要です • M0+ バリアントでは DMA が限られています
モダンな Rust:static mut は使用しないでください
正しいパターン:
static READY: AtomicBool = AtomicBool::new(false);
static STATE: Mutex<RefCell<Option<T>>> = Mutex::new(RefCell::new(None));
// Access: critical_section::with(|cs| STATE.borrow_ref_mut(cs))
static mut は避けてください。同期されていない並行アクセスや無効な参照は、未定義動作を引き起こす可能性があります。宣言するだけでは未定義動作にはなりません。
アトミック順序付け: Relaxed(CPU のみ) • Acquire/Release(共有状態) • AcqRel(CAS) • SeqCst(必要となることはまれです)
🎯 割り込み優先度と NVIC 設定
プラットフォーム固有の優先度レベル:
- M0/M0+: 2~4 優先度レベル(制限あり)
- M3/M4/M7: 8~256 優先度レベル(設定可能)
主要な原則:
- 数値が小さいほど優先度が高い(例: 優先度 0 は優先度 1 をプリエンプトします)
- 同じ優先度レベルの ISR は互いにプリエンプトできません
- 優先度グループ化: プリエンプション優先度とサブ優先度(M3/M4/M7)
- 時間的に重要な処理(DMA、タイマー)には最高優先度(0~2)を確保する
- 通常のペリフェラル(UART、SPI、I2C)には中間優先度(3~7)を使用する
- バックグラウンドタスクには最低優先度(8 以上)を使用する
設定:
- C/C++:
NVIC_SetPriority(IRQn, priority)またはHAL_NVIC_SetPriority() - Rust:
NVIC::set_priority()または PAC 固有の関数を使用する
🔒 クリティカルセクションと割り込みマスキング
目的: ISR とメインコードによる同時アクセスから共有データを保護します。
C/C++:
uint32_t primask = __get_PRIMASK();
__disable_irq(); /* critical section */ __set_PRIMASK(primask);
// Preserve nesting; PRIMASK does not mask NMI or HardFault.
// M3/M4/M7: Mask only lower-priority interrupts
uint32_t basepri = __get_BASEPRI();
__set_BASEPRI(priority_threshold << (8 - __NVIC_PRIO_BITS));
/* critical section */
__set_BASEPRI(basepri);
Rust: cortex_m::interrupt::free(|cs| { /* use cs token */ })
ベストプラクティス:
- クリティカルセクションは短く保つ(ミリ秒ではなくマイクロ秒)
- 可能な場合は PRIMASK よりも BASEPRI を優先する(高優先度 ISR の実行を許可します)
- 割り込みを無効化する代わりに、可能な場合はアトミック操作を使用する
- クリティカルセクションの根拠をコメントで文書化する
🐛 ハードフォールトのデバッグの基本
よくある原因:
- アライメントされていないメモリアクセス(特に M0/M0+)
- ヌルポインタの逆参照
- スタックオーバーフロー(SP の破損、またはヒープ/データ領域へのオーバーフロー)
- 不正命令、またはデータをコードとして実行
- 読み取り専用メモリや無効なペリフェラルアドレスへの書き込み
調査パターン(M3/M4/M7):
HFSR(HardFault Status Register)でフォールトの種類を確認するCFSR(Configurable Fault Status Register)で詳細な原因を確認するMMFAR/BFARでフォールトアドレスを確認する(有効な場合)- スタックフレームを検査する:
R0-R3, R12, LR, PC, xPSR
プラットフォームの制限:
- M0/M0+: フォールト情報が限られる(CFSR、MMFAR、BFAR なし)
- M3/M4/M7: 完全なフォールトレジスタが利用可能
デバッグのヒント: ハードフォールトハンドラーを使用して、リセット前にスタックフレームをキャプチャし、レジスタを出力またはログに記録します。
📊 Cortex-M アーキテクチャの違い
| 機能 | M0/M0+ | M3 | M4/M4F | M7/M7F |
|---|---|---|---|---|
| 最大クロック | 約 50 MHz | 約 100 MHz | 約 180 MHz | 約 600 MHz |
| ISA | Thumb-1 のみ | Thumb-2 | Thumb-2 + DSP | Thumb-2 + DSP |
| MPU | M0+ はオプション | オプション | オプション | オプション |
| FPU | なし | なし | M4F: 単精度 | M7F: 単精度 + 倍精度 |
| キャッシュ | なし | なし | なし | I-cache + D-cache |
| TCM | なし | なし | なし | ITCM + DTCM |
| DWT | なし | あり | あり | あり |
| フォールト処理 | 限定的(HardFault のみ) | 完全 | 完全 | 完全 |
🧮 FPU コンテキストの保存
レイジースタッキング(M4F/M7F ではデフォルト): FPU コンテキスト(S0-S15、FPSCR)は、ISR が FPU を使用する場合にのみ保存されます。FPU を使用しない ISR のレイテンシを削減しますが、タイミングが変動します。
確定的なレイテンシのために無効化する: ハードリアルタイムシステム、または ISR が常に FPU を使用する場合は、FPU->FPCCR を設定します(LSPEN ビットをクリア)。
🛡️ スタックオーバーフロー保護
MPU ガードページ(最良): スタックの下にアクセス不可の MPU リージョンを設定します。M3/M4/M7 では MemManage フォールトをトリガーします。M0/M0+ では制限があります。
カナリア値(ポータブル): スタックの底にマジック値(例: 0xDEADBEEF)を配置し、定期的にチェックします。
ウォッチドッグ: タイムアウトによる間接的な検出で、復旧手段を提供します。最良: MPU ガードページ、そうでなければカナリア + ウォッチドッグ。
🔄 ワークフロー
- 要件を明確にする → 対象プラットフォーム、周辺機器の種類、プロトコルの詳細(速度、モード、パケットサイズ)
- ドライバーの骨組みを設計する → 定数、構造体、コンパイル時設定
- コア機能を実装する → init()、ISR ハンドラー、バッファーロジック、ユーザー向け API
- 検証する → 使用例と、タイミング、レイテンシー、スループットに関する注記
- 最適化する → 必要に応じて DMA、割り込み優先度、RTOS タスクを提案する
- 反復する → ハードウェアとのやり取りからフィードバックが得られたら、改良版で調整する
🛠 例:外部センサー用 SPI ドライバー
パターン: トランザクションベースの読み書きを行う、ノンブロッキング SPI ドライバーを作成します。
- SPI を設定する(クロック速度、モード、ビット順)
- 適切なタイミングで CS ピンを制御する
- レジスターの読み書き操作を抽象化する
- 例:WHO_AM_I 用の
sensorReadRegister(0x0F) - 転送長、オーバーヘッド、タイミング制約を測定した後、DMA の使用を検討する
プラットフォーム固有の API:
- Teensy 4.x:
SPI.beginTransaction(SPISettings(speed, order, mode))→SPI.transfer(data)→SPI.endTransaction() - STM32:
HAL_SPI_Transmit()/HAL_SPI_Receive()または LL ドライバー - nRF52:
nrfx_spi_xfer()またはnrf_drv_spi_transfer() - SAMD:SPI マスターモードで SERCOM を設定し、
SERCOM_SPI_MODE_MASTERを使用する
制限事項
- タスクが上記の説明範囲に明確に一致する場合にのみ、このスキルを使用してください。
- 出力を、環境固有の検証、テスト、または専門家によるレビューの代わりとして扱わないでください。
- 必要な入力、権限、安全上の境界、または成功基準が不足している場合は、作業を中断して確認を求めてください。
ハードウェアの制限、メモリーマップ、キャッシュメンテナンス、NVIC 優先度ビット、クロック、ピンの許容範囲は、正確な MCU リビジョンとボードによって異なります。アーキテクチャの比較は例示であり、最大クロックの認証を示すものではありません。接続したハードウェアに書き込んだり通電したりする前に、データシートを確認してください。割り込みマスキングについては、CMSIS コアレジスターのセマンティクスを参照してください。