velxio/backend/app/services/esp-idf-template/main/velxio_compat.h

74 lines
2.9 KiB
C
Raw Normal View History

#ifndef VELXIO_COMPAT_H
#define VELXIO_COMPAT_H
// Compatibility shims for sketches written against arduino-esp32 3.x running
// on the toolchain pinned to 2.0.17. Only kept until we bump the toolchain.
//
fix(esp32/ledc): translate ledcWrite(pin,duty) → ledcWrite(channel,duty) arduino-esp32 3.x ledcWrite takes a PIN and looks up the attached channel internally. arduino-esp32 2.x (the toolchain version we pin) takes a CHANNEL. The velxio_compat.h shim already aliased the 3.x-only ledcAttach onto ledcSetup+ledcAttachPin so 3.x sketches would compile, but ledcWrite still mapped 1:1 — so a call like #define R_PIN 16 ledcAttach(R_PIN, 5000, 8); // shim → channel 0 attached to pin 16 ledcWrite(R_PIN, 128); // ★ writes to "channel 16" (invalid) silently wrote to LEDC channel 16, which doesn't exist (valid range 0-15). The hardware duty register never changed, qemu-lcgamboa never emitted a `ledc_duty` event, and the RGB LED stayed dark even though the firmware ran cleanly and the wires looked right. Verified end-to-end with examples/esp32-pwm-led-rgb: gpio_change events fired at boot, no ledc_duty events fired, ledRed/ledGreen/ledBlue all stayed at 0. Fix: maintain a 40-entry pin→channel table populated by both ledcAttach variants. Replace ledcWrite with a macro that calls a helper checking the table first; if the value isn't a known pin we pass it through as a channel, preserving 2.x channel-style call sites. Macro/function name collision is sidestepped with the standard parenthesizing trick — `(ledcWrite)(channel, duty)` doesn't expand the function-like macro because the token isn't followed by `(`. Verified live on velxio.dev/example/esp32-pwm-led-rgb after hot-copying the new header into the velxio-app container: ledRed/ledGreen/ledBlue now cycle through the full HSV wheel as expected (samples: (255,41,0), (41,255,0), (0,41,255), (232,255,0), …). Single-file sketch only — the table is `static` (internal linkage) and ledcAttach + ledcWrite live in the header. Multi-file sketches that attach in file A and write in file B would each see their own table. Acceptable for now since arduino-esp32 sketches are nearly always single-file; revisit when we bump the toolchain to 3.x and can drop the shim entirely.
2026-05-22 21:14:55 +07:00
// arduino-esp32 3.x reshaped the LEDC API around pins:
// - ledcAttach(pin, freq, res) // auto-allocates a channel
// - ledcAttachChannel(pin, freq, res, channel)
// - ledcWrite(pin, duty) // takes pin, looks up channel
//
// arduino-esp32 2.x is channel-centric:
// - ledcSetup(channel, freq, res)
// - ledcAttachPin(pin, channel)
// - ledcWrite(channel, duty)
//
// We shim the 3.x calls onto the 2.x pair so unmodified 3.x sketches both
// compile AND drive the LEDC peripheral correctly. A pin-to-channel table
// keeps `ledcWrite(pin, ...)` working — without that translation
// `ledcWrite(16, ...)` writes to LEDC channel 16 (invalid; channels are
// 0-15) and the duty register never changes, so qemu-lcgamboa never emits
// a duty event and the LED stays dark.
#if defined(ARDUINO_ARCH_ESP32) && !defined(ledcAttach)
#include "Arduino.h"
fix(esp32/ledc): translate ledcWrite(pin,duty) → ledcWrite(channel,duty) arduino-esp32 3.x ledcWrite takes a PIN and looks up the attached channel internally. arduino-esp32 2.x (the toolchain version we pin) takes a CHANNEL. The velxio_compat.h shim already aliased the 3.x-only ledcAttach onto ledcSetup+ledcAttachPin so 3.x sketches would compile, but ledcWrite still mapped 1:1 — so a call like #define R_PIN 16 ledcAttach(R_PIN, 5000, 8); // shim → channel 0 attached to pin 16 ledcWrite(R_PIN, 128); // ★ writes to "channel 16" (invalid) silently wrote to LEDC channel 16, which doesn't exist (valid range 0-15). The hardware duty register never changed, qemu-lcgamboa never emitted a `ledc_duty` event, and the RGB LED stayed dark even though the firmware ran cleanly and the wires looked right. Verified end-to-end with examples/esp32-pwm-led-rgb: gpio_change events fired at boot, no ledc_duty events fired, ledRed/ledGreen/ledBlue all stayed at 0. Fix: maintain a 40-entry pin→channel table populated by both ledcAttach variants. Replace ledcWrite with a macro that calls a helper checking the table first; if the value isn't a known pin we pass it through as a channel, preserving 2.x channel-style call sites. Macro/function name collision is sidestepped with the standard parenthesizing trick — `(ledcWrite)(channel, duty)` doesn't expand the function-like macro because the token isn't followed by `(`. Verified live on velxio.dev/example/esp32-pwm-led-rgb after hot-copying the new header into the velxio-app container: ledRed/ledGreen/ledBlue now cycle through the full HSV wheel as expected (samples: (255,41,0), (41,255,0), (0,41,255), (232,255,0), …). Single-file sketch only — the table is `static` (internal linkage) and ledcAttach + ledcWrite live in the header. Multi-file sketches that attach in file A and write in file B would each see their own table. Acceptable for now since arduino-esp32 sketches are nearly always single-file; revisit when we bump the toolchain to 3.x and can drop the shim entirely.
2026-05-22 21:14:55 +07:00
// ESP32 / S2 / S3 / C3 all have <40 usable GPIOs. Sentinel 0xFF = unmapped.
static uint8_t _velxio_pin_to_channel[40] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
};
static inline bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) {
static uint8_t _velxio_next_channel = 0;
if (_velxio_next_channel >= 16) return false;
uint8_t ch = _velxio_next_channel++;
ledcSetup(ch, freq, resolution);
ledcAttachPin(pin, ch);
fix(esp32/ledc): translate ledcWrite(pin,duty) → ledcWrite(channel,duty) arduino-esp32 3.x ledcWrite takes a PIN and looks up the attached channel internally. arduino-esp32 2.x (the toolchain version we pin) takes a CHANNEL. The velxio_compat.h shim already aliased the 3.x-only ledcAttach onto ledcSetup+ledcAttachPin so 3.x sketches would compile, but ledcWrite still mapped 1:1 — so a call like #define R_PIN 16 ledcAttach(R_PIN, 5000, 8); // shim → channel 0 attached to pin 16 ledcWrite(R_PIN, 128); // ★ writes to "channel 16" (invalid) silently wrote to LEDC channel 16, which doesn't exist (valid range 0-15). The hardware duty register never changed, qemu-lcgamboa never emitted a `ledc_duty` event, and the RGB LED stayed dark even though the firmware ran cleanly and the wires looked right. Verified end-to-end with examples/esp32-pwm-led-rgb: gpio_change events fired at boot, no ledc_duty events fired, ledRed/ledGreen/ledBlue all stayed at 0. Fix: maintain a 40-entry pin→channel table populated by both ledcAttach variants. Replace ledcWrite with a macro that calls a helper checking the table first; if the value isn't a known pin we pass it through as a channel, preserving 2.x channel-style call sites. Macro/function name collision is sidestepped with the standard parenthesizing trick — `(ledcWrite)(channel, duty)` doesn't expand the function-like macro because the token isn't followed by `(`. Verified live on velxio.dev/example/esp32-pwm-led-rgb after hot-copying the new header into the velxio-app container: ledRed/ledGreen/ledBlue now cycle through the full HSV wheel as expected (samples: (255,41,0), (41,255,0), (0,41,255), (232,255,0), …). Single-file sketch only — the table is `static` (internal linkage) and ledcAttach + ledcWrite live in the header. Multi-file sketches that attach in file A and write in file B would each see their own table. Acceptable for now since arduino-esp32 sketches are nearly always single-file; revisit when we bump the toolchain to 3.x and can drop the shim entirely.
2026-05-22 21:14:55 +07:00
if (pin < 40) _velxio_pin_to_channel[pin] = ch;
return true;
}
static inline bool ledcAttachChannel(uint8_t pin, uint32_t freq, uint8_t resolution, uint8_t channel) {
if (channel >= 16) return false;
ledcSetup(channel, freq, resolution);
ledcAttachPin(pin, channel);
fix(esp32/ledc): translate ledcWrite(pin,duty) → ledcWrite(channel,duty) arduino-esp32 3.x ledcWrite takes a PIN and looks up the attached channel internally. arduino-esp32 2.x (the toolchain version we pin) takes a CHANNEL. The velxio_compat.h shim already aliased the 3.x-only ledcAttach onto ledcSetup+ledcAttachPin so 3.x sketches would compile, but ledcWrite still mapped 1:1 — so a call like #define R_PIN 16 ledcAttach(R_PIN, 5000, 8); // shim → channel 0 attached to pin 16 ledcWrite(R_PIN, 128); // ★ writes to "channel 16" (invalid) silently wrote to LEDC channel 16, which doesn't exist (valid range 0-15). The hardware duty register never changed, qemu-lcgamboa never emitted a `ledc_duty` event, and the RGB LED stayed dark even though the firmware ran cleanly and the wires looked right. Verified end-to-end with examples/esp32-pwm-led-rgb: gpio_change events fired at boot, no ledc_duty events fired, ledRed/ledGreen/ledBlue all stayed at 0. Fix: maintain a 40-entry pin→channel table populated by both ledcAttach variants. Replace ledcWrite with a macro that calls a helper checking the table first; if the value isn't a known pin we pass it through as a channel, preserving 2.x channel-style call sites. Macro/function name collision is sidestepped with the standard parenthesizing trick — `(ledcWrite)(channel, duty)` doesn't expand the function-like macro because the token isn't followed by `(`. Verified live on velxio.dev/example/esp32-pwm-led-rgb after hot-copying the new header into the velxio-app container: ledRed/ledGreen/ledBlue now cycle through the full HSV wheel as expected (samples: (255,41,0), (41,255,0), (0,41,255), (232,255,0), …). Single-file sketch only — the table is `static` (internal linkage) and ledcAttach + ledcWrite live in the header. Multi-file sketches that attach in file A and write in file B would each see their own table. Acceptable for now since arduino-esp32 sketches are nearly always single-file; revisit when we bump the toolchain to 3.x and can drop the shim entirely.
2026-05-22 21:14:55 +07:00
if (pin < 40) _velxio_pin_to_channel[pin] = channel;
return true;
}
fix(esp32/ledc): translate ledcWrite(pin,duty) → ledcWrite(channel,duty) arduino-esp32 3.x ledcWrite takes a PIN and looks up the attached channel internally. arduino-esp32 2.x (the toolchain version we pin) takes a CHANNEL. The velxio_compat.h shim already aliased the 3.x-only ledcAttach onto ledcSetup+ledcAttachPin so 3.x sketches would compile, but ledcWrite still mapped 1:1 — so a call like #define R_PIN 16 ledcAttach(R_PIN, 5000, 8); // shim → channel 0 attached to pin 16 ledcWrite(R_PIN, 128); // ★ writes to "channel 16" (invalid) silently wrote to LEDC channel 16, which doesn't exist (valid range 0-15). The hardware duty register never changed, qemu-lcgamboa never emitted a `ledc_duty` event, and the RGB LED stayed dark even though the firmware ran cleanly and the wires looked right. Verified end-to-end with examples/esp32-pwm-led-rgb: gpio_change events fired at boot, no ledc_duty events fired, ledRed/ledGreen/ledBlue all stayed at 0. Fix: maintain a 40-entry pin→channel table populated by both ledcAttach variants. Replace ledcWrite with a macro that calls a helper checking the table first; if the value isn't a known pin we pass it through as a channel, preserving 2.x channel-style call sites. Macro/function name collision is sidestepped with the standard parenthesizing trick — `(ledcWrite)(channel, duty)` doesn't expand the function-like macro because the token isn't followed by `(`. Verified live on velxio.dev/example/esp32-pwm-led-rgb after hot-copying the new header into the velxio-app container: ledRed/ledGreen/ledBlue now cycle through the full HSV wheel as expected (samples: (255,41,0), (41,255,0), (0,41,255), (232,255,0), …). Single-file sketch only — the table is `static` (internal linkage) and ledcAttach + ledcWrite live in the header. Multi-file sketches that attach in file A and write in file B would each see their own table. Acceptable for now since arduino-esp32 sketches are nearly always single-file; revisit when we bump the toolchain to 3.x and can drop the shim entirely.
2026-05-22 21:14:55 +07:00
// 3.x-style ledcWrite(pin, duty). Translate pin→channel via the table if
// we know about this pin; otherwise fall through to 2.x channel semantics
// so plain 2.x sketches keep working unchanged.
static inline void _velxio_ledc_write(uint32_t pin_or_channel, uint32_t duty) {
if (pin_or_channel < 40) {
uint8_t ch = _velxio_pin_to_channel[pin_or_channel];
if (ch != 0xFF) {
// The extra parentheses around `ledcWrite` block macro expansion
// (function-like macros only expand when followed by `(`), so
// this calls the real 2.x ledcWrite from esp32-hal-ledc.h.
(ledcWrite)((uint8_t)ch, duty);
return;
}
}
(ledcWrite)((uint8_t)pin_or_channel, duty);
}
#define ledcWrite(pin_or_channel, duty) _velxio_ledc_write((pin_or_channel), (duty))
#endif
#endif // VELXIO_COMPAT_H