mcpField
E
embedded-best-practices

embedded-best-practices

github.com/laurigates開発7103 回インストール今日更新
リポジトリ

embedded-best-practices について

Embedded Best Practices Skillは、ESP32とESP-IDFを中心に、組み込みシステムの実装とレビューを案内するスキルです。ファームウェア構成、FreeRTOSタスク、周辺機器、メモリ、エラー処理、電源管理を扱います。

主な機能

  1. 01ESP-IDFプロジェクトとコンポーネントの構成
  2. 02FreeRTOSタスクと同期の指針
  3. 03メモリ割り当てと安全な文字列処理
  4. 04エラー処理と縮退運転
  5. 05周辺機器と割り込みの実践
  6. 06WiFi、ログ、電源管理

活用シーン

  1. 01ESP-IDFファームウェアの構成
  2. 02FreeRTOSタスクとキュー設計のレビュー
  3. 03周辺機器の初期化と割り込みへの対応
  4. 04メモリと消費電力のリスク低減

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/laurigates/mcu-tinkering-lab --skill embedded-best-practices

インストール後、Claude Code で/embedded-best-practicesを入力して読み込まれたか確認します。

詳細

組み込み開発のベストプラクティススキル

このスキルでは、ESP32 と ESP-IDF を中心とした組み込みシステム開発について、包括的なガイダンスを提供します。

使用する場面

  • 新しいファームウェアコードを作成するとき
  • 実装方針をレビューするとき
  • 組み込み開発のパターンを学ぶとき
  • 問題をデバッグするとき
  • コードを最適化するとき

ESP-IDF プロジェクトの構成

推奨レイアウト
project/
├── main/
│   ├── CMakeLists.txt
│   ├── main.c
│   ├── Kconfig.projbuild
│   └── include/
│       └── project.h
├── components/
│   └── custom_component/
│       ├── CMakeLists.txt
│       ├── component.c
│       └── include/
│           └── component.h
├── CMakeLists.txt
├── sdkconfig.defaults
├── partitions.csv
└── README.md
コンポーネントの構成
  • 各コンポーネントの責務を1つにする
  • include/ に明確な公開インターフェースを設ける
  • src/ に非公開の実装を配置する
  • 依存関係を文書化する

FreeRTOS のベストプラクティス

タスクの設計
// Good: Proper task function
void sensor_task(void *pvParameters) {
    sensor_config_t *config = (sensor_config_t *)pvParameters;

    while (1) {
        // Do work
        read_sensor(config);

        // Must yield to prevent watchdog
        vTaskDelay(pdMS_TO_TICKS(100));
    }

    // Tasks should never return, but if they do:
    vTaskDelete(NULL);
}

// Create with appropriate stack
xTaskCreate(sensor_task, "sensor", 4096, &config, 5, &task_handle);
スタックサイズの設定
  • 一般的なタスクでは、4096 バイトから始めます
  • uxTaskGetStackHighWaterMark() を使って実際の使用量を測定します
  • 25% の安全マージンを加えます
  • カメラやネットワークのタスクには、8192 以上が必要な場合があります
同期
// Mutex for shared resource protection
SemaphoreHandle_t mutex = xSemaphoreCreateMutex();

// Use with timeout, never infinite wait in production
if (xSemaphoreTake(mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
    // Access shared resource
    xSemaphoreGive(mutex);
} else {
    ESP_LOGE(TAG, "Failed to acquire mutex");
}
キューの使用
// Prefer queues for inter-task communication
QueueHandle_t data_queue = xQueueCreate(10, sizeof(sensor_data_t));

// Send with timeout
sensor_data_t data = {.value = 42};
if (xQueueSend(data_queue, &data, pdMS_TO_TICKS(100)) != pdTRUE) {
    ESP_LOGW(TAG, "Queue full, dropping data");
}

// Receive
sensor_data_t received;
if (xQueueReceive(data_queue, &received, portMAX_DELAY) == pdTRUE) {
    process_data(&received);
}

メモリ管理

静的割り当てと動的割り当て
// Prefer static for fixed resources
static StaticTask_t task_buffer;
static StackType_t task_stack[4096];
TaskHandle_t task = xTaskCreateStatic(
    task_func, "task", 4096, NULL, 5,
    task_stack, &task_buffer
);

// Dynamic for variable-size resources
char *buffer = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
if (buffer == NULL) {
    ESP_LOGE(TAG, "Allocation failed");
    return ESP_ERR_NO_MEM;
}
// ... use buffer ...
free(buffer);
文字列の処理
// Bad
char buf[64];
sprintf(buf, "Value: %d", value);

// Good - prevents buffer overflow
char buf[64];
snprintf(buf, sizeof(buf), "Value: %d", value);

// For const strings, keep in flash
static const char *TAG = "mymodule";
ESP_LOGI(TAG, "Starting");

エラー処理

ESP-IDF のエラーパターン
esp_err_t initialize_peripheral(void) {
    esp_err_t ret;

    ret = gpio_config(&io_conf);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "GPIO config failed: %s", esp_err_to_name(ret));
        return ret;
    }

    ret = spi_bus_initialize(SPI2_HOST, &bus_cfg, DMA_CHAN);
    if (ret != ESP_OK) {
        ESP_LOGE(TAG, "SPI init failed: %s", esp_err_to_name(ret));
        // Clean up GPIO if needed
        return ret;
    }

    return ESP_OK;
}

