| name | embedded-best-practices |
| description | Embedded systems development best practices for ESP32, FreeRTOS, and ESP-IDF. Use when writing firmware code, reviewing implementations, or learning about embedded patterns. |
| user-invocable | false |
| allowed-tools | Read, Grep, Glob |
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
void sensor_task(void *pvParameters) {
sensor_config_t *config = (sensor_config_t *)pvParameters;
while (1) {
read_sensor(config);
vTaskDelay(pdMS_TO_TICKS(100));
}
vTaskDelete(NULL);
}
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
SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
if (xSemaphoreTake(mutex, pdMS_TO_TICKS(1000)) == pdTRUE) {
xSemaphoreGive(mutex);
} else {
ESP_LOGE(TAG, "Failed to acquire mutex");
}
Queue Usage
QueueHandle_t data_queue = xQueueCreate(10, sizeof(sensor_data_t));
sensor_data_t data = {.value = 42};
if (xQueueSend(data_queue, &data, pdMS_TO_TICKS(100)) != pdTRUE) {
ESP_LOGW(TAG, "Queue full, dropping data");
}
sensor_data_t received;
if (xQueueReceive(data_queue, &received, portMAX_DELAY) == pdTRUE) {
process_data(&received);
}
Memory Management
Static vs Dynamic Allocation
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
);
char *buffer = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
if (buffer == NULL) {
ESP_LOGE(TAG, "Allocation failed");
return ESP_ERR_NO_MEM;
}
free(buffer);
String Handling
char buf[64];
sprintf(buf, "Value: %d", value);
char buf[64];
snprintf(buf, sizeof(buf), "Value: %d", value);
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));
return ret;
}
return ESP_OK;
}
ESP_ERROR_CHECK(nvs_flash_init());
Graceful Degradation
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
static void IRAM_ATTR gpio_isr_handler(void *arg) {
uint32_t gpio_num = (uint32_t)arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
void gpio_task(void *arg) {
uint32_t io_num;
while (1) {
if (xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
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
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();
}
}
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");
ESP_LOGW(TAG, "Warning: unusual condition");
ESP_LOGI(TAG, "Info: normal operation");
ESP_LOGD(TAG, "Debug: detailed info");
ESP_LOGV(TAG, "Verbose: very detailed");
Production Logging
- Set log level via menuconfig
- Reduce logging in production (ESP_LOGW minimum)
- Log strings consume flash space
Power Management
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
esp_sleep_enable_timer_wakeup(60 * 1000000);
esp_deep_sleep_start();
Additional Resources
For more detailed information on specific topics, consult:
- ESP-IDF Programming Guide
- FreeRTOS documentation
- ESP32 Technical Reference Manual