CHEATSHEET

Firmware Reference Cards

ESP32 GPIO limits, FreeRTOS API signatures, MQTT QoS, Modbus function codes, and more. The quick-reference you open in a second tab while writing firmware.

ESP32 GPIO Quick Reference

Input-only pins

GPIO 34, 35, 36, 39 — input only, no internal pull-up/down, no output

Strapping pins

Affect boot mode. Avoid driving at startup:

  • GPIO 0 — BOOT button (pull low = download mode)
  • GPIO 2 — must be low or floating at boot
  • GPIO 12 — flash voltage select (keep low for 3.3 V flash)
  • GPIO 15 — silences boot log when pulled low

ADC caveats

  • ADC2 is shared with Wi-Fi — unusable while Wi-Fi is active
  • ADC1: GPIO 32–39
  • ADC2: GPIO 0, 2, 4, 12–15, 25–27
  • Non-linear above ~3.1 V — use voltage divider for 3.3 V rail sensing

Current limits

  • Single GPIO: 40 mA max (12 mA recommended)
  • All GPIOs combined: 1200 mA total
  • 3.3 V LDO output: typically 500 mA — check your module

Touch pins

Capacitive touch: T0–T9 mapped to GPIO 4, 0, 2, 15, 13, 12, 14, 27, 33, 32

Read with touchRead(pin) — lower value = touched

PWM (LEDC)

  • 16 independent channels (0–15)
  • Any output-capable GPIO
  • ledcSetup(ch, freq, bits)
  • ledcAttachPin(pin, ch)
  • ledcWrite(ch, duty)

FreeRTOS API Signatures

Tasks

// Create
xTaskCreate(
  taskFunction,   // void myTask(void *pvParameters)
  "TaskName",     // debug name
  stackDepth,     // words (not bytes) — 2048 is a safe start
  pvParameters,   // passed to taskFunction
  priority,       // 0 (lowest) … configMAX_PRIORITIES-1
  &taskHandle     // or NULL
);

// Pinned to core (ESP32-specific)
xTaskCreatePinnedToCore(fn, name, stack, params, prio, &handle, coreID);  // coreID: 0 or 1

// Delete self
vTaskDelete(NULL);

// Delay
vTaskDelay(pdMS_TO_TICKS(100));  // 100 ms

Queues

QueueHandle_t q = xQueueCreate(length, sizeof(MyType));

// Send (from task or ISR)
xQueueSend(q, &item, pdMS_TO_TICKS(10));          // wait up to 10 ms
xQueueSendFromISR(q, &item, &higherPriorityWoken);

// Receive (blocks until item arrives or timeout)
MyType item;
xQueueReceive(q, &item, portMAX_DELAY);

Semaphores & Mutexes

// Binary semaphore
SemaphoreHandle_t sem = xSemaphoreCreateBinary();
xSemaphoreGive(sem);
xSemaphoreTake(sem, portMAX_DELAY);

// Counting semaphore
SemaphoreHandle_t cnt = xSemaphoreCreateCounting(maxCount, initialCount);

// Mutex (use for shared resources, never take from ISR)
SemaphoreHandle_t mtx = xSemaphoreCreateMutex();
xSemaphoreTake(mtx, portMAX_DELAY);
// ... critical section ...
xSemaphoreGive(mtx);

Priority guide

  • 0 — idle tasks only
  • 1 — background / logging
  • 2–3 — normal application tasks
  • 4–5 — time-sensitive (sensor reads, comms)
  • configMAX_PRIORITIES-1 — real-time / ISR-like

Stack sizing

  • Unit is words, not bytes (×4 on ESP32)
  • Start with 2048 words for simple tasks
  • Add 4096+ for tasks using printf/JSON/TLS
  • Check with uxTaskGetStackHighWaterMark(NULL)

MQTT Reference

QoS Comparison

QoSGuaranteeDelivery countUse when
0 Fire and forget 0 or 1 Sensor telemetry you can afford to lose
1 At least once 1 or more (duplicates possible) Commands, state changes — subscriber must be idempotent
2 Exactly once Exactly 1 Billing events, financial triggers — high overhead

Topic conventions

  • home/bedroom/temp — hierarchy with /
  • + — single-level wildcard: home/+/temp
  • # — multi-level wildcard: home/#
  • No leading / — it creates an empty first segment

