mcpField
E
embedded-best-practices

embedded-best-practices

github.com/laurigatesDevelopment7103 installsUpdated today
Repository

About embedded-best-practices

Embedded Best Practices Skill provides implementation and review guidance for embedded systems, with a focus on ESP32 and ESP-IDF. It is intended for firmware developers working on project structure, FreeRTOS tasks, peripherals, memory, error handling, and power management.

Key features

  1. 01ESP-IDF project and component organization
  2. 02FreeRTOS task and synchronization guidance
  3. 03Memory allocation and safe string handling
  4. 04Error handling and graceful degradation
  5. 05Peripheral and interrupt practices
  6. 06WiFi, logging, and power management

Use cases

  1. 01Structuring an ESP-IDF firmware project
  2. 02Reviewing FreeRTOS task and queue design
  3. 03Handling peripheral initialization and interrupts
  4. 04Reducing memory and power risks

Install

Copy the command and run it in a terminal.

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

After installing, type/embedded-best-practicesin Claude Code to confirm it loaded.

Details

From the project README

Embedded Best Practices Skill

This skill provides comprehensive guidance for embedded systems development with a focus on ESP32 and ESP-IDF.

When to Use

  • Writing new firmware code
  • Reviewing implementation approaches
  • Learning embedded patterns
  • Debugging issues
  • Optimizing code

ESP-IDF Project Structure

Recommended Layout
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
Component Organization
  • One responsibility per component
  • Clear public interface in include/
  • Private implementation in src/
  • Document dependencies

FreeRTOS Best Practices

Task Design
// 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);
Stack Sizing
  • Start with 4096 bytes for typical tasks
  • Use uxTaskGetStackHighWaterMark() to measure actual usage
  • Add 25% safety margin
  • Camera/network tasks may need 8192+
Synchronization
// 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");
}
Queue Usage
// 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);
}

Memory Management

Static vs Dynamic Allocation
// 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);
String Handling
// 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");

Error Handling

ESP-IDF Error Pattern
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());
Graceful Degradation
// Don't crash on non-fatal errors
if (wifi_connect() != ESP_OK) {
    ESP_LOGW(TAG, "WiFi failed, running in offline mode");
    run_offline_mode();
}

Peripheral Initialization

GPIO Configuration
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 Setup
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));

Interrupt Handlers

Keep ISRs Minimal
// 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 Considerations
  • Mark ISR handlers with IRAM_ATTR
  • Functions called from ISR also need IRAM_ATTR
  • Minimize IRAM usage (limited to ~128KB)

WiFi Best Practices

Connection Handling
// 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 and WiFi
  • WiFi uses significant memory
  • Enable PSRAM for memory-intensive applications
  • Use CONFIG_SPIRAM_USE_MALLOC to extend heap

Logging

Log Levels
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
Production Logging
  • Set log level via menuconfig
  • Reduce logging in production (ESP_LOGW minimum)
  • Log strings consume flash space

Power Management

Light Sleep
// 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));
Deep Sleep
// Configure wakeup source
esp_sleep_enable_timer_wakeup(60 * 1000000);  // 60 seconds

// Enter deep sleep
esp_deep_sleep_start();

Additional Resources

For more detailed information on specific topics, consult:

  • ESP-IDF Programming Guide
  • FreeRTOS documentation
  • ESP32 Technical Reference Manual

Comments

Markdown supported