1. はじめに
pico SDKには例愛発生時のハンドラを登録する仕組みが用意されています。
この機能を使用し、Hard Fault発生時にPCなどのレジスタし、発生k佐保を特定できる情報を表示するハンドラを作成しました。
2. Hard Faultとは
通常処理または例外処理中にエラーが発生したことによって、発生する例外です。Cortex-M0/M0+においては、以下のとうな要因で発生します。
- アラインメントエラー
- BKPT命令の実行(デバッガ未使用時)
- 未定義命令の実行
3. 作成するハンドラの処理内容
ハンドラの処理内容は以下のようになっています。
- 例外発生時の情報を取得する
- ユーザが指定した関数に制御を移す
Hard Fault発生時の情報表示はユーザが作成するようにしました。これはArduino環境でもpico-SDK環境でも動作できるように考慮したためです。
例外から復帰させているのは、USBによるシリアル出力を可能とするためです。
4. 実装したコードとテストプログラム
実装したコード:
ヘッダファイル
HardFaultHanler.h
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t r0;
uint32_t r1;
uint32_t r2;
uint32_t r3;
uint32_t r12;
uint32_t lr;
uint32_t pc;
uint32_t psr;
uint32_t sp;
} fault_regs_t;
extern void registerHardfaultHandler(void (*func)(fault_regs_t *regs));
#ifdef __cplusplus
}
#endif
ハンドラ部
HardFaultHanler.c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <hardware/exception.h>
#if PICO_RP2040
#include <RP2040.h>
#else
#include <RP2350.h>
#endif
#include "HardFaultHandler.h"
static fault_regs_t regs;
static void (*handler)(fault_regs_t *regs) = NULL;
static void
exceptionHandlerEntry(void)
{
uint32_t *sp;
uint32_t lr = (uint32_t)__builtin_return_address(0);
if ((lr & 4) == 0) {
sp = (uint32_t *)(__get_MSP() + 4);
} else {
sp = (uint32_t *)__get_PSP();
}
regs.r0 = sp[0];
regs.r1 = sp[1];
regs.r2 = sp[2];
regs.r3 = sp[3];
regs.r12 = sp[4];
regs.lr = sp[5];
regs.pc = sp[6];
regs.psr = sp[7];
regs.sp = (uint32_t)sp;
if ((regs.psr &0x1f) != 0) {
// 例外処理中にfaultが発生」した場合は、
// 表示ができない可能性があるため、ここで停止させる。
for (;;) {
;
}
}
// 例外からの戻りアドレスを変更
sp[6] = ((uint32_t)handler) & 0xfffffffeU;
// レジスタの値を格納した構造体のアドレスを第一引数として渡す
sp[0] = (uint32_t)®s;
}
void
registerHardfaultHandler(void (*func)(fault_regs_t *regs))
{
exception_set_exclusive_handler(HARDFAULT_EXCEPTION, exceptionHandlerEntry);
handler = func;
}
テスト用のプログラム
HardFaultHanler.ino
#include <Arduino.h>
#include <stdlib.h>
#include <hardware/exception.h>
#include "HardFaultHandler.h"
void
showFaultStatus(fault_regs_t *regs)
{
Serial.printf("******** Hard fault ********\n");
Serial.printf("r0: %08x r1:%08x r2:%08x r3: %08x\n", regs->r0, regs->r1, regs->r2, regs->r3);
Serial.printf("r12:%08x lr:%08x pc:%08x psr:%08x\n", regs->r12, regs->lr, regs->pc, regs->psr);
Serial.printf("******** Halted ********\n");
for (;;) {
delay(1000);
}
}
void
setup()
{
while (!Serial)
{
;
}
delay(1000);
Serial.printf("build [%s %s]\n", __DATE__, __TIME__);
registerHardfaultHandler(showFaultStatus);
}
void
loop()
{
__breakpoint();
}
5. 実行結果
動作確認は、rp2040で実施しました。
実行すると、以下のように例外発生時のレジスタ情報が表示されます。
build [Feb 24 2025 06:56:05]
******** Hard fault ********
r0: 100030c5 r1:1000320d r2:d0000150 r3: 20001d44
r12:2000026d lr:10003a1b pc:100032d8 psr:01000000
******** Halted ********
addr2lineコマンドを使えば、pcの値からソース上のどこで例外が発生したかを特定できますが、ちょっと手間がかかるのが難点です。