embedded-stm32 について
STM32 HAL を使うファームウェア開発者向けのスキルです。生成コードとアプリケーションロジックの分離を軸に、周辺機器、中断、メモリやタイミングの制約、テストを扱います。
主な機能
- 01CubeMX プロジェクト構成
- 02HAL 周辺機器と DMA の利用
- 03割り込みと並行処理
- 04メモリとタイミングの制約
- 05段階的なテストとハードウェアデバッグ
活用シーン
- 01STM32 HAL ファームウェア構成の計画
- 02ノンブロッキングな周辺機器転送の実装
- 03割り込みと DMA のデータ共有の確認
- 04ホスト単体テストとハードウェアインザループテストの設計
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill embedded-stm32インストール後、Claude Code で/embedded-stm32を入力して読み込まれたか確認します。
詳細
STM32 組み込み/HAL 開発
このスキルでは、STM32 HAL を使用した STM32 マイクロコントローラーのファームウェア開発について説明します。プロジェクト構成、ペリフェラルと割り込みの処理、メモリとタイミングの制約、ハードウェアを重視したコードのテスト戦略を扱います。
STM32 HAL ファームウェア開発のワークフロー
- CubeMX でハードウェアを設定する —
.iocファイルでクロック、ピン、ペリフェラルを設定し、HAL 初期化コードを生成します。 - 生成コードと手書きコードを分離する — CubeMX が生成したファイルは、指定された
USER CODE BEGIN/ENDブロック以外は変更せず、アプリケーションロジックは別のファイルに記述します。 - ペリフェラルを一度だけ初期化する —
HAL_*_Init()の呼び出しはmain()/MX_*_Init()に集約し、コード内の別の場所で場当たり的に再設定しないようにします。 - 割り込みハンドラーを記述する — ISR(
HAL_*_Callback関数、EXTI/DMA/タイマーの IRQ ハンドラー)は短く保ちます。フラグを設定するかキューにデータを追加し、実際の処理はメインループまたは RTOS タスクに委ねます。 - 高スループットの I/O には DMA を使用する — ポーリングによって処理をブロックしたり CPU サイクルを消費したりする UART/SPI/I2C/ADC 転送には DMA を設定します。
- あらゆる処理にタイムアウトを設ける — ブロッキングする HAL 呼び出しとハードウェアの待機ループには、必ずタイムアウトと明示的なエラー処理を設けます。
- 段階的にテストする — まずホストビルドでハードウェアに依存しない純粋なロジックを単体テストし、その後、ハードウェアインザループテストでペリフェラルの動作を検証します。
- フラッシュ書き込みとデバッグを行う — デバッガーと SWD/JTAG(ST-Link、OpenOCD、または J-Link)を使用し、出力頻度を制限したシリアルログも併用して、実機での動作を検証します。
プロジェクト構成
- ボードサポート(ピン/クロック設定)、ドライバー、ミドルウェア(FreeRTOS、USB スタックなど)、アプリケーションロジック、テストは、明確に分けたディレクトリに配置します。
- CubeMX で再生成したときに独自のロジックが上書きされないよう、CubeMX 生成コードやベンダーコード(
Core/Src/main.c、Drivers/)を手書きのアプリケーションコードから分離します。生成ファイルを編集する場合は、/* USER CODE BEGIN */ ... /* USER CODE END */マーカー内に限ります。 - ハードウェアへのアクセスは、範囲を絞ったインターフェース(例:
motor_set_speed()を持つmotor_driver.h)の背後に隠し、実際のペリフェラルがなくてもホストビルドでアプリケーションロジックを単体テストできるようにします。 - クロックツリー(
SYSCLK、HCLK、PCLK1/PCLK2とその最大周波数)、ピンの割り当て、ペリフェラルの所有者、割り込み優先度の割り当てを、1 つの資料(README またはヘッダーコメント)に記載します。デバッグを始める際、まず確認する必要がある情報です。
STM32 HAL とペリフェラル
- 各ペリフェラルは必ず 1 か所で初期化します。複数のファイルに
HAL_*_Init()/HAL_*_MspInit()の呼び出しや場当たり的なレジスタ書き込みを分散させないでください。 - HAL の呼び出しは戻り値(
HAL_OK、HAL_ERROR、HAL_BUSY、HAL_TIMEOUT)を必ず確認し、タイムアウトやエラーを明示的に処理します。HAL_UART_TransmitのHAL_TIMEOUTを黙って無視することは、「自分の机では動く」バグの典型的な原因です。 - 制御ループや ISR など、時間制約の厳しい処理では、ブロッキングする HAL 呼び出し(
HAL_UART_Transmit、_IT/_DMAサフィックスのないHAL_I2C_Master_Receive)を使用しないでください。 - CPU がデータを 1 バイトずつ移動する処理にサイクルを費やすべきでない場合、高スループットの UART、SPI、I2C、ADC、またはタイマーキャプチャー処理には DMA を使用します。
- DMA 操作ごとに、バッファーの所有権と有効期間を記録します。DMA が読み取り中のバッファーは、転送完了コールバックが呼び出されるまで、CPU が変更したり解放したりしてはいけません。
volatileは ISR と共有するメモリ、またはメモリーマップドハードウェアレジスターに限って使用します。コンテキスト間でデータを共有するとき、volatileは適切なメモリーバリアやクリティカルセクションの代わりにはなりません。
例:DMA とアイドルライン検出を使用したノンブロッキング UART 受信
/* USER CODE BEGIN Includes */
#include "main.h"
#include <string.h>
#define RX_BUF_SIZE 128
static uint8_t rx_buf[RX_BUF_SIZE];
static volatile uint8_t rx_ready = 0;
static volatile uint16_t rx_len = 0;
extern UART_HandleTypeDef huart2;
extern DMA_HandleTypeDef hdma_usart2_rx;
/* USER CODE END Includes */
/* USER CODE BEGIN 2 */
void app_uart_start_receive(void)
{
/* Enable idle-line interrupt so a packet of unknown length completes
* the transfer without waiting for the buffer to fill. */
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
if (HAL_UART_Receive_DMA(&huart2, rx_buf, RX_BUF_SIZE) != HAL_OK) {
Error_Handler();
}
}
/* USER CODE END 2 */
/* USER CODE BEGIN 4 */
void USART2_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
HAL_UART_DMAStop(&huart2);
rx_len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
rx_ready = 1; /* Deferred: main loop processes the packet. */
/* Re-arm for the next packet. */
HAL_UART_Receive_DMA(&huart2, rx_buf, RX_BUF_SIZE);
return;
}
HAL_UART_IRQHandler(&huart2);
}
/* USER CODE END 4 */
/* Main loop excerpt: heavy work deferred out of the ISR. */
void app_main_loop(void)
{
if (rx_ready) {
rx_ready = 0;
uint16_t len = rx_len;
/* Copy out or parse rx_buf[0..len) here. Do NOT touch rx_buf
* again until this point, since DMA may already be refilling it. */
(void)len;
}
}
割り込みと並行処理
- ISRは短く、実行時間が予測可能な処理にします。フラグの設定、小さな固定サイズ値のコピー、ロックフリーキューへの追加などを行い、すぐに戻ります。
- 割り込み内で重い処理(解析、計算、ログ記録)を行わず、メインループ、RTOSタスク、または割り込みコンテキスト外で処理するイベントキューに委ねます。
- ISRとメインコンテキスト間で共有するデータは、クリティカルセクション(
__disable_irq()/__enable_irq()、またはRTOSのtaskENTER_CRITICAL())、アトミック操作、ロックフリーキューで保護します。複数バイトの読み書きがアトミックだと決めつけてはいけません。 - 割り込みハンドラー内での動的メモリ確保(
malloc/new)は避けます。メモリ確保は実行時間が予測可能ではなく、再入可能である保証もありません。 - 割り込み優先度の設定を明示し、文書化します(
NVIC_SetPriority)。優先度の設定を誤ると、時間制約の厳しい周辺機器が処理されなくなったり、FreeRTOSのconfigMAX_SYSCALL_INTERRUPT_PRIORITY制約に違反したりする可能性があります。
メモリとタイミング
- プロジェクトで明示的に許可され、予算が確保されていない限り、ファームウェアでのヒープ割り当ては避け、静的割り当てと固定サイズのバッファー/プールを優先します。
- ISRとRTOSタスクの両方でスタック使用量を確認します(リンク時のスタック使用量レポート、またはFreeRTOSの
uxTaskGetStackHighWaterMark())。組み込みターゲットでのスタックオーバーフローは、通常、気付かれないままメモリを破壊します。 - ルックアップテーブルには
constを指定し、限られたRAMを消費せず、リンカーによってフラッシュに配置されるようにします。 - ハードウェアに関わる値(レジスター値、プロトコルフィールド、バッファーサイズ)には、
int/longではなく固定幅整数型(uint8_t、int32_t、uint32_t)を使います。int/longの幅は保証されていません。 - ハードウェアの待機処理には必ずタイムアウトを設けます。上限のないステータスフラグのポーリングは、ハードウェアがフラグをセットしない場合(クロック設定の誤りや周辺機器の切断でよく発生します)に永久に停止します。
- 独立型/ウィンドウ型ウォッチドッグは、後付けではなく、初日からアプリケーション設計の一部として扱います。フィールド障害が発生してからではなく、メインループを書く前にリフレッシュ戦略を決めます。
テストとデバッグ
- 純粋なロジック(プロトコル解析、ステートマシン、数学処理)は、プロジェクト構造で定義された限定的なインターフェースの背後にあるハードウェア層をモックまたはスタブに置き換え、ホストビルド(ネイティブのgcc/clang)で単体テストします。
- ホストビルドではテストできない実際の周辺機器の動作(タイミング、電気信号、実際のセンサーデータ)には、ハードウェア・イン・ザ・ループテストを使います。
- デバッグビルドでは、あり得ないハードウェア状態に対するアサーション(
assert()またはconfigASSERT形式のカスタムマクロ)を追加します。コードサイズが厳しい場合は、リリースビルドでアサーションを無効にします。 - ライブデバッグにはSWD/JTAG(ST-Link/V2、OpenOCD、J-Link)、信号レベルの問題にはロジックアナライザーを使います。シリアルログにはレート制限を設けます(タイトなループやISR内でUARTにログを大量出力してはいけません)。
- 障害ハンドラー(
HardFault_Handlerなど)で有用な情報を取得できるようにします。リセット理由(RCC->CSR)、関連する障害ステータスレジスター(SCB->CFSR、SCB->HFSR)、ファームウェアのビルドバージョン/ハッシュを記録し、フィールドでのクラッシュを後から診断できるようにします。
よくある間違い
- CubeMXが生成したファイルの
USER CODEブロック外を変更し、次回の再生成時に変更が気付かれないまま削除される。 - タイムアウトなしでハードウェアのステータスフラグを無期限にビジーウェイトし、ハードウェアに異常があるたびにファームウェアを停止させる。
- 同期を取らずにDMAとCPUでバッファーを共有する(データキャッシュを備えたコアではキャッシュを無効化する、または転送完了フラグ/コールバックが発生する前に読み取る)。
- 低電力モード(Stop/Standby)から復帰した後も、周辺機器のレジスター状態が変わっていないと思い込む。これらのモードからの復帰後に再初期化が必要な周辺機器は多数あります。
コメント
Markdown 対応