cpp-embedded について
組み込み C/C++ 開発者向けのコーディングスキルで、メモリ管理、ファームウェア設計、デバッグ、安全対策を扱います。ISR、スタック、ハードウェアアクセスに伴う問題や、非決定的なメモリ確保を避けるためのパターンと資料を提供します。
主な機能
- 01決定論的なメモリ管理とオブジェクトプールのパターン
- 02ISRとタスク間の通信パターン
- 03C/C++の規約と設計パターン
- 04HardFault、スタック、メモリのデバッグ
- 05安全なハードウェア利用とファームウェア設計のガイド
活用シーン
- 01ヒープ確保を使わないファームウェアのデータ構造設計
- 02HardFaultやヒープ破損の調査
- 03ISRからタスクへのデータ転送の実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bmad-labs/ skills --skill cpp-embeddedインストール後、Claude Code で/cpp-embeddedを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/architecture.mdc-patterns.mdcoding-style.mddebugging.mddesign-patterns.mdmemory-patterns.mdsafety-hardware.mdstm32-pitfalls.md詳細
組み込み C および C++ のスキル
クイックナビゲーション
- メモリパターン、ESP32 のヒープ断片化、ETL コンテナー →
references/memory-patterns.md- C99/C11 のパターン、UART の読み取り時クリア、_Static_assert →
references/c-patterns.md- デバッグのワークフロー(スタックオーバーフロー、ヒープ破損、HardFault) →
references/debugging.md- コーディングスタイルと規約 →
references/coding-style.md- ファームウェアのアーキテクチャ、RTOS IPC、低消費電力、ESP32 のディープスリープ、CI/CD →
references/architecture.md- STM32 の落とし穴、CubeMX、デバッグが難しい問題、コードレビュー →
references/stm32-pitfalls.md- デザインパターン、組み込み向け SOLID、HAL の設計、C/C++ のコールバック →
references/design-patterns.md- MPU による保護、ウォッチドッグの階層、安全性が重要なシステム、プロトコル、リンカースクリプト →
references/safety-hardware.md
組み込みメモリの考え方
組み込みシステムには、OS によるセーフティネットがありません。不正なポインター参照は、丁寧なセグメンテーション違反を起こすのではなく、メモリをひそかに破損させ、数時間後に HardFault を引き起こしたり、ISR 内でハングしたりします。ホスト環境よりも、あらゆるメモリ割り当ての判断が重大です。
組み込みメモリを左右する原則は 3 つあります。
利便性より決定性を優先します。 動的割り当て(malloc/new)は、処理時間と失敗の両面で非決定的です。MISRA C Rule 21.3 と MISRA C++ Rule 21.6.1 は、初期化後の動的メモリ使用を禁止しています。MISRA に準拠しない場合でも、本番環境の処理ではヒープ割り当てを避けてください。
サイズはコンパイル時に把握します。 組み込みソフトウェアでは、各オブジェクト型の最大数が固定されています。それを前提に設計してください。UART メッセージバッファーが 8 個必要なら、コンパイル時に 8 個宣言します。実行時に最大数を調べることは避けてください。
ISR は神聖な領域です。 ISR 内では、割り当て、ブロッキング、リエントラントでない関数の呼び出しを絶対に行わないでください。ISR は最小限に保ち、フラグを設定するかリングバッファーに書き込み、実際の処理はタスクで行います。
割り当ての判断表
| 必要なもの | 解決策 | 備考 |
|---|---|---|
| 短命なローカルデータ | スタック | 1 フレームあたり 256 バイト未満に抑え、-fstack-usage でプロファイルします |
| 固定のシングルトンオブジェクト | ファイルスコープまたは関数スコープの static | main() の前にゼロ初期化されます |
| 固定数のオブジェクト配列 | オブジェクトプール(ObjectPool<T, N>) | O(1) の割り当て/解放で、断片化しません |
| 一時的なスクラッチ領域 | アリーナ/バンプアロケーター | 操作の終了時にアリーナ全体をリセットします |
| 可変サイズのメッセージ | 固定サイズスロットのリングバッファー | 最もシンプルな ISR セーフな通信パターンです |
| カスタムのライフタイム制御 | プレースメント new +静的ストレージ | 完全に制御でき、ヒープを使用しません |
| ISR 内では絶対に使用しない | スタック以外の上記すべて | アロケーターの呼び出しは ISR セーフではありません |
| 完全に避ける | malloc/new / std::vector | 非決定的で、断片化のリスクがあります |
重要なパターン
RAII リソースガード
構築時に取得し、破棄時に解放します。早期 return や例外を経由した場合でも、解放が保証されます(例外を使用する場合。組み込みではまれですが、例外を許可する C++ 環境では可能です)。
class SpiGuard {
public:
explicit SpiGuard(SPI_HandleTypeDef* spi) : spi_(spi) {
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
}
~SpiGuard() {
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}
// Non-copyable, non-movable — guard is tied to this scope
SpiGuard(const SpiGuard&) = delete;
SpiGuard& operator=(const SpiGuard&) = delete;
private:
SPI_HandleTypeDef* spi_;
};
// Usage: CS deasserts automatically at end of scope
void read_sensor() {
SpiGuard guard(&hspi1);
// ... transfer bytes ...
} // CS deasserts here
ISR からタスクへの通信:リングバッファーとピンポン DMA
ISR/DMA からタスクにデータを渡すには、主に 2 つのパターンがあります。
- SPSC リングバッファー(ビットマスクによるインデックス指定を使用する 2 の累乗の容量) — データレートが変動するストリームに最適です。完全な実装については
references/memory-patterns.md§3 を参照してください。容量が 2 の累乗であることを検証するには、必ず_Static_assertまたはstatic_assertを使用してください。 - ピンポン(ダブル)バッファリング — 一定量のバースト DMA 転送に最適です。ISR が一方のバッファーに入力している間、タスクはもう一方を処理します。
ISR からタスクへの通信について尋ねられた場合、特定の DMA シナリオの代替案としてピンポン方式に触れる場合でも、ビットマスクによるインデックス指定を用いる 2 の累乗のリングバッファーを主要パターンとして含めてください。
静的オブジェクトプール
ヒープを使用せず、型 T のオブジェクトを N 個事前に割り当て、O(1) の割り当て/解放を実現します。
アリーナアロケーターの完全な説明は references/memory-patterns.md §1 を、CRTP パターンの説明は §2 を参照してください。
template<typename T, size_t N>
class ObjectPool {
public:
template<typename... Args>
T* allocate(Args&&... args) {
for (auto& slot : slots_) {
if (!slot.used) {
slot.used = true;
return new (&slot.storage) T(std::forward<Args>(args)...);
}
}
return nullptr; // Pool exhausted — handle at call site
}
void free(T* obj) {
obj->~T();
for (auto& slot : slots_) {
if (reinterpret_cast<T*>(&slot.storage) == obj) {
slot.used = false;
return;
}
}
}
private:
struct Slot {
alignas(T) std::byte storage[sizeof(T)];
bool used = false;
};
Slot slots_[N]{};
};
揮発性ハードウェアレジスタへのアクセス
volatile は、値がコンパイラーの把握外で変更される可能性があること(ハードウェアが書き込めること)をコンパイラーに伝えます。
volatile がないと、コンパイラーは読み取った値をレジスターにキャッシュし、ハードウェアによる更新を認識できなくなる場合があります。
// Define register layout matching the hardware manual
struct UartRegisters {
volatile uint32_t SR; // Status register
volatile uint32_t DR; // Data register
volatile uint32_t BRR; // Baud rate register
volatile uint32_t CR1; // Control register 1
};
// Map to the hardware base address
auto* uart = reinterpret_cast<UartRegisters*>(0x40011000U);
// Read status — volatile ensures each read hits the hardware
if (uart->SR & (1U << 5)) { // RXNE bit
uint8_t byte = static_cast<uint8_t>(uart->DR);
}
割り込みセーフなアクセス
ISR とタスクの間でデータを共有するには、クリティカルセクションまたは std::atomic が必要です。
型が単一のロード/ストアに収まる場合(通常はポインターサイズ以下)は、アトミックを使用してください。より大きな構造体にはクリティカルセクションを使用してください。
#include <atomic>
// Atomic: ISR and task can access without disabling interrupts
std::atomic<uint32_t> adc_value{0};
// In ISR:
void ADC_IRQHandler() {
adc_value.store(ADC1->DR, std::memory_order_relaxed);
}
// In task:
uint32_t val = adc_value.load(std::memory_order_relaxed);
// Critical section for larger structures (ARM Cortex-M):
struct SensorFrame { uint32_t timestamp; int16_t x, y, z; };
volatile SensorFrame latest_frame{};
void update_frame_from_isr(const SensorFrame& f) {
__disable_irq();
latest_frame = f;
__enable_irq();
}
スマートポインターの方針
| ポインター型 | 組み込みで使用するか | 指針 |
|---|---|---|
| 生ポインター(参照用) | はい | 所有権を持たない参照に使用し、命名で所有権を明確にします |
| 生ポインター(所有用) | 慎重に | ライフタイムが明確な静的領域/プール領域でのみ使用します |
std::unique_ptr | はい。ただし慎重に | オーバーヘッドはゼロです。プールオブジェクトにはカスタムデリーターを使用します |
std::unique_ptr +カスタムデリーター | はい | 破棄時にプールオブジェクトをプールに戻します |
std::shared_ptr | 避けてください | 参照カウントはヒープを使用し、非決定的です |
std::weak_ptr | 避けてください | shared_ptr に依存し、同じ懸念があります |
// unique_ptr with pool deleter — zero heap, automatic return to pool
ObjectPool<SensorData, 8> sensor_pool;
auto deleter = [](SensorData* p) { sensor_pool.free(p); };
using PooledSensor = std::unique_ptr<SensorData, decltype(deleter)>;
PooledSensor acquire_sensor() {
return PooledSensor(sensor_pool.allocate(), deleter);
}
コンパイル時の優先事項
実行時チェックよりも、コンパイル時の計算と検証を優先してください。
// constexpr: computed at compile time, no runtime cost
constexpr uint32_t BAUD_DIVISOR = PCLK_FREQ / (16U * TARGET_BAUD);
static_assert(BAUD_DIVISOR > 0 && BAUD_DIVISOR < 65536, "Baud divisor out of range");
// std::array: bounds info preserved, unlike raw arrays
std::array<uint8_t, 64> tx_buffer{};
// std::span: non-owning view, no allocation, C++20 but often available via ETL
// std::string_view: for string literals and buffers, no heap
// CRTP replaces virtual dispatch — zero runtime overhead
// See references/memory-patterns.md §2 for full example
組み込みで避けるべき C++ の機能:
| 避けるもの | 理由 | 代替手段 |
|---|---|---|
-fexceptions | コードサイズの増加(10~30%)、非決定的なスタック巻き戻し | std::optional、std::expected(C++23/ポリフィル)、エラーコード |
-frtti / typeid / dynamic_cast | 実行時の型テーブルによって ROM が増加します | CRTP、明示的な型タグ |
std::vector、std::string、std::map | ヒープ割り当て | std::array、ETL コンテナー |
std::thread | OS プリミティブが必要です | RTOS タスク |
std::function | キャプチャーのためにヒープ割り当てが発生します | 関数ポインター、テンプレート |
| 深い階層での仮想デストラクター | vtable のサイズ、間接ディスパッチ、インライン化の妨げ | CRTP またはフラットな階層(2 階層以下)。クリティカルでないパスでは仮想関数も使用できます |
ARM ターゲット向けには -fno-exceptions -fno-rtti を指定してコンパイルしてください。固定サイズの etl::vector、etl::map、etl::string の代替として、Embedded Template Library (ETL) を使用してください。
よくあるアンチパターン
これらのパターンは、本番ファームウェアで実際のバグを引き起こします。把握しておけば、デバッグにかかる時間を大幅に節約できます。
| アンチパターン | 問題 | 対策 |
|---|---|---|
キャプチャ付きの std::function | キャプチャが小容量バッファのしきい値(実装により約16~32バイト)を超えると、ヒープに割り当てられます | 関数ポインターと void* context、テンプレート呼び出し可能オブジェクト、またはテンプレートパラメーターにラムダを直接渡す方法を使います |
ループ内での Arduino String | 連結や変換のたびにヒープに割り当てられます。10 Hz × 4センサーでは1日あたり340万回の割り当てとなり、断片化を招きます | 固定長の char[] と snprintf を使います。複雑な文字列には etl::string<N> を使います |
周期タスク内での new/delete | 同様に断片化を招き、MISRA C++ Rule 21.6.1に違反します | オブジェクトプール、静的配列、またはETLの固定容量コンテナーを使います |
あらゆる場所での std::shared_ptr | アトミックな参照カウントのオーバーヘッドがあり、制御ブロックがヒープに置かれます | プール用デリーターを使う std::unique_ptr、または所有権を明確にした生ポインターを使います |
| 深い仮想関数階層 | クラスごとにvtable(ROM)が必要となり、間接ディスパッチが発生してインライン化を妨げます | CRTPまたはフラットな階層(2レベル以下)を使います |
隠れた std::string/std::vector | 操作のたびに動的割り当てが発生します。多くの場合、ライブラリーAPIに隠れています | ETLコンテナー(etl::string<N>、etl::vector<T,N>)を使います |
スレッド同期に volatile を使う | volatile は、コンパイラーによる読み書きのキャッシュや削除を防ぐだけです。スレッド間の順序変更を防いだり、原子性を保証したりするものではありません。volatile 変数のデータ競合も依然として未定義動作です | 明示的なメモリー順序指定を使って std::atomic を使用します(シングルコアのCortex-Mでは、ISRからタスクへの受け渡しに memory_order_relaxed で十分です) |
| ISRハンドラー名の不一致 | ISR名のスペルミスは気付かれないまま Default_Handler にフォールスルーし、システムがハングまたはリセットします | スタートアップの .s ベクターテーブルと照合して名前を確認します。リンク時に不足シンボルを検出するには -Wl,--undefined=USART1_IRQHandler を使います |
ISR内での printf/sprintf | ヒープ割り当て、ロック、再入不可により、クラッシュやデッドロックが発生します | ISRの外で、事前に割り当てたバッファーに snprintf で出力するか、ITM/SWOトレース出力を使います |
| 上限のない再帰 | スタックが1~8KBのMCUではスタックオーバーフローを引き起こします。MISRA C Rule 17.2は再帰を禁止しています | 明示的なスタックを使った反復処理に置き換えます |
隠れた割り当てのチェックリスト: STL型を使う前に、割り当てが発生するか確認してください。よくある見落としとして、std::string::operator+=、std::vector::push_back(再割り当て)、std::function(型消去されたキャプチャ)、std::any、std::regex があります。
よくあるメモリーバグの診断
| 症状 | 考えられる原因 | 最初の対応 |
|---|---|---|
| 数時間稼働した後にクラッシュする | ヒープの断片化 | プール/ETLコンテナーに切り替えます。MISRA C Rule 21.3を参照してください。ESP32固有のガイダンスは references/memory-patterns.md §9 を参照してください |
| BFAR/MMFARが有効なHardFault | nullポインター/不正ポインターの逆参照、またはバスフォルト | CFSRのサブレジスターを読み取ります。アドレスレジスターを使う前にMMARVALID/BFARVALIDを確認してください。references/debugging.md §4 を参照してください |
| スタックポインターが誤った領域を指している | スタックオーバーフロー | .su ファイルを確認します。MPUガード領域を追加してください。references/debugging.md §1 を参照してください |
| ISRのデータが古く見える | volatile の不足 | 共有変数に volatile を追加し、ISRのデータ経路を監査してください |
| ISR付近でランダムな破損が発生する | データ競合 | アトミック操作またはクリティカルセクションを適用してください。references/debugging.md §3 を参照してください |
| 解放後使用 | まだ参照されているオブジェクトがプールに返却された | エイリアスがないことを確認してください。プール用デリーターを使うunique_ptrを使用します |
| タスクでMPUフォルトが発生する | タスクがスタックを隣接領域までオーバーフローさせた | スタックサイズを増やすか、フレームの深さを減らします |
| 未初期化値の読み取り | ローカル変数が代入前に使用された | -Wuninitialized を有効にし、すべてのローカル変数を初期化します |
最初の float 演算でHardFaultが発生する | CPACRでFPUが有効になっていない | 浮動小数点コードの実行前に `SCB->CPACR |
| ISRが何もせず、デフォルトハンドラーが実行される | ISR関数名のスペルミス | スタートアップの .s ベクターテーブルと照合して名前を確認します |
デバッグツールの選択フローチャート
Is the bug reproducible on a host (PC)?
├── YES → Use AddressSanitizer (ASan) + Valgrind
│ Compile embedded logic for PC with -fsanitize=address
│ See references/debugging.md §5
└── NO → Is it a memory layout/access issue?
├── YES → Enable MPU; add stack canaries; read CFSR on fault
│ See references/debugging.md §1, §4
└── NO → Is it a data-race between ISR and task?
├── YES → Audit shared state; apply atomics/critical section
│ See references/debugging.md §3
└── NO → Use GDB watchpoint on the corrupted address
See references/debugging.md §6
静的解析:clang-analyzer-* と cppcoreguidelines-* チェックを指定して clang-tidy を実行します。
C コードには cppcheck --enable=all を実行します。どちらも、ターゲットハードウェアで実行する前に多くの問題を検出できます。
エラー処理の方針
それぞれ異なる種類の障害に対応する、4 つのレイヤーを使います。
回復可能なエラー(コンテキスト付き) — 失敗の理由を伝える必要がある場合は、std::expected(C++23)、または C++17 用のヘッダーオンリーなポリフィルである tl::expected/etl::expected を使用します。ヒープ割り当てと例外は発生せず、型安全にエラーを伝播できます。
enum class SensorError : uint8_t { not_ready, crc_fail, timeout };
[[nodiscard]] std::expected<SensorReading, SensorError> read_sensor() {
if (!sensor_ready()) return std::unexpected(SensorError::not_ready);
auto raw = read_raw();
if (!verify_crc(raw)) return std::unexpected(SensorError::crc_fail);
return SensorReading{.temp = convert(raw)};
}
回復可能なエラー(シンプル) — 唯一の失敗が「値がないこと」である場合は、std::optional を使用します。
[[nodiscard]] std::optional<SensorReading> read_sensor() {
if (!sensor_ready()) return std::nullopt;
return SensorReading{.temp = read_temp(), .humidity = read_humidity()};
}
[[nodiscard]] による強制: エラーコード、ステータス、または割り当て済みのポインターを返すすべての関数に [[nodiscard]] を付けます。これにより、呼び出し元が戻り値を黙って無視した場合にコンパイラーが警告します。ファームウェアで最も多いエラー処理のバグ(エラーコードの破棄)を検出できます。C では [[nodiscard]](C23)または __attribute__((warn_unused_result)) を使用します。
プログラミングエラー — assert、またはデバッグ情報を出力して停止するトラップを使用します。
void write_to_pool(uint8_t* buf, size_t len) {
assert(buf != nullptr);
assert(len <= MAX_PACKET_SIZE); // Trips in debug, removed in release with NDEBUG
// ...
}
回復不可能な実行時エラー — 障害の理由とレジスターを不揮発性メモリー(フラッシュ/EEPROM)に記録し、ウォッチドッグによるシステムのリセットを待ちます。リセット前に記録しないと、実機での障害に事後解析用のデータが残りません。専用のフラッシュセクターに CFSR、スタック上の PC、LR を書き込み、その後 NVIC_SystemReset() を呼び出すか、ループしてウォッチドッグを作動させます。
テスト可能なアーキテクチャ
ターゲットハードウェアなしで PC 上でテストできるファームウェアを作成します。重要なのは、明確な HAL 境界でビジネスロジックとハードウェア I/O を分離することです。
コンパイル時の依存性注入 — ハードウェアはコンパイル時に決まるため、仮想関数によるディスパッチではなくテンプレートを使用します。実行時のオーバーヘッドはゼロで、完全にテスト可能です。
// HAL interface as a concept (or just template parameter)
template<typename Hal>
class SensorController {
public:
explicit SensorController(Hal& hal) : hal_(hal) {}
std::optional<float> read_temperature() {
auto raw = hal_.i2c_read(SENSOR_ADDR, TEMP_REG, 2);
if (!raw) return std::nullopt;
return convert_raw_to_celsius(*raw);
}
private:
Hal& hal_;
};
// Production: uses real hardware
SensorController<StmHal> controller(real_hal);
// Test: uses mock — same code, no vtable, no overhead in production
SensorController<MockHal> test_controller(mock_hal);
ファームウェアのテストピラミッド:
- ユニットテスト(ホスト PC): モック HAL を使ったビジネスロジックのテスト — ASan/UBSan で実行でき、CI も高速です
- 統合テスト(QEMU): エミュレートされた Cortex-M 上でファームウェア全体をテスト — リンカーや起動時の問題を検出します
- ハードウェア・イン・ザ・ループ(HIL): 実際のターゲット上でテスト — タイミング、周辺機器、電気的な問題を検出します
ファームウェアアーキテクチャの選択
要件を満たす、最もシンプルなアーキテクチャを選択します。
| アーキテクチャ | 複雑さ | 最適な用途 |
|---|---|---|
| Superloop(ベアメタルポーリング) | 最低 | タスクが 5 個未満、タイミング要件が緩く、完全に決定論的な場合 |
| 協調型スケジューラー(時間起動型) | 低 | ハードリアルタイム、安全性が重要(IEC 61508 SIL 1–2)、解析可能な場合 |
| RTOS プリエンプティブ(FreeRTOS/Zephyr) | 中 | 複雑なマルチタスク、優先度ベースのスケジューリング |
| Active Object(QP フレームワーク) | 最高 | イベント中心、階層型ステートマシン、プロトコル処理 |
FreeRTOS の IPC の選択(タスク通知とキューとストリームバッファーの比較)、低消費電力のパターン、CI/CD パイプラインの設定、バイナリーサイズの予算管理については、references/architecture.md を参照してください。
STM32 CubeMX の落とし穴、HAL ドライバーと LL ドライバーの選択、デバッグが難しい組み込みの問題、コードレビューのチェックリストについては、references/stm32-pitfalls.md を参照してください。
ESP32 プラットフォームに関するガイダンス
ESP32/ESP32-S2/S3/C3 に関する質問に回答する際は、必ず次のプラットフォーム固有の注意点を考慮してください。
メモリアーキテクチャ — ESP32 に関する回答では必ず説明すること: ESP32 には、連続していない複数のメモリ領域があります。ESP32 に関する回答では毎回、バッファをどこに配置すべきかを明示してください。
- DRAM(約 320KB、高速):リングバッファ、DMA バッファ、ISR データ、FreeRTOS スタックに使用します。リアルタイム性や低レイテンシが重要なデータはすべてここに配置します。
- PSRAM(4~8MB、約 10 倍低速、-WROVER/S3 ではオプション):SD カードの書き込みバッファ、CSV フォーマット用バッファ、ウェブサーバー用バッファ、ディスプレイフレームバッファなど、大きな非リアルタイムデータに使用します。
heap_caps_malloc(size, MALLOC_CAP_SPIRAM)またはps_malloc()で割り当てます。ISR やタイトな制御ループでは PSRAM を使わないでください。アクセスレイテンシは約 100ns で、DRAM の約 10ns より遅いためです。 - ESP32 のバリアントに PSRAM がある場合は、大きなフォーマット用/書き込み用バッファを PSRAM に移し、リアルタイム処理に DRAM を空けることを推奨してください。PSRAM が利用できない場合は、DRAM への負荷とサイズの予算に触れてください。ユーザーがトレードオフを理解できるよう、両方の領域に必ず言及してください。
ESP32 での ETL: ESP32 で std::vector、std::string、または std::map を置き換える場合は、必ず Embedded Template Library(ETL) の名称と具体的な型を挙げて推奨してください:etl::vector<T, N>、etl::string<N>、etl::map<K, V, N>、etl::queue_spsc_atomic<T, N>。ETL は Arduino(lib_deps = ETLCPP/Embedded Template Library)と ESP-IDF(コンポーネントとして追加)の両方で ESP32 に対応しています。リングバッファなどの独自実装を提示する場合でも、実運用向けの代替案として ETL を検討するよう伝えてください。
ディープスリープと高速ウェイク: バッテリー駆動の ESP32 センサーノードについては、RTC メモリを利用した WiFi キャッシュ、静的 IP、センサーの強制モードのパターンが説明されている references/architecture.md を参照してください。
ESP32 でのタスクスタックのサイズ設定: WiFi タスクには 4096~8192 バイト、BLE には 4096~8192 バイト、TLS/SSL には 8192~16384 バイトが必要です。uxTaskGetStackHighWaterMark() で監視してください。詳細は references/memory-patterns.md §9 を参照してください。
ESP32 のヒープ断片化の完全な診断、監視、ETL の統合については、
references/memory-patterns.md §9 を参照してください。
コーディング規約の概要
詳しいガイドは references/coding-style.md を参照してください。主なルールは次のとおりです。
- 変数と関数:
snake_case - クラスと構造体:
PascalCase - 定数:
kConstantName(Google スタイル)、またはマクロにはALL_CAPS - メンバー変数:末尾にアンダースコアを付ける
- インクルードガード:
#pragma once(推奨)または#ifndef HEADER_H_ガード - const の正しさ:変更を行わない各メソッドに const を付け、変更されない各パラメーターにも const を付ける
[[nodiscard]]:戻り値を黙って破棄してはならない関数(エラーコード、プールの割り当てなど)に付ける
参照ファイル一覧
| ファイル | 読むタイミング |
|---|---|
references/memory-patterns.md | アリーナ、リングバッファ、DMA バッファ、ロックフリー SPSC、シングルトン、リンカーセクション、ESP32 のヒープ断片化、ETL コンテナーを実装するとき |
references/c-patterns.md | C99/C11 ファームウェア、C メモリプール、C のエラー処理、C/C++ の相互運用、MISRA C ルール、UART の読み取り時クリアの仕組み、バッファ検証用の _Static_assert を使用するとき |
references/debugging.md | スタックオーバーフロー、ヒープ破損、HardFault、データ競合、NVIC 優先度の問題の診断や、ASan/GDB の実行を行うとき |
references/coding-style.md | 命名規則、機能の使用状況表、構造体のパッキング、属性、インクルードガードを確認するとき |
references/architecture.md | ファームウェアアーキテクチャの選択、FreeRTOS の IPC パターン、低消費電力モード、ESP32 のディープスリープと高速ウェイク、CI/CD パイプラインの設定、バイナリサイズの予算を検討するとき |
references/stm32-pitfalls.md | CubeMX のコード生成に関する問題、HAL と LL の選択、デバッグが難しい問題(キャッシュ、優先度逆転、フラッシュストール)、コードレビューのチェックリストを確認するとき |
references/design-patterns.md | HAL の設計パターン(CRTP/テンプレート/仮想関数/不透明型)、依存性注入の戦略、組み込み向け SOLID、コールバックとトランポリンのパターンを確認するとき |
references/safety-hardware.md | MPU 保護パターン、ウォッチドッグの階層、IEC 61508 の障害復旧、周辺機器プロトコルの選択(UART/I2C/SPI/CAN)、リンカースクリプトによるメモリ配置を確認するとき |