embedded-best-practices

Embedded Best Practices Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "embedded-best-practices" with this command: npx skills add laurigates/mcu-tinkering-lab/laurigates-mcu-tinkering-lab-embedded-best-practices

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

esp32-debugging

No summary provided by upstream source.

Repository SourceNeeds Review
General

esp-idf-setup

No summary provided by upstream source.

Repository SourceNeeds Review
General

safe-build-operations

No summary provided by upstream source.

Repository SourceNeeds Review
General

dual-controller-sync

No summary provided by upstream source.

Repository SourceNeeds Review