
Freertos Patterns
- 37 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
freertos-patterns is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- freertos-patterns
- AI & Agent Building
- AI-coding skill
Freertos Patterns by the numbers
- 37 all-time installs (skills.sh)
- +4 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #8,534 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wedsamuel1230/arduino-skills --skill freertos-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 37 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
FreeRTOS Patterns for ESP32 and RP2040
Use this skill for real concurrency problems on ESP32 or RP2040. Keep the main flow focused and load the detailed reference that matches the user's exact problem.
Resources
references/patterns-task-creation.md- task lifecycle, priorities, stack
sizing, and task startup
references/patterns-queues.md- inter-task communication with queuesreferences/patterns-synchronization.md- mutexes, semaphores, critical
sections, and ISR coordination
references/patterns-memory.md- stack sizing, heap checks, and monitoringreferences/patterns-advanced.md- watchdogs, notifications, event groups,
timers, and affinity
assets/workflow.mmd- architecture overview
When to Use
Use this skill when the request includes:
- multiple concurrent jobs on ESP32
- task priorities or preemption
- queue, semaphore, mutex, or notification design
- RP2040
setup1andloop1multicore coordination - watchdog-safe long-running behavior
- debugging race conditions, stack overflows, or deadlocks
Do not use this skill when:
- the target is UNO or another single-core board
- a simple
millis()scheduler solves the problem - the request is only about one blocking loop with no concurrency requirement
Workflow
1. Confirm the target platform first:
- ESP32 -> FreeRTOS task model
- RP2040 -> dual-core coordination model
2. Identify the dominant problem:
- task startup -> open
patterns-task-creation.md - communication -> open
patterns-queues.md - shared data or ISR signaling -> open
patterns-synchronization.md - stack or heap risk -> open
patterns-memory.md - watchdogs, notifications, timers, or affinity -> open
patterns-advanced.md 3. Solve one synchronization boundary at a time. Avoid mixing queue, mutex, and notification advice unless the design truly needs all of them. 4. Keep board-specific details explicit. ESP32 FreeRTOS guidance and RP2040 multicore guidance are not interchangeable.
Core Rules
- Prefer the smallest concurrency primitive that solves the problem.
- Protect shared state explicitly. "Probably safe" is not safe.
- Use
vTaskDelay()or event-driven blocking on ESP32, neverdelay()inside
a task.
- Budget stack size deliberately and verify it with runtime evidence.
- For RP2040, define core ownership clearly before sharing data.
Verification
- Confirm each task or core has a single clear responsibility.
- Check queue depth, notification flow, or mutex coverage against the concrete
data path.
- For ESP32, inspect stack headroom with
uxTaskGetStackHighWaterMark() where the task size is uncertain.
- Verify the sketch can keep making progress without deadlock or busy waiting.
- If an ISR is involved, verify the synchronization primitive is ISR-safe for
that platform.
Common Failure Modes
- undersized task stack
delay()used inside a FreeRTOS task- unprotected shared data
- deadlock from inconsistent lock order
- RP2040 core startup assumptions without mutex or ownership discipline
Integration
- Combine with
arduino-code-generatorwhen the user needs a full sketch rather
than design guidance.
- Combine with
circuit-debuggeronly after the concurrency design looks sound
and a hardware timing issue still remains.
- Combine with
mermaid-diagram-generatorwhen the concurrency design needs a
task, queue, or event-flow diagram.
{
"name": "freertos-patterns",
"metadata": {
"description": "Comprehensive FreeRTOS task management, synchronization, and memory optimization patterns for ESP32 multitasking",
"version": "0.10.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": [
"freertos",
"esp32",
"rtos",
"multitasking",
"task-synchronization",
"queues",
"semaphores",
"embedded-systems",
"real-time"
],
"category": "embedded-systems"
},
"plugins": [
{
"name": "freertos-patterns",
"description": "FreeRTOS patterns for ESP32 task management and synchronization",
"enabled": true
}
]
}
```mermaid
%%{init: {
"theme": "neutral",
"themeVariables": {
"primaryColor": "#4A90E2",
"primaryTextColor": "#fff",
"primaryBorderColor": "#2E5C8A",
"lineColor": "#4A77B5",
"secondaryColor": "#82B366",
"tertiaryColor": "#D79B00"
}
}}%%
flowchart TD
A["🚀 User Request: ESP32 Multitasking"] --> B{"Pattern Type?"}
B -->|Task Creation| C1["patterns-task-creation.md"]
B -->|Communication| C2["patterns-queues.md"]
B -->|Synchronization| C3["patterns-synchronization.md"]
B -->|Memory| C4["patterns-memory.md"]
B -->|Advanced| C5["patterns-advanced.md"]
C1 --> D1["xTaskCreate<br/>Priority & Stack"]
C2 --> D2["Queues<br/>Producer-Consumer"]
C3 --> D3["Mutex/Semaphore<br/>Shared Resources"]
C4 --> D4["Heap/Stack Monitor<br/>Memory Pools"]
C5 --> D5["Task Notifications<br/>Event Groups"]
D1 & D2 & D3 & D4 & D5 --> E["Pattern Assembly"]
E --> F{"Verification?"}
F -->|Stack Check| G1["uxTaskGetStackHighWaterMark"]
F -->|Heap Check| G2["ESP.getFreeHeap"]
F -->|Task List| G3["vTaskList"]
F -->|Runtime Stats| G4["vTaskGetRunTimeStats"]
G1 & G2 & G3 & G4 --> H["✅ Working ESP32 Code"]
style A fill:#4A90E2,stroke:#2E5C8A,color:#fff
style H fill:#82B366,stroke:#5A8C4A,color:#fff
style E fill:#D79B00,stroke:#9C7200,color:#fff
style C1 fill:#E8F4F8,stroke:#4A90E2
style C2 fill:#E8F4F8,stroke:#4A90E2
style C3 fill:#E8F4F8,stroke:#4A90E2
style C4 fill:#E8F4F8,stroke:#4A90E2
style C5 fill:#E8F4F8,stroke:#4A90E2
```
Advanced FreeRTOS Patterns
Task Notifications (Lightweight Alternative to Semaphores)
TaskHandle_t workerTaskHandle;
void taskWorker(void* parameter) {
uint32_t notificationValue;
while(true) {
// Wait for notification (clears counter on read)
notificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
Serial.printf("Received %lu notifications\n", notificationValue);
// Process work
for (uint32_t i = 0; i < notificationValue; i++) {
Serial.printf("Processing task %lu...\n", i + 1);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
}
void taskController(void* parameter) {
uint32_t workCount = 1;
while(true) {
vTaskDelay(pdMS_TO_TICKS(2000));
Serial.printf("Sending %lu work items\n", workCount);
// Notify worker (increment notification count)
xTaskNotifyGive(workerTaskHandle);
workCount = (workCount % 5) + 1;
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskWorker, "Worker", 2048, NULL, 2, &workerTaskHandle);
xTaskCreate(taskController, "Controller", 2048, NULL, 1, NULL);
}Event Groups (Complex Synchronization)
EventGroupHandle_t systemEvents;
// Event bits
#define BIT_WIFI_CONNECTED (1 << 0)
#define BIT_SENSOR_READY (1 << 1)
#define BIT_SD_MOUNTED (1 << 2)
#define BIT_ALL_READY (BIT_WIFI_CONNECTED | BIT_SENSOR_READY | BIT_SD_MOUNTED)
void taskInitWiFi(void* parameter) {
Serial.println("Initializing WiFi...");
vTaskDelay(pdMS_TO_TICKS(2000));
Serial.println("WiFi connected");
xEventGroupSetBits(systemEvents, BIT_WIFI_CONNECTED);
vTaskDelete(NULL);
}
void taskInitSensor(void* parameter) {
Serial.println("Initializing sensor...");
vTaskDelay(pdMS_TO_TICKS(1500));
Serial.println("Sensor ready");
xEventGroupSetBits(systemEvents, BIT_SENSOR_READY);
vTaskDelete(NULL);
}
void taskInitSD(void* parameter) {
Serial.println("Mounting SD card...");
vTaskDelay(pdMS_TO_TICKS(1000));
Serial.println("SD mounted");
xEventGroupSetBits(systemEvents, BIT_SD_MOUNTED);
vTaskDelete(NULL);
}
void taskStartup(void* parameter) {
Serial.println("Waiting for all subsystems...");
// Wait for all bits (AND logic)
EventBits_t bits = xEventGroupWaitBits(
systemEvents,
BIT_ALL_READY,
pdFALSE, // Don't clear bits
pdTRUE, // Wait for ALL bits
portMAX_DELAY
);
if ((bits & BIT_ALL_READY) == BIT_ALL_READY) {
Serial.println("All subsystems ready!");
Serial.println("Starting main application...");
}
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
systemEvents = xEventGroupCreate();
xTaskCreate(taskInitWiFi, "InitWiFi", 4096, NULL, 1, NULL);
xTaskCreate(taskInitSensor, "InitSensor", 2048, NULL, 1, NULL);
xTaskCreate(taskInitSD, "InitSD", 2048, NULL, 1, NULL);
xTaskCreate(taskStartup, "Startup", 2048, NULL, 2, NULL);
}Software Timers
TimerHandle_t periodicTimer;
TimerHandle_t oneShotTimer;
uint32_t timerCount = 0;
void periodicCallback(TimerHandle_t xTimer) {
timerCount++;
Serial.printf("Periodic timer: %lu\n", timerCount);
// Keep this FAST (<10ms)
}
void oneShotCallback(TimerHandle_t xTimer) {
Serial.println("One-shot timer fired!");
}
void setup() {
Serial.begin(115200);
// Create periodic timer (1 second, auto-reload)
periodicTimer = xTimerCreate(
"Periodic",
pdMS_TO_TICKS(1000),
pdTRUE, // Auto-reload
NULL,
periodicCallback
);
// Create one-shot timer (5 seconds, no auto-reload)
oneShotTimer = xTimerCreate(
"OneShot",
pdMS_TO_TICKS(5000),
pdFALSE, // One-shot
NULL,
oneShotCallback
);
// Start timers
if (periodicTimer != NULL) {
xTimerStart(periodicTimer, 0);
}
if (oneShotTimer != NULL) {
xTimerStart(oneShotTimer, 0);
}
}
void loop() {
vTaskDelay(portMAX_DELAY);
}Watchdog Integration
#include <esp_task_wdt.h>
#define WDT_TIMEOUT 3 // 3 seconds
void taskCritical(void* parameter) {
// Register with watchdog
esp_task_wdt_add(NULL);
while(true) {
// Critical operations
readSafetySensors();
updateMotorControl();
// Reset watchdog (must happen every 3 seconds)
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void taskNonCritical(void* parameter) {
while(true) {
// Non-critical work (no watchdog)
updateDisplay();
logData();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void setup() {
Serial.begin(115200);
// Initialize watchdog
esp_task_wdt_init(WDT_TIMEOUT, true); // Panic on timeout
xTaskCreate(taskCritical, "Critical", 4096, NULL, 10, NULL);
xTaskCreate(taskNonCritical, "NonCritical", 4096, NULL, 1, NULL);
Serial.println("Watchdog enabled");
}Stream Buffers (High-Throughput Data)
StreamBufferHandle_t uartStream;
void taskUARTReceiver(void* parameter) {
uint8_t buffer[128];
while(true) {
if (Serial.available()) {
int bytesRead = Serial.readBytes(buffer, sizeof(buffer));
// Send to stream buffer
size_t bytesSent = xStreamBufferSend(
uartStream,
buffer,
bytesRead,
pdMS_TO_TICKS(100)
);
if (bytesSent < bytesRead) {
Serial.println("Stream buffer full, data lost!");
}
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void taskDataProcessor(void* parameter) {
uint8_t buffer[128];
while(true) {
// Wait for at least 1 byte
size_t bytesReceived = xStreamBufferReceive(
uartStream,
buffer,
sizeof(buffer),
portMAX_DELAY
);
if (bytesReceived > 0) {
Serial.printf("Processing %d bytes\n", bytesReceived);
// Process data...
}
}
}
void setup() {
Serial.begin(115200);
// Create stream buffer (1024 bytes)
uartStream = xStreamBufferCreate(1024, 1); // Trigger level = 1 byte
xTaskCreate(taskUARTReceiver, "UART_RX", 2048, NULL, 2, NULL);
xTaskCreate(taskDataProcessor, "Processor", 4096, NULL, 1, NULL);
}Task Notifications with Value
TaskHandle_t controlTaskHandle;
enum ControlCommand {
CMD_START = 1,
CMD_STOP = 2,
CMD_RESET = 3,
CMD_STATUS = 4
};
void taskControl(void* parameter) {
uint32_t command;
while(true) {
// Wait for notification with value
if (xTaskNotifyWait(0, 0xFFFFFFFF, &command, portMAX_DELAY) == pdPASS) {
switch(command) {
case CMD_START:
Serial.println("Starting...");
break;
case CMD_STOP:
Serial.println("Stopping...");
break;
case CMD_RESET:
Serial.println("Resetting...");
break;
case CMD_STATUS:
Serial.println("Status: Running");
break;
default:
Serial.println("Unknown command");
break;
}
}
}
}
void taskCommandSender(void* parameter) {
ControlCommand commands[] = {CMD_START, CMD_STATUS, CMD_STOP, CMD_RESET};
int cmdIndex = 0;
while(true) {
vTaskDelay(pdMS_TO_TICKS(2000));
// Send command via task notification
xTaskNotify(controlTaskHandle, commands[cmdIndex], eSetValueWithOverwrite);
cmdIndex = (cmdIndex + 1) % 4;
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskControl, "Control", 2048, NULL, 2, &controlTaskHandle);
xTaskCreate(taskCommandSender, "Sender", 2048, NULL, 1, NULL);
}Direct-to-Task Notifications from ISR
#define BUTTON_PIN 0
TaskHandle_t buttonTaskHandle;
volatile uint32_t isrCount = 0;
void IRAM_ATTR buttonISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
// Notify task from ISR
vTaskNotifyGiveFromISR(buttonTaskHandle, &xHigherPriorityTaskWoken);
isrCount++;
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void taskButtonHandler(void* parameter) {
uint32_t notificationCount;
while(true) {
// Wait for ISR notification
notificationCount = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
Serial.printf("Button events: %lu (ISR count: %lu)\n",
notificationCount, isrCount);
// Debounce
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
xTaskCreate(taskButtonHandler, "Button", 2048, NULL, 3, &buttonTaskHandle);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}Task Suspend/Resume Pattern
TaskHandle_t workerTaskHandle;
void taskWorker(void* parameter) {
while(true) {
Serial.println("Worker: Doing work...");
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void taskSupervisor(void* parameter) {
bool workerSuspended = false;
while(true) {
vTaskDelay(pdMS_TO_TICKS(3000));
if (workerSuspended) {
Serial.println("Supervisor: Resuming worker");
vTaskResume(workerTaskHandle);
} else {
Serial.println("Supervisor: Suspending worker");
vTaskSuspend(workerTaskHandle);
}
workerSuspended = !workerSuspended;
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskWorker, "Worker", 2048, NULL, 1, &workerTaskHandle);
xTaskCreate(taskSupervisor, "Supervisor", 2048, NULL, 2, NULL);
}Dual-Core Task Pinning
void taskCore0(void* parameter) {
while(true) {
// WiFi/BLE operations (protocol stack on Core 0)
Serial.printf("Core 0: %d\n", xPortGetCoreID());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskCore1(void* parameter) {
while(true) {
// Application logic (Core 1)
Serial.printf("Core 1: %d\n", xPortGetCoreID());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
// Pin tasks to specific cores
xTaskCreatePinnedToCore(taskCore0, "Core0", 4096, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(taskCore1, "Core1", 4096, NULL, 1, NULL, 1);
}Task List Debugging
void printTaskList() {
char buffer[512];
vTaskList(buffer);
Serial.println("\n=== Task List ===");
Serial.println("Name State Prio Stack Num");
Serial.println("-------------------------------------");
Serial.println(buffer);
}
void printRunTimeStats() {
char buffer[512];
vTaskGetRunTimeStats(buffer);
Serial.println("\n=== Runtime Stats ===");
Serial.println("Task Time %");
Serial.println("------------------------------------");
Serial.println(buffer);
}
void loop() {
printTaskList();
printRunTimeStats();
vTaskDelay(pdMS_TO_TICKS(10000));
}Idle Task Hook
extern "C" void vApplicationIdleHook() {
// Called during idle task execution
// Keep this VERY FAST (<100µs)
static unsigned long lastCheck = 0;
unsigned long now = millis();
if (now - lastCheck > 10000) {
// Periodic maintenance (every 10 seconds)
lastCheck = now;
}
}Tick Hook (Periodic Callback)
extern "C" void vApplicationTickHook() {
// Called every tick (default: 1ms)
// Keep this EXTREMELY FAST (<10µs)
static uint32_t tickCount = 0;
tickCount++;
// Example: Toggle GPIO every 1000 ticks (1 second)
if (tickCount % 1000 == 0) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}Best Practices
1. Prefer task notifications - Faster than semaphores (45% less overhead) 2. Use event groups for complex sync - Multiple condition wait 3. Stream buffers for data pipes - High-throughput, low-latency 4. Software timers for periodic tasks - If no blocking operations 5. Watchdog for critical tasks - Safety-critical systems 6. Suspend/resume sparingly - Can cause priority inversion 7. Core pinning for performance - Separate protocol stack from app logic
Performance Comparison
| Mechanism | RAM Overhead | Wake Latency | Throughput |
|---|---|---|---|
| Task Notification | 8 bytes | ~3µs | High |
| Binary Semaphore | 80 bytes | ~5µs | Medium |
| Queue | 80 + (N×itemSize) | ~7µs | Medium |
| Stream Buffer | bufferSize + 80 | ~4µs | Very High |
| Event Groups | 24 bytes | ~6µs | Low |
Verification Checklist
- [ ] Task notifications used instead of semaphores where possible
- [ ] Event groups only for complex synchronization
- [ ] Software timers callback duration <10ms
- [ ] Watchdog timeout appropriate (3-10 seconds)
- [ ] Stream buffers sized for peak throughput
- [ ] Idle hook duration <100µs
- [ ] Tick hook duration <10µs (or disabled)
- [ ] Core affinity set correctly (0 for WiFi, 1 for app)
Memory Management Patterns
Heap Monitoring
void printHeapStats() {
uint32_t freeHeap = ESP.getFreeHeap();
uint32_t minFreeHeap = ESP.getMinFreeHeap();
uint32_t heapSize = ESP.getHeapSize();
Serial.println("=== Heap Statistics ===");
Serial.printf("Free: %d bytes (%.1f%%)\n", freeHeap, (freeHeap * 100.0) / heapSize);
Serial.printf("Min Free: %d bytes\n", minFreeHeap);
Serial.printf("Total Size: %d bytes\n", heapSize);
if (freeHeap < 10000) {
Serial.println("WARNING: Low heap!");
}
}
void loop() {
printHeapStats();
vTaskDelay(pdMS_TO_TICKS(5000));
}Stack High Water Mark
TaskHandle_t task1Handle, task2Handle;
void printStackStats() {
UBaseType_t freeStack1 = uxTaskGetStackHighWaterMark(task1Handle);
UBaseType_t freeStack2 = uxTaskGetStackHighWaterMark(task2Handle);
Serial.println("=== Stack Statistics ===");
Serial.printf("Task1 free: %d bytes\n", freeStack1 * sizeof(StackType_t));
Serial.printf("Task2 free: %d bytes\n", freeStack2 * sizeof(StackType_t));
if (freeStack1 < 512 / sizeof(StackType_t)) {
Serial.println("WARNING: Task1 stack low!");
}
if (freeStack2 < 512 / sizeof(StackType_t)) {
Serial.println("WARNING: Task2 stack low!");
}
}
void loop() {
printStackStats();
vTaskDelay(pdMS_TO_TICKS(5000));
}Dynamic Memory Allocation
void taskWithDynamicMemory(void* parameter) {
while(true) {
// Allocate memory
uint8_t* buffer = (uint8_t*)pvPortMalloc(1024);
if (buffer == NULL) {
Serial.println("Memory allocation failed!");
vTaskDelay(pdMS_TO_TICKS(1000));
continue;
}
// Use buffer
memset(buffer, 0xAA, 1024);
Serial.println("Buffer allocated and used");
// Free memory
vPortFree(buffer);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskWithDynamicMemory, "Dynamic", 4096, NULL, 1, NULL);
}⚠️ Prefer static allocation when possible - Less fragmentation, predictable behavior.
Static Allocation (No Heap)
#define TASK_STACK_SIZE 2048
StaticTask_t taskBuffer;
StackType_t taskStack[TASK_STACK_SIZE];
void taskStatic(void* parameter) {
while(true) {
Serial.println("Static task running");
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
// Create task with static buffers (no heap allocation)
TaskHandle_t handle = xTaskCreateStatic(
taskStatic,
"Static",
TASK_STACK_SIZE,
NULL,
1,
taskStack,
&taskBuffer
);
if (handle == NULL) {
Serial.println("Static task creation failed!");
}
}Memory Pool Pattern
#define POOL_SIZE 10
#define BUFFER_SIZE 128
struct Buffer {
uint8_t data[BUFFER_SIZE];
bool inUse;
};
Buffer memoryPool[POOL_SIZE];
SemaphoreHandle_t poolMutex;
Buffer* allocateBuffer() {
Buffer* result = NULL;
if (xSemaphoreTake(poolMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
for (int i = 0; i < POOL_SIZE; i++) {
if (!memoryPool[i].inUse) {
memoryPool[i].inUse = true;
result = &memoryPool[i];
break;
}
}
xSemaphoreGive(poolMutex);
}
return result;
}
void freeBuffer(Buffer* buffer) {
if (xSemaphoreTake(poolMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
buffer->inUse = false;
xSemaphoreGive(poolMutex);
}
}
void taskUser(void* parameter) {
while(true) {
Buffer* buf = allocateBuffer();
if (buf != NULL) {
// Use buffer
memset(buf->data, 0, BUFFER_SIZE);
Serial.println("Buffer allocated");
vTaskDelay(pdMS_TO_TICKS(500));
// Free buffer
freeBuffer(buf);
Serial.println("Buffer freed");
} else {
Serial.println("No buffers available!");
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void setup() {
Serial.begin(115200);
// Initialize pool
for (int i = 0; i < POOL_SIZE; i++) {
memoryPool[i].inUse = false;
}
poolMutex = xSemaphoreCreateMutex();
// Create multiple tasks using pool
xTaskCreate(taskUser, "User1", 2048, NULL, 1, NULL);
xTaskCreate(taskUser, "User2", 2048, NULL, 1, NULL);
xTaskCreate(taskUser, "User3", 2048, NULL, 1, NULL);
}Stack Overflow Detection
Enable in Arduino IDE → Tools → Core Debug Level → Verbose
// Hook called when stack overflow detected
extern "C" void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName) {
Serial.print("STACK OVERFLOW in task: ");
Serial.println(pcTaskName);
// Print task info
UBaseType_t freeStack = uxTaskGetStackHighWaterMark(xTask);
Serial.printf("Free stack: %d bytes\n", freeStack * sizeof(StackType_t));
// Halt system
while(1) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
delay(100);
}
}Heap Usage Analysis
void analyzeHeapUsage() {
uint32_t heapBefore = ESP.getFreeHeap();
// Allocate test buffer
uint8_t* testBuffer = (uint8_t*)malloc(1024);
uint32_t heapAfter = ESP.getFreeHeap();
uint32_t overhead = (heapBefore - heapAfter) - 1024;
Serial.printf("Heap overhead: %d bytes per allocation\n", overhead);
free(testBuffer);
// Check fragmentation
uint32_t heapRestored = ESP.getFreeHeap();
if (heapRestored < heapBefore) {
Serial.printf("Heap fragmentation: %d bytes lost\n", heapBefore - heapRestored);
}
}PSRAM Support (ESP32 with External RAM)
void taskWithPSRAM(void* parameter) {
// Allocate from PSRAM (external RAM)
uint8_t* largeBuf = (uint8_t*)ps_malloc(100 * 1024); // 100KB
if (largeBuf == NULL) {
Serial.println("PSRAM allocation failed!");
vTaskDelete(NULL);
}
Serial.println("PSRAM buffer allocated");
// Use buffer
memset(largeBuf, 0, 100 * 1024);
// Free
free(largeBuf);
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
// Check if PSRAM available
if (psramFound()) {
Serial.printf("PSRAM size: %d bytes\n", ESP.getPsramSize());
Serial.printf("PSRAM free: %d bytes\n", ESP.getFreePsram());
xTaskCreate(taskWithPSRAM, "PSRAM", 2048, NULL, 1, NULL);
} else {
Serial.println("PSRAM not available");
}
}Memory Leak Detection
uint32_t baselineHeap;
void setup() {
Serial.begin(115200);
// Wait for tasks to stabilize
vTaskDelay(pdMS_TO_TICKS(5000));
// Record baseline
baselineHeap = ESP.getFreeHeap();
Serial.printf("Baseline heap: %d bytes\n", baselineHeap);
}
void loop() {
uint32_t currentHeap = ESP.getFreeHeap();
int32_t delta = currentHeap - baselineHeap;
Serial.printf("Heap delta: %d bytes\n", delta);
if (delta < -10000) {
Serial.println("WARNING: Possible memory leak!");
}
vTaskDelay(pdMS_TO_TICKS(10000));
}Fragmentation Test
void testFragmentation() {
const int numAllocs = 100;
void* pointers[numAllocs];
uint32_t heapBefore = ESP.getFreeHeap();
// Allocate many small blocks
for (int i = 0; i < numAllocs; i++) {
pointers[i] = malloc(64);
}
// Free every other block
for (int i = 0; i < numAllocs; i += 2) {
free(pointers[i]);
}
// Try to allocate large block
uint32_t largeSize = 4096;
void* largeBuf = malloc(largeSize);
if (largeBuf == NULL) {
Serial.println("Fragmentation detected: Can't allocate large block");
} else {
Serial.println("No fragmentation");
free(largeBuf);
}
// Cleanup
for (int i = 1; i < numAllocs; i += 2) {
free(pointers[i]);
}
uint32_t heapAfter = ESP.getFreeHeap();
Serial.printf("Heap recovered: %d / %d bytes\n", heapAfter, heapBefore);
}Safe Buffer Sizing
// Calculate required buffer size with safety margin
#define SENSOR_DATA_SIZE 128
#define SAFETY_MARGIN 1.5 // 50% margin
#define BUFFER_SIZE ((int)(SENSOR_DATA_SIZE * SAFETY_MARGIN))
void taskSafeBuffer(void* parameter) {
char buffer[BUFFER_SIZE];
while(true) {
int written = snprintf(buffer, BUFFER_SIZE, "Sensor data: %lu", millis());
if (written >= BUFFER_SIZE) {
Serial.println("WARNING: Buffer truncation!");
} else {
Serial.println(buffer);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}Heap Integrity Check
void checkHeapIntegrity() {
// ESP32-specific heap check
if (!heap_caps_check_integrity_all(true)) {
Serial.println("HEAP CORRUPTION DETECTED!");
// Print heap info
heap_caps_print_heap_info(MALLOC_CAP_DEFAULT);
// Halt
while(1);
} else {
Serial.println("Heap integrity: OK");
}
}
void loop() {
checkHeapIntegrity();
vTaskDelay(pdMS_TO_TICKS(30000));
}Memory Guidelines
Heap Sizing
ESP32 Total SRAM: 520KB
- WiFi stack: ~40KB
- System overhead: ~20KB
- FreeRTOS kernel: ~8KB
- Available: ~450KB
Safe allocation: 80% of available = 360KBStack Sizing
| Task Type | Recommended Stack |
|---|---|
| Simple LED blink | 1024 bytes |
| Serial I/O | 2048 bytes |
| WiFi/Network | 4096 bytes |
| JSON parsing | 8192 bytes |
| Large buffers | 16384 bytes |
Formula
Stack Size = LocalVars + CallChain + Margin
Example:
- char buffer[1024]
- 3 function calls (256 bytes each)
- 50% margin
Total: 1024 + 768 + 896 = 2688 → Round to 3072 (3KB)Best Practices
1. Monitor heap continuously - Check every 5-10 seconds 2. Prefer static allocation - Avoid malloc/new in tasks 3. Use memory pools - For fixed-size allocations 4. Check high water mark - Stack usage monitoring 5. Enable overflow detection - Catch stack issues early 6. Free immediately - Don't hold memory longer than needed 7. Use PSRAM for large buffers - If available (ESP32-WROVER) 8. Avoid fragmentation - Allocate/free in patterns, not randomly
Verification Checklist
- [ ] Heap monitored every 5-10 seconds
- [ ] Stack high water marks checked
- [ ] Stack overflow detection enabled
- [ ] All malloc/new calls checked for NULL
- [ ] All allocated memory freed
- [ ] No memory leaks after 1 hour runtime
- [ ] Heap remains above 20% free
- [ ] Stack remains above 20% free per task
- [ ] Large buffers (>4KB) justified
Queue Communication Patterns
Basic Queue (Producer-Consumer)
QueueHandle_t dataQueue;
struct SensorData {
uint32_t timestamp;
float temperature;
int humidity;
};
void taskProducer(void* parameter) {
SensorData data;
while(true) {
// Generate data
data.timestamp = millis();
data.temperature = random(200, 300) / 10.0;
data.humidity = random(400, 700) / 10;
// Send to queue (wait max 100ms if full)
if (xQueueSend(dataQueue, &data, pdMS_TO_TICKS(100)) == pdPASS) {
Serial.println("Data sent");
} else {
Serial.println("Queue full, data dropped");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskConsumer(void* parameter) {
SensorData data;
while(true) {
// Wait indefinitely for data
if (xQueueReceive(dataQueue, &data, portMAX_DELAY) == pdPASS) {
Serial.printf("[%lu] T=%.1f°C H=%d%%\n",
data.timestamp, data.temperature, data.humidity);
}
}
}
void setup() {
Serial.begin(115200);
// Create queue: 10 items, each sizeof(SensorData)
dataQueue = xQueueCreate(10, sizeof(SensorData));
if (dataQueue == NULL) {
Serial.println("Queue creation failed!");
while(1);
}
xTaskCreate(taskProducer, "Producer", 4096, NULL, 1, NULL);
xTaskCreate(taskConsumer, "Consumer", 4096, NULL, 1, NULL);
}Queue Monitoring
void loop() {
UBaseType_t available = uxQueueSpacesAvailable(dataQueue);
UBaseType_t messages = uxQueueMessagesWaiting(dataQueue);
Serial.printf("Queue: %d/%d used\n", messages, messages + available);
vTaskDelay(pdMS_TO_TICKS(5000));
}Multi-Producer, Single Consumer
QueueHandle_t commandQueue;
enum CommandType {
CMD_LED_ON,
CMD_LED_OFF,
CMD_RESET,
CMD_STATUS
};
struct Command {
CommandType type;
uint32_t value;
char source[16];
};
void taskButtonReader(void* parameter) {
Command cmd;
strcpy(cmd.source, "Button");
while(true) {
if (digitalRead(0) == LOW) { // Boot button
cmd.type = CMD_LED_ON;
cmd.value = millis();
xQueueSend(commandQueue, &cmd, 0);
vTaskDelay(pdMS_TO_TICKS(200)); // Debounce
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void taskSerialReader(void* parameter) {
Command cmd;
strcpy(cmd.source, "Serial");
while(true) {
if (Serial.available()) {
char c = Serial.read();
if (c == '1') {
cmd.type = CMD_LED_ON;
} else if (c == '0') {
cmd.type = CMD_LED_OFF;
}
cmd.value = millis();
xQueueSend(commandQueue, &cmd, 0);
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void taskCommandProcessor(void* parameter) {
Command cmd;
while(true) {
if (xQueueReceive(commandQueue, &cmd, portMAX_DELAY) == pdPASS) {
Serial.printf("Command from %s: ", cmd.source);
switch(cmd.type) {
case CMD_LED_ON:
digitalWrite(LED_BUILTIN, HIGH);
Serial.println("LED ON");
break;
case CMD_LED_OFF:
digitalWrite(LED_BUILTIN, LOW);
Serial.println("LED OFF");
break;
default:
Serial.println("Unknown");
break;
}
}
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(0, INPUT_PULLUP);
commandQueue = xQueueCreate(20, sizeof(Command));
xTaskCreate(taskButtonReader, "Button", 2048, NULL, 2, NULL);
xTaskCreate(taskSerialReader, "Serial", 2048, NULL, 2, NULL);
xTaskCreate(taskCommandProcessor, "Processor", 4096, NULL, 3, NULL);
}Priority Queue (Urgent Messages First)
QueueHandle_t normalQueue;
QueueHandle_t urgentQueue;
struct Message {
char text[64];
uint32_t timestamp;
};
void taskMessageProcessor(void* parameter) {
Message msg;
while(true) {
// Check urgent queue first
if (xQueueReceive(urgentQueue, &msg, 0) == pdPASS) {
Serial.print("URGENT: ");
Serial.println(msg.text);
}
// Then normal queue
else if (xQueueReceive(normalQueue, &msg, pdMS_TO_TICKS(100)) == pdPASS) {
Serial.print("Normal: ");
Serial.println(msg.text);
}
}
}
void taskNormalSender(void* parameter) {
Message msg;
int count = 0;
while(true) {
snprintf(msg.text, sizeof(msg.text), "Normal message %d", count++);
msg.timestamp = millis();
xQueueSend(normalQueue, &msg, pdMS_TO_TICKS(100));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskUrgentSender(void* parameter) {
Message msg;
while(true) {
if (digitalRead(0) == LOW) { // Emergency button
strcpy(msg.text, "EMERGENCY STOP");
msg.timestamp = millis();
xQueueSend(urgentQueue, &msg, 0);
vTaskDelay(pdMS_TO_TICKS(200));
}
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void setup() {
Serial.begin(115200);
pinMode(0, INPUT_PULLUP);
normalQueue = xQueueCreate(10, sizeof(Message));
urgentQueue = xQueueCreate(5, sizeof(Message));
xTaskCreate(taskNormalSender, "Normal", 2048, NULL, 1, NULL);
xTaskCreate(taskUrgentSender, "Urgent", 2048, NULL, 2, NULL);
xTaskCreate(taskMessageProcessor, "Processor", 4096, NULL, 3, NULL);
}Queue Overwrite (Latest Value Only)
QueueHandle_t statusQueue;
void taskSensorReader(void* parameter) {
int sensorValue;
while(true) {
sensorValue = analogRead(A0);
// Overwrite old value (queue length = 1)
xQueueOverwrite(statusQueue, &sensorValue);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void taskDisplay(void* parameter) {
int sensorValue;
while(true) {
if (xQueuePeek(statusQueue, &sensorValue, 0) == pdPASS) {
Serial.print("Latest value: ");
Serial.println(sensorValue);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
pinMode(A0, INPUT);
// Queue with 1 item for overwrite pattern
statusQueue = xQueueCreate(1, sizeof(int));
xTaskCreate(taskSensorReader, "Sensor", 2048, NULL, 2, NULL);
xTaskCreate(taskDisplay, "Display", 2048, NULL, 1, NULL);
}Queue From ISR
#define BUTTON_PIN 0
QueueHandle_t isrQueue;
volatile uint32_t isrCount = 0;
void IRAM_ATTR buttonISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
uint32_t timestamp = millis();
xQueueSendFromISR(isrQueue, ×tamp, &xHigherPriorityTaskWoken);
isrCount++;
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void taskButtonHandler(void* parameter) {
uint32_t timestamp;
uint32_t eventCount = 0;
while(true) {
if (xQueueReceive(isrQueue, ×tamp, portMAX_DELAY) == pdPASS) {
eventCount++;
Serial.printf("Button event #%lu at %lu ms (ISR count: %lu)\n",
eventCount, timestamp, isrCount);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
isrQueue = xQueueCreate(10, sizeof(uint32_t));
xTaskCreate(taskButtonHandler, "Handler", 2048, NULL, 3, NULL);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}Queue Reset (Clear All Messages)
void clearQueue() {
xQueueReset(dataQueue);
Serial.println("Queue cleared");
}Queue Sizing Guidelines
| Use Case | Queue Length | Item Size | Total Memory |
|---|---|---|---|
| Button events | 5-10 | 4 bytes | 40 bytes |
| Sensor data | 10-20 | 12 bytes | 240 bytes |
| Commands | 20-50 | 64 bytes | 3200 bytes |
| Large JSON | 5 | 512 bytes | 2560 bytes |
Formula:
Memory = (QueueLength × ItemSize) + QueueOverhead
QueueOverhead ≈ 80 bytesQueue Performance Tips
1. Size appropriately - Too large wastes RAM, too small drops data 2. Use timeout - Don't block forever (except consumers) 3. Check return values - pdPASS = success 4. Prefer peek for status - xQueuePeek() doesn't remove item 5. Reset on error - xQueueReset() clears stuck queues
Verification Checklist
- [ ] Queue created before tasks start
- [ ] Queue handle checked for NULL
- [ ] Item size matches struct sizeof()
- [ ] Send/receive return values checked
- [ ] Timeout values appropriate (0, pdMS_TO_TICKS(x), portMAX_DELAY)
- [ ] FromISR variants used in interrupts
- [ ] Queue usage monitored (not full/empty warnings)
- [ ] Large queues (>100 items) justified
Synchronization Patterns
Mutex (Mutual Exclusion)
SemaphoreHandle_t i2cMutex;
void taskSensorA(void* parameter) {
while(true) {
// Acquire mutex before I2C access
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
// Protected I2C transaction
Wire.beginTransmission(0x76);
Wire.write(0xF7);
Wire.endTransmission();
Serial.println("Sensor A read complete");
// Release mutex
xSemaphoreGive(i2cMutex);
} else {
Serial.println("Sensor A: Mutex timeout!");
}
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void taskSensorB(void* parameter) {
while(true) {
if (xSemaphoreTake(i2cMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
Wire.beginTransmission(0x77);
Wire.write(0x00);
Wire.endTransmission();
Serial.println("Sensor B read complete");
xSemaphoreGive(i2cMutex);
} else {
Serial.println("Sensor B: Mutex timeout!");
}
vTaskDelay(pdMS_TO_TICKS(700));
}
}
void setup() {
Serial.begin(115200);
Wire.begin();
// Create mutex with priority inheritance
i2cMutex = xSemaphoreCreateMutex();
if (i2cMutex == NULL) {
Serial.println("Mutex creation failed!");
while(1);
}
xTaskCreate(taskSensorA, "SensorA", 4096, NULL, 2, NULL);
xTaskCreate(taskSensorB, "SensorB", 4096, NULL, 2, NULL);
}Binary Semaphore (Event Signaling)
SemaphoreHandle_t dataReadySemaphore;
float sensorData = 0.0;
void taskProducer(void* parameter) {
while(true) {
// Read sensor
sensorData = analogRead(A0) * (3.3 / 4095.0);
// Signal data ready
xSemaphoreGive(dataReadySemaphore);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskConsumer(void* parameter) {
while(true) {
// Wait for data ready signal
if (xSemaphoreTake(dataReadySemaphore, portMAX_DELAY) == pdPASS) {
Serial.print("New data: ");
Serial.println(sensorData, 2);
}
}
}
void setup() {
Serial.begin(115200);
pinMode(A0, INPUT);
// Create binary semaphore (initially empty)
dataReadySemaphore = xSemaphoreCreateBinary();
xTaskCreate(taskProducer, "Producer", 2048, NULL, 2, NULL);
xTaskCreate(taskConsumer, "Consumer", 2048, NULL, 1, NULL);
}Counting Semaphore (Resource Pool)
#define NUM_BUFFERS 5
SemaphoreHandle_t bufferSemaphore;
char buffers[NUM_BUFFERS][128];
void taskWriter(void* parameter) {
int taskID = (int)parameter;
while(true) {
// Wait for available buffer
if (xSemaphoreTake(bufferSemaphore, pdMS_TO_TICKS(5000)) == pdPASS) {
// Find free buffer (simplified - should track properly)
Serial.printf("Task %d: Got buffer\n", taskID);
// Use buffer
vTaskDelay(pdMS_TO_TICKS(random(500, 2000)));
// Release buffer
xSemaphoreGive(bufferSemaphore);
Serial.printf("Task %d: Released buffer\n", taskID);
} else {
Serial.printf("Task %d: No buffers available!\n", taskID);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void setup() {
Serial.begin(115200);
// Create counting semaphore (5 resources)
bufferSemaphore = xSemaphoreCreateCounting(NUM_BUFFERS, NUM_BUFFERS);
// Create 10 tasks competing for 5 buffers
for (int i = 0; i < 10; i++) {
xTaskCreate(taskWriter, "Writer", 2048, (void*)i, 1, NULL);
}
}Critical Sections (Short Protection)
volatile uint32_t sharedCounter = 0;
void taskIncrement(void* parameter) {
while(true) {
// Enter critical section (disables interrupts)
taskENTER_CRITICAL();
sharedCounter++;
uint32_t local = sharedCounter;
taskEXIT_CRITICAL();
if (local % 1000 == 0) {
Serial.println(local);
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskIncrement, "Inc1", 2048, NULL, 1, NULL);
xTaskCreate(taskIncrement, "Inc2", 2048, NULL, 1, NULL);
}⚠️ Warning: Critical sections disable interrupts! Keep duration <10 microseconds.
Recursive Mutex (Nested Locking)
SemaphoreHandle_t recursiveMutex;
void functionNested() {
// Can be called while mutex already held
if (xSemaphoreTakeRecursive(recursiveMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
Serial.println("Nested function executing");
xSemaphoreGiveRecursive(recursiveMutex);
}
}
void functionOuter() {
if (xSemaphoreTakeRecursive(recursiveMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
Serial.println("Outer function executing");
// Call nested function (reentrant lock)
functionNested();
xSemaphoreGiveRecursive(recursiveMutex);
}
}
void taskExample(void* parameter) {
while(true) {
functionOuter();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void setup() {
Serial.begin(115200);
// Create recursive mutex
recursiveMutex = xSemaphoreCreateRecursiveMutex();
xTaskCreate(taskExample, "Example", 2048, NULL, 1, NULL);
}Priority Inheritance (Prevent Inversion)
SemaphoreHandle_t resourceMutex;
void taskLowPriority(void* parameter) {
while(true) {
Serial.println("LOW: Requesting resource");
if (xSemaphoreTake(resourceMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
Serial.println("LOW: Got resource, holding 500ms");
vTaskDelay(pdMS_TO_TICKS(500)); // Long operation
xSemaphoreGive(resourceMutex);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
void taskMediumPriority(void* parameter) {
while(true) {
Serial.println("MED: Busy work (no mutex)");
vTaskDelay(pdMS_TO_TICKS(10)); // CPU-intensive
}
}
void taskHighPriority(void* parameter) {
vTaskDelay(pdMS_TO_TICKS(100)); // Let low priority get mutex first
while(true) {
Serial.println("HIGH: Requesting resource");
// High priority will boost low priority task
if (xSemaphoreTake(resourceMutex, pdMS_TO_TICKS(1000)) == pdPASS) {
Serial.println("HIGH: Got resource");
xSemaphoreGive(resourceMutex);
}
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
void setup() {
Serial.begin(115200);
// Mutex has priority inheritance by default
resourceMutex = xSemaphoreCreateMutex();
xTaskCreate(taskLowPriority, "Low", 2048, NULL, 1, NULL);
xTaskCreate(taskMediumPriority, "Med", 2048, NULL, 2, NULL);
xTaskCreate(taskHighPriority, "High", 2048, NULL, 3, NULL);
}Semaphore from ISR
#define BUTTON_PIN 0
SemaphoreHandle_t buttonSemaphore;
void IRAM_ATTR buttonISR() {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(buttonSemaphore, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR();
}
}
void taskButtonHandler(void* parameter) {
while(true) {
if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY) == pdPASS) {
Serial.println("Button pressed!");
vTaskDelay(pdMS_TO_TICKS(200)); // Debounce
}
}
}
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
buttonSemaphore = xSemaphoreCreateBinary();
xTaskCreate(taskButtonHandler, "Button", 2048, NULL, 3, NULL);
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);
}Deadlock Prevention
❌ Deadlock Example (Wrong)
// Task A: Lock mutex1 → Lock mutex2
// Task B: Lock mutex2 → Lock mutex1
// = DEADLOCK!✅ Correct Pattern (Always Same Order)
SemaphoreHandle_t mutex1, mutex2;
void taskA(void* parameter) {
while(true) {
xSemaphoreTake(mutex1, portMAX_DELAY); // Order: 1 then 2
xSemaphoreTake(mutex2, portMAX_DELAY);
// Critical section
xSemaphoreGive(mutex2);
xSemaphoreGive(mutex1);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void taskB(void* parameter) {
while(true) {
xSemaphoreTake(mutex1, portMAX_DELAY); // Same order: 1 then 2
xSemaphoreTake(mutex2, portMAX_DELAY);
// Critical section
xSemaphoreGive(mutex2);
xSemaphoreGive(mutex1);
vTaskDelay(pdMS_TO_TICKS(100));
}
}Synchronization Primitive Comparison
| Primitive | Max Count | Priority Inheritance | Use Case |
|---|---|---|---|
| Mutex | 1 | ✅ Yes | Exclusive resource access |
| Binary Semaphore | 1 | ❌ No | Event signaling |
| Counting Semaphore | N | ❌ No | Resource pool (N items) |
| Recursive Mutex | Unlimited | ✅ Yes | Nested function calls |
| Critical Section | N/A | N/A | Very short (<10µs) |
Best Practices
1. Always use timeout - Don't block forever (except proven safe) 2. Check return values - pdPASS = success 3. Minimize lock duration - <10ms typical, <1ms ideal 4. Lock ordering - Always acquire in same order to prevent deadlock 5. Use mutex for shared data - Not binary semaphore 6. Critical sections: <10µs only - They disable interrupts! 7. FromISR variants in ISR - Never use regular take/give in ISR
Verification Checklist
- [ ] Mutex/semaphore created before use
- [ ] Handle checked for NULL
- [ ] Take/give always paired (no orphaned locks)
- [ ] Timeout appropriate (not portMAX_DELAY unless proven safe)
- [ ] Priority inheritance enabled for shared resources
- [ ] Lock duration minimized (<10ms)
- [ ] FromISR variants used in interrupts
- [ ] No nested locks (or consistent order)
Task Creation & Lifecycle Patterns
Basic Task Creation
TaskHandle_t myTaskHandle;
void myTask(void* parameter) {
// Task initialization
pinMode(LED_BUILTIN, OUTPUT);
while(true) {
// Task main loop
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
vTaskDelay(pdMS_TO_TICKS(1000));
}
// Task cleanup (if exiting)
vTaskDelete(NULL);
}
void setup() {
BaseType_t result = xTaskCreate(
myTask, // Task function
"MyTask", // Name (max 16 chars)
2048, // Stack size (bytes)
NULL, // Parameters
1, // Priority (0 = lowest, 24 = highest)
&myTaskHandle // Task handle (optional)
);
if (result != pdPASS) {
Serial.println("Task creation failed!");
}
}Task with Parameters
struct TaskParams {
int ledPin;
unsigned long interval;
};
void taskBlink(void* pvParameters) {
TaskParams* params = (TaskParams*)pvParameters;
pinMode(params->ledPin, OUTPUT);
while(true) {
digitalWrite(params->ledPin, HIGH);
vTaskDelay(pdMS_TO_TICKS(params->interval));
digitalWrite(params->ledPin, LOW);
vTaskDelay(pdMS_TO_TICKS(params->interval));
}
}
void setup() {
static TaskParams led1Params = {LED_BUILTIN, 500};
static TaskParams led2Params = {2, 1000};
xTaskCreate(taskBlink, "LED1", 2048, &led1Params, 1, NULL);
xTaskCreate(taskBlink, "LED2", 2048, &led2Params, 1, NULL);
}Core Pinning (ESP32 Dual-Core)
void taskCore0(void* parameter) {
while(true) {
Serial.print("Running on core: ");
Serial.println(xPortGetCoreID());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void taskCore1(void* parameter) {
while(true) {
Serial.print("Running on core: ");
Serial.println(xPortGetCoreID());
vTaskDelay(pdMS_TO_TICKS(1500));
}
}
void setup() {
Serial.begin(115200);
// Pin to Core 0 (protocol/WiFi stack)
xTaskCreatePinnedToCore(taskCore0, "Core0", 2048, NULL, 1, NULL, 0);
// Pin to Core 1 (application)
xTaskCreatePinnedToCore(taskCore1, "Core1", 2048, NULL, 1, NULL, 1);
}Priority Demonstration
void taskLowPriority(void* parameter) {
while(true) {
Serial.println("LOW priority task");
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void taskHighPriority(void* parameter) {
while(true) {
Serial.println("HIGH priority task");
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void setup() {
Serial.begin(115200);
// Low priority runs less often
xTaskCreate(taskLowPriority, "Low", 2048, NULL, 1, NULL);
// High priority preempts low priority
xTaskCreate(taskHighPriority, "High", 2048, NULL, 5, NULL);
}Stack Monitoring
TaskHandle_t taskHandle;
void taskMonitored(void* parameter) {
// Local variable (consumes stack)
char buffer[512];
while(true) {
snprintf(buffer, sizeof(buffer), "Task running");
Serial.println(buffer);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void loop() {
// Monitor stack usage
UBaseType_t freeWords = uxTaskGetStackHighWaterMark(taskHandle);
uint32_t freeBytes = freeWords * sizeof(StackType_t);
Serial.print("Free stack: ");
Serial.print(freeBytes);
Serial.println(" bytes");
vTaskDelay(pdMS_TO_TICKS(5000));
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskMonitored, "Monitor", 2048, NULL, 1, &taskHandle);
}Task Suspension & Resumption
TaskHandle_t workerTask;
void taskWorker(void* parameter) {
while(true) {
Serial.println("Working...");
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void taskController(void* parameter) {
bool suspended = false;
while(true) {
vTaskDelay(pdMS_TO_TICKS(3000));
if (suspended) {
Serial.println("Resuming worker");
vTaskResume(workerTask);
} else {
Serial.println("Suspending worker");
vTaskSuspend(workerTask);
}
suspended = !suspended;
}
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskWorker, "Worker", 2048, NULL, 1, &workerTask);
xTaskCreate(taskController, "Control", 2048, NULL, 2, NULL);
}Graceful Task Deletion
TaskHandle_t taskHandle;
volatile bool shouldExit = false;
void taskGraceful(void* parameter) {
// Allocate resources
uint8_t* buffer = (uint8_t*)malloc(1024);
while(!shouldExit) {
// Do work
Serial.println("Task running");
vTaskDelay(pdMS_TO_TICKS(500));
}
// Cleanup before exit
free(buffer);
Serial.println("Task exiting gracefully");
vTaskDelete(NULL);
}
void setup() {
Serial.begin(115200);
xTaskCreate(taskGraceful, "Graceful", 4096, NULL, 1, &taskHandle);
// Signal task to exit after 5 seconds
delay(5000);
shouldExit = true;
}Task State Monitoring
void printTaskState(TaskHandle_t handle, const char* name) {
eTaskState state = eTaskGetState(handle);
Serial.print(name);
Serial.print(": ");
switch(state) {
case eRunning: Serial.println("Running"); break;
case eReady: Serial.println("Ready"); break;
case eBlocked: Serial.println("Blocked"); break;
case eSuspended: Serial.println("Suspended"); break;
case eDeleted: Serial.println("Deleted"); break;
default: Serial.println("Unknown"); break;
}
}
TaskHandle_t task1, task2;
void loop() {
printTaskState(task1, "Task1");
printTaskState(task2, "Task2");
Serial.println();
vTaskDelay(pdMS_TO_TICKS(2000));
}Stack Overflow Detection
Enable in sdkconfig or Arduino IDE:
CONFIG_FREERTOS_CHECK_STACKOVERFLOW=2// Hook called when stack overflow detected
extern "C" void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName) {
Serial.print("STACK OVERFLOW in task: ");
Serial.println(pcTaskName);
// Infinite loop to halt system
while(1);
}Idle Hook (Background Tasks)
// Called during idle task execution
extern "C" void vApplicationIdleHook() {
// Keep this FAST (<10ms)
// Can be used for power management or cleanup
static unsigned long lastPrint = 0;
if (millis() - lastPrint > 5000) {
Serial.print("Heap free: ");
Serial.println(ESP.getFreeHeap());
lastPrint = millis();
}
}Task Statistics
void printTaskStats() {
char buffer[512];
vTaskList(buffer);
Serial.println("=== Task List ===");
Serial.println("Name State Prio Stack Num");
Serial.println(buffer);
}
void printRunTimeStats() {
char buffer[512];
vTaskGetRunTimeStats(buffer);
Serial.println("=== Runtime Stats ===");
Serial.println("Task Time %");
Serial.println(buffer);
}
void loop() {
printTaskStats();
printRunTimeStats();
vTaskDelay(pdMS_TO_TICKS(10000));
}Priority Guidelines
| Priority | Use Case | Example |
|---|---|---|
| 0 | Idle task (reserved) | System cleanup |
| 1-2 | Background tasks | Logging, LED blink |
| 3-4 | Standard tasks | Sensor reading, display |
| 5-10 | Important tasks | Network, user input |
| 11-24 | Critical/system | WiFi stack, safety |
Stack Sizing Guide
Minimum safe sizes:
- Simple task (LED blink): 1024 bytes
- Serial I/O: 2048 bytes
- WiFi/network: 4096 bytes
- Large buffers/JSON: 8192 bytes
Formula:
Stack = LocalVars + CallChain + MarginExample:
void taskExample(void* parameter) {
char buffer[512]; // 512 bytes
// Call chain: ~512 bytes
// Margin: 1024 bytes
// Total: 2048 bytes minimum
}
xTaskCreate(taskExample, "Ex", 2048, NULL, 1, NULL);Verification Checklist
- [ ] Task creation return value checked (pdPASS)
- [ ] Stack size validated with uxTaskGetStackHighWaterMark()
- [ ] Priority in valid range (0-24)
- [ ] Task function never returns (use while(true) or vTaskDelete)
- [ ] Core affinity set correctly (0 or 1) on ESP32
- [ ] Task handle stored if suspension/deletion needed
- [ ] Stack overflow detection enabled in config