Original skill document
Rendered from the source repository. Headings, examples, code, tables, links, and referenced images are preserved.
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.mdComponent Organization
- One responsibility per component
- Clear public interface in include/
- Private implementation in src/
- Document dependencies
FreeRTOS Best Practices
Task Design
c
// 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
c
// 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
c
// 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
c
// 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
c
// 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
c
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
c
// 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
c
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
c
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
c
// 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
c
// 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_MALLOCto extend heap
Logging
Log Levels
c
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"); // TracingProduction Logging
- Set log level via menuconfig
- Reduce logging in production (ESP_LOGW minimum)
- Log strings consume flash space
Power Management
Light Sleep
c
// 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
c
// 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
