FastLED 3.10.8
Loading...
Searching...
No Matches
pin.cpp.hpp
Go to the documentation of this file.
1
26
27#include "fl/system/pin.h"
28
29// Include platform-specific implementations (must come after fl/pin.h for proper type resolution)
30#include "platforms/pin.h"
31
32// Additional includes for PWM implementation
33#include "fl/stl/isr.h"
34#include "fl/log/log.h"
36#include "fl/stl/singleton.h"
37#include "fl/stl/noexcept.h"
38
39namespace fl {
40
41// ============================================================================
42// Non-inline wrapper functions (basic I/O)
43// ============================================================================
44// These wrappers are necessary because the platform-specific implementations
45// are inline functions. The linker needs actual symbols to link against when
46// other translation units call these functions.
47//
48// NOTE: pinMode() is defined later, after PWM state management, because it
49// needs to release PWM channels.
50
52 platforms::digitalWrite(pin, val);
53}
54
56 return platforms::digitalRead(pin);
57}
58
59// analogRead() lives in fl/system/analog/analog_read.cpp.hpp, its own unity
60// object, so the platform ADC driver links only when it is used (#4796).
61
63 platforms::setAdcRange(range);
64}
65
66// ============================================================================
67// Unified PWM State Management
68// ============================================================================
69
70namespace pwm_state {
71
72enum class PwmBackend : u8 {
73 None, // No setPwmFrequency called — use platform default
74 Native, // Platform hardware PWM handles frequency
75 IsrSoftware // ISR-based software PWM
76};
77
78constexpr u8 MAX_PWM_CHANNELS = 8;
79constexpr u32 ISR_FREQUENCY_HZ = 128000; // 128 kHz for 8-bit @ 500 Hz
80constexpr u32 MAX_ISR_PWM_FREQUENCY = 500;
81
83 int pin; // GPIO pin (-1 = unused)
84 u32 frequency_hz; // Configured PWM frequency
85 PwmBackend backend; // Which backend handles this pin
86 u8 duty_cycle; // 0-255 (ISR backend duty)
87 // ISR-only fields:
88 u16 period_ticks; // ISR ticks per PWM period
89 u16 high_ticks; // Ticks to stay HIGH
90 u16 tick_counter; // Current tick (0 to period_ticks-1)
91 bool pin_state; // Current GPIO state (ISR only)
92
96};
97
98// Singleton state container for PWM management
106
107// Set the first time state() runs, which happens only on the paths that
108// allocate a channel (setPwmFrequency) or service one (the ISR). The
109// lookup paths taken by every sketch -- pinMode(), analogWrite(),
110// setPwm16() -- read this pointer instead of calling state(), so a sketch
111// that never calls setPwmFrequency() does not link the singleton storage.
112// A null pointer means no channel has ever been allocated. The target is
113// static storage, so the raw pointer can never dangle.
114// FL_LINT_ALLOW_GLOBAL(constant-initialized null pointer; see above)
116
117// Access singleton state (creates it on first use)
120 g_state = &st;
121 return st;
122}
123
124// ISR handler — services only ISR-backend entries
125void FL_IRAM pwm_isr_handler(void* user_data) FL_NO_EXCEPT {
126 (void)user_data;
127 // The ISR is attached only after setPwmFrequency() has created the
128 // state, so read the singleton directly instead of republishing g_state.
130
131 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
132 PwmPinState& ch = st.channels[i];
133 if (ch.pin < 0 || ch.backend != PwmBackend::IsrSoftware) continue;
134
135 ch.tick_counter++;
136
137 // Turn LOW at duty cycle boundary
138 if (ch.tick_counter == ch.high_ticks && ch.pin_state) {
140 ch.pin_state = false;
141 }
142 // Start new period: reset counter, turn HIGH if duty > 0
143 else if (ch.tick_counter >= ch.period_ticks) {
144 ch.tick_counter = 0;
145 if (ch.high_ticks > 0) {
147 ch.pin_state = true;
148 }
149 }
150 }
151}
152
153// Find channel by pin number
155 if (!g_state) return nullptr; // No channel ever allocated.
156 PwmStateData& st = *g_state;
157 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
158 if (st.channels[i].pin == pin) {
159 return &st.channels[i];
160 }
161 }
162 return nullptr;
163}
164
165// Allocate a free channel
167 PwmStateData& st = state();
168 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
169 if (st.channels[i].pin < 0) {
170 return &st.channels[i];
171 }
172 }
173 return nullptr;
174}
175
176// Count active ISR-backend channels
178 if (!g_state) return 0;
179 PwmStateData& st = *g_state;
180 u8 count = 0;
181 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
182 if (st.channels[i].pin >= 0 && st.channels[i].backend == PwmBackend::IsrSoftware) {
183 count++;
184 }
185 }
186 return count;
187}
188
189// Ensure ISR timer is running (lazy init)
191 PwmStateData& st = state();
192 if (st.isr_active) return 0;
193
194 fl::isr::config cfg;
199
201 if (result != 0) {
202 FL_WARN("PWM: ISR attach failed: " << (fl::isr::get_error_string(result)));
203 return result;
204 }
205 st.isr_active = true;
206 return 0;
207}
208
209// Shutdown ISR if no ISR-backend channels remain
211 if (!g_state) return; // ISR can only be active once state exists.
212 PwmStateData& st = *g_state;
213 if (!st.isr_active) return;
214 if (countIsrChannels() > 0) return;
215
217 st.isr_active = false;
218}
219
220// Release a channel and cleanup
222 if (!ch || ch->pin < 0) return;
223
225
226 {
228 ch->pin = -1;
229 ch->backend = PwmBackend::None;
230 ch->frequency_hz = 0;
231 ch->duty_cycle = 0;
232 }
233
235}
236
237} // namespace pwm_state
238
239// ============================================================================
240// analogWrite / setPwm16 — route through PWM state when configured
241// ============================================================================
242
243void analogWrite(int pin, u16 val) FL_NO_EXCEPT {
245
247 // Route to ISR duty cycle update (scale 8-bit val to duty)
248 u8 duty = (val > 255) ? 255 : static_cast<u8>(val);
250 ch->duty_cycle = duty;
251 ch->high_ticks = (static_cast<u32>(ch->period_ticks) * duty) / 256;
252 return;
253 }
254
255 if (ch && ch->backend == pwm_state::PwmBackend::Native) {
256 // Native backend — forward to platform (frequency already configured)
257 platforms::analogWrite(pin, val);
258 return;
259 }
260
261 // No setPwmFrequency called — forward to platform as before
262 platforms::analogWrite(pin, val);
263}
264
265void setPwm16(int pin, u16 val) FL_NO_EXCEPT {
267
269 // Route to ISR duty cycle update (scale 16-bit to 8-bit)
270 u8 duty = static_cast<u8>(val >> 8);
272 ch->duty_cycle = duty;
273 ch->high_ticks = (static_cast<u32>(ch->period_ticks) * duty) / 256;
274 return;
275 }
276
277 if (ch && ch->backend == pwm_state::PwmBackend::Native) {
278 platforms::setPwm16(pin, val);
279 return;
280 }
281
282 // No setPwmFrequency called — forward to platform as before
283 platforms::setPwm16(pin, val);
284}
285
286// ============================================================================
287// PWM Frequency API
288// ============================================================================
289
290int setPwmFrequency(int pin, u32 frequency_hz) FL_NO_EXCEPT {
291 // Check if pin already has a channel
293
294 if (ch) {
295 // Pin already configured — release and reconfigure
297 ch = nullptr;
298 }
299
300 // Validate frequency
301 if (frequency_hz == 0) {
302 FL_WARN("setPwmFrequency: Frequency must be > 0");
303 return -1;
304 }
305
306 // Query platform: can it handle this natively?
307 bool needs_isr = platforms::needsPwmIsrFallback(pin, frequency_hz);
308
309 if (!needs_isr) {
310 // Native path
311 int result = platforms::setPwmFrequencyNative(pin, frequency_hz);
312 if (result != 0) {
313 FL_WARN("setPwmFrequency: Native backend failed: " << result);
314 return result;
315 }
316
317 // Allocate tracking slot
318 ch = pwm_state::allocate();
319 if (!ch) {
320 FL_WARN("setPwmFrequency: All " << (static_cast<int>(pwm_state::MAX_PWM_CHANNELS)) << " channels in use");
321 return -2;
322 }
323
324 ch->pin = pin;
325 ch->frequency_hz = frequency_hz;
327 ch->duty_cycle = 0;
328 return 0;
329 }
330
331 // ISR software fallback path
332 if (frequency_hz > pwm_state::MAX_ISR_PWM_FREQUENCY) {
333 FL_WARN("setPwmFrequency: ISR fallback max " << (pwm_state::MAX_ISR_PWM_FREQUENCY) << " Hz, requested " << frequency_hz);
334 return -1;
335 }
336
337 // Allocate channel
338 ch = pwm_state::allocate();
339 if (!ch) {
340 FL_WARN("setPwmFrequency: All " << (static_cast<int>(pwm_state::MAX_PWM_CHANNELS)) << " channels in use");
341 return -2;
342 }
343
344 // Ensure ISR is running
345 int isr_result = pwm_state::ensureIsrActive();
346 if (isr_result != 0) {
347 return -3;
348 }
349
350 // Configure GPIO
353
354 // Initialize channel (atomic)
355 {
357 ch->pin = pin;
358 ch->frequency_hz = frequency_hz;
360 ch->period_ticks = pwm_state::ISR_FREQUENCY_HZ / frequency_hz;
361 ch->duty_cycle = 0;
362 ch->high_ticks = 0;
363 ch->tick_counter = 0;
364 ch->pin_state = false;
365 }
366
367 return 0;
368}
369
372 if (ch) {
373 return ch->frequency_hz;
374 }
375
376 // Not in our state — ask platform (may have been set externally)
377 return platforms::getPwmFrequencyNative(pin);
378}
379
380int pwmEnd(int pin) FL_NO_EXCEPT {
382 if (!ch) {
383 return -1;
384 }
385
387 return 0;
388}
389
390// ============================================================================
391// pinMode - releases PWM when pin mode changes
392// ============================================================================
393
394void pinMode(int pin, PinMode mode) FL_NO_EXCEPT {
395 // Release any active PWM channel on this pin
396 // When pinMode is called, the pin's function is being changed,
397 // so any existing PWM configuration should be cleared
399 if (ch) {
401 }
402
403 platforms::pinMode(pin, mode);
404}
405
406} // namespace fl
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
RAII helper for critical sections (interrupt disable/enable) Automatically disables interrupts on con...
#define FL_IRAM
Umbrella header for ISR subsystem.
#define FL_WARN(...)
Definition log.h:437
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr u32 ISR_FLAG_IRAM_SAFE
Definition constants.h:21
const char * get_error_string(int error_code) FL_NO_EXCEPT
Get platform-specific error description.
int attach_timer_handler(const config &cfg, handle *out_handle) FL_NO_EXCEPT
Attach a timer-based ISR handler.
constexpr u8 ISR_PRIORITY_MEDIUM
Definition constants.h:15
int detach_handler(handle &h) FL_NO_EXCEPT
Detach an ISR handler.
void releaseChannel(PwmPinState *ch) FL_NO_EXCEPT
Definition pin.cpp.hpp:221
void FL_IRAM pwm_isr_handler(void *user_data) FL_NO_EXCEPT
Definition pin.cpp.hpp:125
constexpr u8 MAX_PWM_CHANNELS
Definition pin.cpp.hpp:78
constexpr u32 MAX_ISR_PWM_FREQUENCY
Definition pin.cpp.hpp:80
PwmStateData & state() FL_NO_EXCEPT
Definition pin.cpp.hpp:118
constexpr u32 ISR_FREQUENCY_HZ
Definition pin.cpp.hpp:79
int ensureIsrActive() FL_NO_EXCEPT
Definition pin.cpp.hpp:190
PwmPinState * findByPin(int pin) FL_NO_EXCEPT
Definition pin.cpp.hpp:154
void maybeShutdownIsr() FL_NO_EXCEPT
Definition pin.cpp.hpp:210
PwmPinState * allocate() FL_NO_EXCEPT
Definition pin.cpp.hpp:166
u8 countIsrChannels() FL_NO_EXCEPT
Definition pin.cpp.hpp:177
PwmStateData * g_state
Definition pin.cpp.hpp:115
unsigned char u8
Definition stdint.h:131
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
u32 getPwmFrequency(int pin) FL_NO_EXCEPT
Query the configured PWM frequency for a pin.
Definition pin.cpp.hpp:370
PinValue
Digital pin value.
Definition pin.h:50
@ Low
Logic low (0V / GND)
Definition pin.h:51
@ High
Logic high (3.3V / 5V, platform-dependent)
Definition pin.h:52
void setAdcRange(AdcRange range) FL_NO_EXCEPT
Set ADC voltage range.
Definition pin.cpp.hpp:62
AdcRange
ADC voltage range configuration.
Definition pin.h:58
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
void analogWrite(int pin, u16 val) FL_NO_EXCEPT
Write analog value to pin (PWM)
Definition pin.cpp.hpp:243
int pwmEnd(int pin) FL_NO_EXCEPT
Release PWM channel and stop output on a pin.
Definition pin.cpp.hpp:380
int setPwmFrequency(int pin, u32 frequency_hz) FL_NO_EXCEPT
Set PWM frequency for a pin.
Definition pin.cpp.hpp:290
void digitalWrite(int pin, PinValue val) FL_NO_EXCEPT
Write digital value to pin.
Definition pin.cpp.hpp:51
PinValue digitalRead(int pin) FL_NO_EXCEPT
Read digital value from pin.
Definition pin.cpp.hpp:55
void setPwm16(int pin, u16 val) FL_NO_EXCEPT
Set PWM duty cycle with 16-bit resolution.
Definition pin.cpp.hpp:265
PinMode
Pin mode configuration.
Definition pin.h:42
@ Output
Digital output (push-pull)
Definition pin.h:44
void pinMode(int pin, PinMode mode) FL_NO_EXCEPT
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:394
Base definition for an LED controller.
Definition crgb.hpp:179
handler_fn handler
Definition handler.h:20
Configuration for ISR attachment.
Definition handler.h:19
Opaque handle to an attached ISR.
Definition handler.h:36
PwmPinState() FL_NO_EXCEPT
Definition pin.cpp.hpp:93
PwmStateData() FL_NO_EXCEPT
Definition pin.cpp.hpp:104
fl::isr::handle isr_handle
Definition pin.cpp.hpp:101
PwmPinState channels[MAX_PWM_CHANNELS]
Definition pin.cpp.hpp:100