// Use ESP_ERROR_CHECK for fatal errors only
ESP_ERROR_CHECK(nvs_flash_init());
グレースフルデグラデーション
// Don't crash on non-fatal errors
if (wifi_connect() != ESP_OK) {
    ESP_LOGW(TAG, "WiFi failed, running in offline mode");
    run_offline_mode();
}

周辺機器の初期化

GPIO の設定
gpio_config_t io_conf = {
    .pin_bit_mask = (1ULL << GPIO_NUM_2),
    .mode = GPIO_MODE_OUTPUT,
    .pull_up_en = GPIO_PULLUP_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_DISABLE,
    .intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf));
I2C のセットアップ
i2c_config_t conf = {
    .mode = I2C_MODE_MASTER,
    .sda_io_num = GPIO_NUM_21,
    .scl_io_num = GPIO_NUM_22,
    .sda_pullup_en = GPIO_PULLUP_ENABLE,
    .scl_pullup_en = GPIO_PULLUP_ENABLE,
    .master.clk_speed = 400000,
};
ESP_ERROR_CHECK(i2c_param_config(I2C_NUM_0, &conf));
ESP_ERROR_CHECK(i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0));

割り込みハンドラー

ISR を最小限に保つ
// ISR - keep it SHORT
static void IRAM_ATTR gpio_isr_handler(void *arg) {
    uint32_t gpio_num = (uint32_t)arg;
    // Just signal, don't process
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}

// Process in task
void gpio_task(void *arg) {
    uint32_t io_num;
    while (1) {
        if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
            // Heavy processing here, not in ISR
            process_gpio_event(io_num);
        }
    }
}
IRAM に関する考慮事項
  • ISR ハンドラーに IRAM_ATTR を指定します
  • ISR から呼び出される関数にも IRAM_ATTR が必要です
  • IRAM の使用を最小限に抑えます(上限は約128KBです)

WiFi のベストプラクティス

接続の処理
// Use event loop for WiFi events
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data) {
    if (event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) {
        ESP_LOGI(TAG, "Disconnected, retrying...");
        esp_wifi_connect();
    }
}

// Register handler
ESP_ERROR_CHECK(esp_event_handler_instance_register(
    WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
PSRAM と WiFi
  • WiFi は大量のメモリを使用します
  • メモリを大量に消費するアプリケーションでは PSRAM を有効にします
  • CONFIG_SPIRAM_USE_MALLOC を使ってヒープを拡張します

ロギング

ログレベル
ESP_LOGE(TAG, "Error: critical failure");      // Always shown
ESP_LOGW(TAG, "Warning: unusual condition");   // Important
ESP_LOGI(TAG, "Info: normal operation");       // Default
ESP_LOGD(TAG, "Debug: detailed info");         // Development
ESP_LOGV(TAG, "Verbose: very detailed");       // Tracing
本番環境でのロギング
  • menuconfig でログレベルを設定します
  • 本番環境ではログを減らします(最小で ESP_LOGW)
  • ログ文字列はフラッシュ領域を消費します

電源管理

ライトスリープ
// Enable automatic light sleep
esp_pm_config_esp32_t pm_config = {
    .max_freq_mhz = 240,
    .min_freq_mhz = 80,
    .light_sleep_enable = true,
};
ESP_ERROR_CHECK(esp_pm_configure(&pm_config));
ディープスリープ
// Configure wakeup source
esp_sleep_enable_timer_wakeup(60 * 1000000);  // 60 seconds

// Enter deep sleep
esp_deep_sleep_start();

追加リソース

各トピックの詳細については、以下を参照してください。

  • ESP-IDF プログラミングガイド
  • FreeRTOS のドキュメント
  • ESP32 テクニカルリファレンスマニュアル

コメント