Platform Abstraction Layer¶
src/pal/Pal.h— zero#ifdefin module code; all platform differences resolved here.
Introduced in Release 4, Sprint 1A.
Why it exists¶
Before PAL, platform guards were scattered through module files. Every new module author had to know which ESP32 API to call and remember to wrap it. PAL moves that knowledge to one place: modules call pal::millis(), pal::log(), pal::udp_bind(), and so on — the right implementation is selected at compile time inside Pal.h.
Three-way platform switch¶
| Macro | Platform | Toolchain |
|---|---|---|
ARDUINO |
ESP32 with Arduino framework | PlatformIO / arduino-esp32 |
IDF_VER |
ESP32 bare ESP-IDF 5.x / 6.x | ESP-IDF CMake |
| (neither) | PC / Raspberry Pi | CMake, standard C++17 |
The pattern inside every PAL function:
inline T pal::foo() {
#if defined(ARDUINO)
// Arduino ESP32 implementation
#elif defined(IDF_VER)
// Bare ESP-IDF implementation (or stub returning 0 / false)
#else
// PC / Raspberry Pi — POSIX / std::chrono / no-op
#endif
}
Function reference¶
Timing¶
| Function | Return | Description |
|---|---|---|
pal::micros() |
int64_t |
Microseconds since boot. ESP32: esp_timer_get_time(); PC: std::chrono::steady_clock |
pal::millis() |
uint32_t |
Milliseconds since boot |
Memory¶
| Function | Return | Description |
|---|---|---|
pal::psram_malloc(n) |
void* |
Allocates from PSRAM if available, falls back to internal heap; malloc() on PC |
pal::psram_free(p) |
void |
free() on all platforms |
GPIO¶
| Function | Description |
|---|---|
pal::gpio_write(pin, value) |
digitalWrite on Arduino, gpio_set_level on IDF, no-op on PC |
Logging¶
| Function | Description |
|---|---|
pal::log(fmt, ...) |
Serial.printf on Arduino, printf everywhere else |
System info¶
All system-info functions return float (or const char* / write to a caller-supplied buffer). ESP32 values come from ESP-IDF heap / chip APIs; PC returns 0.0f or "" where the concept doesn't apply.
| Function | Unit | IDF | PC |
|---|---|---|---|
cpu_freq_mhz() |
MHz | CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ |
0 |
cpu_cores() |
count | portNUM_PROCESSORS |
0 |
total_heap_kb() |
KB | heap_caps internal |
0 |
free_heap_kb() |
KB | heap_caps internal |
0 |
max_alloc_kb() |
KB | largest free block | 0 |
heap_min_kb() |
KB | minimum free since boot | 0 |
core_temp() |
°C | temperatureRead() (Arduino only) |
0 |
total_psram_kb() |
KB | MALLOC_CAP_SPIRAM |
0 |
free_psram_kb() |
KB | MALLOC_CAP_SPIRAM |
0 |
flash_size_mb() |
MB | esp_flash_get_size |
0 |
flash_speed_mhz() |
MHz | Arduino only | 0 |
sketch_kb() |
KB | Arduino OTA partition | 0 |
free_sketch_kb() |
KB | Arduino OTA partition | 0 |
fs_total_kb() |
KB | LittleFS partition | 0 |
fs_used_kb() |
KB | LittleFS partition | 0 |
reset_reason() |
int | esp_reset_reason() |
0 |
sdk_version() |
const char* |
IDF version string | "" |
chip_model(buf, len) |
writes string | e.g. "ESP32-S3 Rev 2" |
"" |
mac_address(buf, len) |
writes string | efuse MAC | "00:00:00:00:00:00" |
platform_version() |
const char* |
e.g. "arduino-esp32 3.1.0 (esp32dev)" |
"PC (pc)" |
Filesystem¶
| Function | Return | Description |
|---|---|---|
pal::fs_begin() |
bool |
Mount LittleFS. On Arduino: tries without format first, logs before formatting. On IDF: esp_vfs_littlefs_register. On PC: always returns true |
Note
board_build.littlefs_version = 2.0 must be set in platformio.ini. Without it PlatformIO's builder writes a v2.1 superblock that the ESP32 Arduino LittleFS driver rejects, silently wiping state (including WiFi credentials) on every boot.
FreeRTOS task helpers¶
| Function | Description |
|---|---|
pal::task_create_pinned(fn, name, stack, arg, priority, core) |
xTaskCreatePinnedToCore on ESP32; no-op on PC |
pal::yield() |
vTaskDelay(1) on ESP32; no-op on PC |
pal::suspend_forever() |
vTaskDelay(portMAX_DELAY) on ESP32; while(true){} on PC |
pal::reboot() |
esp_restart() after 100 ms on ESP32; printf no-op on PC |
FreeRTOS binary semaphore¶
Used for core-to-core synchronisation (e.g. effects task signals driver task after blending is done).
| Function | Return | Description |
|---|---|---|
pal::sem_binary_create() |
void* |
Allocate a binary semaphore (initially empty); cast to SemaphoreHandle_t internally on ESP32 |
pal::sem_give(h) |
void |
Signal: xSemaphoreGive; no-op on PC |
pal::sem_take(h, timeoutMs) |
bool |
Wait up to timeoutMs ms; xSemaphoreTake; always returns true on PC |
pal::sem_delete(h) |
void |
Free the semaphore object; no-op on PC |
Pass 100 (ms) rather than portMAX_DELAY so the task watchdog fires if Core 0 stalls.
UDP¶
Used by DeviceDiscoveryModule and (upcoming) Art-Net. The handle returned by udp_bind is an integer on all platforms — a pool-slot index on Arduino (where WiFiUDP is a stateful object), a raw file descriptor on PC.
| Function | Return | Description |
|---|---|---|
pal::udp_bind(port) |
int (handle) or -1 |
Bind a UDP socket for receiving on port |
pal::udp_close(handle) |
void |
Release the socket |
pal::udp_recv(handle, buf, len, from_ip, ip_len) |
bytes read or 0 |
Non-blocking read of one packet; populates from_ip |
pal::udp_broadcast(port, buf, len) |
bool |
Fire-and-forget send to 255.255.255.255:port |
Arduino note: a pool of 4 WiFiUDP slots is held in pal::_detail::udp_slot(i) (C++17 inline function with local static — ODR-safe across translation units). udp_bind finds a free slot; the returned index is the handle.
IDF note: all four UDP functions are stubs — discovery is inactive on bare IDF until the driver is wired.
IDF migration path¶
Most #elif defined(IDF_VER) branches call the same underlying ESP-IDF API that Arduino wraps. The only functions that still return 0 on IDF and need follow-up work are:
| Function | Reason |
|---|---|
core_temp() |
IDF 5.x temperature sensor requires stateful init/enable/read/disable |
flash_speed_mhz() |
Not exposed via public IDF API |
sketch_kb() / free_sketch_kb() |
Arduino OTA-partition concept; no direct IDF equivalent |
| UDP functions | Stubs — POSIX sockets need wiring under IDF VFS |
Source¶
For design questions about PAL threading, semaphore scope, OS handle abstraction, and cross-platform strategy, see the Developer FAQ.