C Refresher
ESP-IDF Patterns
This section highlights the practical differences between writing C for a desktop application and writing C for an embedded system like the ESP-IDF.
Normal C vs Embedded C — Key Differences
| Aspect | Normal C (Desktop) | Embedded C (ESP-IDF) |
|---|---|---|
| Entry point | main() — runs once | app_main() — runs forever |
| Exit | return 0 / exit() | Never exits — esp_restart() to reboot |
| Memory | Abundant (GBs), OS-managed | Limited (~320KB internal + 8MB PSRAM) |
| Types | int, float, double | int32_t, uint8_t, float only |
| I/O | printf, scanf, fprintf | GPIO, I2C, SPI, UART drivers |
| Error handling | exit(1) — OS cleans up | Must handle and continue or reboot |
| Timing | sleep(), usleep() — imprecise | vTaskDelay() — precise, cooperative |
| Concurrency | pthread — OS scheduled | FreeRTOS tasks — priority scheduled |
| Debugging | GDB, Valgrind, ASAN | Serial output, ESP_LOGx, JTAG (rare) |
| Compilation | gcc native | xtensa-esp32s3-elf-gcc cross-compiler |
Program Structure
c
1// ── Normal C (desktop) ──2// Program runs once and exits3int main(int argc, char *argv[]) {4 printf("Hello!\n");5 process_data();6 return 0; // OS cleans up everything7}89// ── Embedded C (ESP-IDF) ──10// Program runs forever — there is no OS to return to11void app_main(void) {12 init_hardware();1314 while (1) { // super loop — or use FreeRTOS tasks15 read_sensors();16 process_data();17 actuate_outputs();18 vTaskDelay(pdMS_TO_TICKS(100)); // yield CPU — don't busy-wait!19 }20 // never reaches here21}Error Handling
c
1// ── Normal C (desktop) ──2// Print error and exit — OS handles cleanup3FILE *f = fopen("data.txt", "r");4if (f == NULL) {5 perror("Failed to open file");6 exit(1); // just quit7}89// ── Embedded C (ESP-IDF) ──10// Must handle error and continue — there is no OS to exit to11esp_err_t ret = i2c_master_init();12if (ret != ESP_OK) {13 ESP_LOGE("MAIN", "I2C init failed: %s", esp_err_to_name(ret));14 // Options: retry, use fallback, or restart the device15 esp_restart(); // last resort — reboot the chip16}Preprocessor Macros in ESP-IDF
Macros are heavily used in ESP-IDF for configuration, logging, and conditional compilation.
c
1// Object-like macro — constant replacement2#define LED_GPIO 234// Conditional compilation (controlled by menuconfig)5#define CONFIG_ENABLE_WIFI 167#if CONFIG_ENABLE_WIFI8 void wifi_init(void) { /* ... */ }9#endif1011// ESP-IDF logging macros (you'll use these constantly)12#include "esp_log.h"13#define TAG "MAIN"1415ESP_LOGI(TAG, "Temperature: %.1f", 24.5); // Info (Green)16ESP_LOGW(TAG, "Low battery: %d%%", 15); // Warning (Yellow)17ESP_LOGE(TAG, "Sensor read failed!"); // Error (Red)Core Patterns You Will See
1. The Error Checking Pattern
Almost every ESP-IDF API returns an esp_err_t. You must check it!
c
1esp_err_t ret = nvs_flash_init();23if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {4 ESP_ERROR_CHECK(nvs_flash_erase());5 ret = nvs_flash_init();6}78// ESP_ERROR_CHECK will immediately crash and reboot the board if ret != ESP_OK9ESP_ERROR_CHECK(ret);2. The Configuration Struct Pattern
Instead of passing 10 arguments into a function, ESP-IDF uses "init config" structs extensively.
c
1// 1. Create and fill the configuration struct2gpio_config_t io_conf = {3 .pin_bit_mask = (1ULL << 2),4 .mode = GPIO_MODE_OUTPUT,5 .pull_up_en = GPIO_PULLUP_DISABLE,6 .pull_down_en = GPIO_PULLDOWN_DISABLE,7 .intr_type = GPIO_INTR_DISABLE,8};910// 2. Pass a pointer to the struct to apply the configuration11gpio_config(&io_conf);3. The Handle Pattern
Many ESP-IDF peripherals use opaque handles to keep track of state behind the scenes.
c
1TaskHandle_t my_task;23xTaskCreate(4 my_task_function,5 "Task Name",6 4096,7 NULL,8 1,9 &my_task // Pass the address so FreeRTOS can give you the handle10);1112// Later, you can use that handle to suspend or delete the task13vTaskDelete(my_task);If you understand pointers, structs, and these three patterns, you are fully prepared to tackle the ESP-IDF API!

