arm-cortex-expert について
ARM Cortex-M のファームウェアやドライバー開発向けのエンジニアリングスキルです。Teensy、STM32、nRF52、SAMD の周辺機器、並行処理、メモリー、タイミングを扱います。
主な機能
- 01周辺機器ドライバーとファームウェア設計
- 02HAL、ベアメタル、プラットフォームライブラリーの指針
- 03割り込み、DMA、並行処理のパターン
- 04キャッシュ整合性とメモリー安全性の指針
- 05Teensy、STM32、nRF52、SAMD の対応
活用シーン
- 01I²C、SPI、UART ドライバーの実装
- 02割り込み駆動データパイプラインの設計
- 03DMA とキャッシュ整合性の問題調査
- 04ARM Cortex-M リアルタイムファームウェアの計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rmyndharis/ antigravity-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++: レジスタアクセスをラップし、読み取りの前後に __DMB() (データメモリバリア)、書き込みの後に __DSB() (データ同期バリア) を使用します。ヘルパー関数を作成します: mmio_read()、mmio_write()、mmio_modify()。
Rust: 揮発性の読み取り/書き込みの周囲で 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 バイトの倍数
- アラインメントに違反すると、キャッシュ無効化時に隣接メモリが破壊されます
メモリ配置戦略 (最良から最悪へ):
-
DTCM/SRAM (キャッシュ不可、CPU アクセスが最速)
- C++:
__attribute__((section(".dtcm.bss"))) __attribute__((aligned(32))) static uint8_t buffer[512]; - Rust:
#[link_section = ".dtcm"] #[repr(C, align(32))] static mut BUFFER: [u8; 512] = [0; 512];
- C++:
-
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: USBSTS、PORTSC、CCM ステータス。誤り: status &= ~bit は W1C レジスタでは何も行いません。
プラットフォームの安全性と注意点
⚠️ 電圧耐性:
- ほとんどのプラットフォーム: GPIO 最大 3.3V (STMicroelectronics の FT ピンを除き 5V 耐性はありません)
- 5V インターフェースにはレベルシフタを使用してください
- データシートの電流制限 (通常 6-25mA) を確認してください
Teensy 4.x: FlexSPI は Flash/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++:
__disable_irq(); /* critical section */ __enable_irq(); // Blocks all
// 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)を使用する - 高スループット(>500 kHz)では、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:
SERCOM_SPI_MODE_MASTERを使用して、SERCOM を SPI マスターモードに設定する