Retained messages

Broker stores the last retained message per topic. New subscribers receive it immediately — useful for device state, config, or last-known sensor value.

Clear by publishing an empty payload with retain flag.

Last Will & Testament

Set at connect time. Broker publishes the LWT message if the client disconnects unexpectedly (no clean disconnect). Use to mark a device offline.

client.setWill(
  "devices/my-esp32/status",
  "offline",
  true,   // retain
  1       // QoS
);

Packet size limits

  • Max payload: 268 MB (protocol) — broker/client set lower
  • ESP32 practical limit: 4–16 KB depending on library
  • PubSubClient default: 256 bytes — increase with MQTT_MAX_PACKET_SIZE

Modbus Function Codes

Standard function codes

Code (hex)NameData typeAccess
0x01Read CoilsSingle-bit (coil)Read
0x02Read Discrete InputsSingle-bit (input)Read
0x03Read Holding Registers16-bit wordRead
0x04Read Input Registers16-bit wordRead
0x05Write Single CoilSingle-bitWrite
0x06Write Single Register16-bit wordWrite
0x0FWrite Multiple CoilsSingle-bitWrite
0x10Write Multiple Registers16-bit wordWrite
0x16Mask Write Register16-bit wordWrite
0x17Read/Write Multiple Regs16-bit wordRead+Write

Exception codes

  • 0x01 — Illegal function
  • 0x02 — Illegal data address
  • 0x03 — Illegal data value
  • 0x04 — Server device failure
  • 0x05 — Acknowledge (long operation)
  • 0x06 — Server device busy

Exception response: function code ORed with 0x80 (e.g. 0x83 = error on FC 0x03)

Address space

  • Coils (RW bits): 00001–09999
  • Discrete inputs (RO bits): 10001–19999
  • Input registers (RO 16-bit): 30001–39999
  • Holding registers (RW 16-bit): 40001–49999

PDU address = register number − 1 (40001 → address 0x0000)

RTU frame format

[Device addr 1B][Function 1B][Data nB][CRC-16 2B]

Silent interval:
  3.5 character times between frames
  At 9600 baud ≈ 4 ms

Arduino / ESP-IDF Timing Patterns

Non-blocking timer (millis)

unsigned long lastRun = 0;
const long INTERVAL = 1000;  // ms

void loop() {
  unsigned long now = millis();
  if (now - lastRun >= INTERVAL) {
    lastRun = now;
    // do work
  }
}

Always use subtraction (now - last), never addition (millis() >= last + N) — subtraction is rollover-safe at 49 days.

Time functions

  • millis() — ms since boot, rolls over at ~49 days
  • micros() — µs since boot, rolls over at ~71 min
  • delay(ms) — blocking, freezes loop
  • delayMicroseconds(µs) — blocking, use for <16 ms only

esp_timer (esp-idf)

esp_timer_handle_t timer;
esp_timer_create_args_t args = {
  .callback = myCallback,
  .name     = "my_timer"
};
esp_timer_create(&args, &timer);
esp_timer_start_periodic(timer, 1000000); // 1 s in µs

Watchdog

  • Task WDT fires if a task hogs CPU for CONFIG_ESP_TASK_WDT_TIMEOUT_S (default 5 s)
  • Feed manually: esp_task_wdt_reset()
  • Prevent by calling vTaskDelay(1) in long loops

Common Unit Conversions

Baud → bit time

BaudBit timeByte time (10 bits)
9600104 µs1.04 ms
1920052 µs521 µs
1152008.68 µs86.8 µs
9216001.09 µs10.9 µs

Memory prefixes

  • 1 KB = 1024 bytes
  • 1 MB = 1 048 576 bytes
  • ESP32 SRAM: 520 KB (internal)
  • ESP32 flash: typically 4 MB (module-dependent)

Voltage divider

Vout = Vin × R2 / (R1 + R2)

// Scale 5 V → 3.3 V:
R1 = 10 kΩ, R2 = 20 kΩ
→ 5 × 20/(10+20) = 3.33 V ✓

Ohm's law

V = I × R
I = V / R
R = V / I

// LED current limiting (3.3 V supply, 2 V LED, 20 mA):
R = (3.3 - 2.0) / 0.020 = 65 Ω → use 68 Ω