C Refresher
Structs & Enums
Structs group related data together — you'll see them everywhere in ESP-IDF for configuration, events, and data passing.
Structs (Structures)
A struct allows you to combine multiple variables (of different types) under a single name. Think of it like a simplified Class in Python or C++, but it only holds data, not functions.
1#include <stdio.h>2#include <stdbool.h>34// Define a struct type using typedef5typedef struct {6 int gpio_num;7 int brightness;8 bool is_on;9} led_config_t;1011int main() {12 // Initialize the struct using designated initializers13 led_config_t led = {14 .gpio_num = 2,15 .brightness = 128,16 .is_on = true17 };1819 // Access members using the dot (.) operator20 printf("GPIO: %d, Brightness: %d\n", led.gpio_num, led.brightness);2122 return 0;23}Passing Structs to Functions
You rarely pass an entire struct into a function by value (which makes a full copy). You almost always pass a pointer to the struct, and use the -> operator to access its members.
1void configure_led(const led_config_t *config) {2 // gpio_set_direction(config->gpio_num, GPIO_MODE_OUTPUT);3 if (config->is_on) {4 // gpio_set_level(config->gpio_num, 1);5 }6}Nested Structs
Structs can contain other structs. This is incredibly common in ESP-IDF for organizing complex telemetry or configuration data.
1#include <stdio.h>2#include <stdint.h>34typedef struct {5 float temperature;6 float humidity;7} dht_reading_t;89typedef struct {10 dht_reading_t sensor;11 char device_id[16];12 uint32_t timestamp;13} telemetry_t;1415int main() {16 telemetry_t data = {17 .sensor = { .temperature = 25.5, .humidity = 60.0 },18 .device_id = "XIAO-S3-01",19 .timestamp = 171342720020 };2122 printf("Temp: %.1f\n", data.sensor.temperature);23 return 0;24}Enums (Enumerations)
An enum is a way to create a set of named integer constants. It makes your code highly readable because you can use descriptive words instead of arbitrary numbers.
In ESP-IDF, almost all error codes, hardware modes, and states are defined as enums.
1#include <stdio.h>23typedef enum {4 ESP_OK = 0, // Success5 ESP_FAIL = -1, // Generic failure6 ESP_ERR_NO_MEM = 0x101, // Out of memory7 ESP_ERR_TIMEOUT = 0x102, // Operation timed out8} esp_err_t;910int main() {11 esp_err_t result = ESP_FAIL; // Simulating a failure12 13 if (result != ESP_OK) {14 printf("Operation failed with code: 0x%x\n", result);15 }16 17 return 0;18}#define vs const vs enum
You will see all three patterns used for constants in ESP-IDF. Here is when to use each:
1. #define — Preprocessor Macro
1#define LED_GPIO 22#define MAX_RETRIES 33#define TIMEOUT_MS 5000- How it works: Text replacement before compilation — the compiler never sees
LED_GPIO, only2. - Pros: Works everywhere (array sizes, switch cases,
#ifconditions). It is the ESP-IDF convention for allmenuconfigvalues. - Cons: No type checking, does not show up as a named symbol in a debugger.
2. const — Typed Constant
1const uint8_t led_gpio = 2;2const int max_retries = 3;- How it works: A real variable that the compiler type-checks, but guarantees cannot be modified.
- Pros: Type-safe, shows up in the debugger, compiler catches misuse.
- Cons: Cannot be used in
switch/caselabels or array sizes in standard C.
3. enum — Named Integer Constants
1typedef enum {2 LED_GPIO = 2,3 MAX_RETRIES = 3,4 TIMEOUT_MS = 5000,5} app_config_t;- How it works: Integer constants grouped together.
- Pros: Type-safe (with
typedef), debugger shows names, works perfectly inswitch/case. - Cons: Only supports integers (no floats or strings).
When in doubt, follow ESP-IDF's lead
Use #define for numeric constants and enum for related integer groups. ESP-IDF uses static const char *TAG = "MAIN"; for logging strings.

