FastLED 3.9.15
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
51void digitalWrite(int pin, PinValue val) {
52 platforms::digitalWrite(pin, val);
53}
54
56 return platforms::digitalRead(pin);
57}
58
59u16 analogRead(int pin) {
60 return platforms::analogRead(pin);
61}
62
63void setAdcRange(AdcRange range) {
64 platforms::setAdcRange(range);
65}
66
67// ============================================================================
68// Unified PWM State Management
69// ============================================================================
70
71namespace pwm_state {
72
73enum class PwmBackend : u8 {
74 None, // No setPwmFrequency called — use platform default
75 Native, // Platform hardware PWM handles frequency
76 IsrSoftware // ISR-based software PWM
77};
78
79constexpr u8 MAX_PWM_CHANNELS = 8;
80constexpr u32 ISR_FREQUENCY_HZ = 128000; // 128 kHz for 8-bit @ 500 Hz
81constexpr u32 MAX_ISR_PWM_FREQUENCY = 500;
82
84 int pin; // GPIO pin (-1 = unused)
85 u32 frequency_hz; // Configured PWM frequency
86 PwmBackend backend; // Which backend handles this pin
87 u8 duty_cycle; // 0-255 (ISR backend duty)
88 // ISR-only fields:
89 u16 period_ticks; // ISR ticks per PWM period
90 u16 high_ticks; // Ticks to stay HIGH
91 u16 tick_counter; // Current tick (0 to period_ticks-1)
92 bool pin_state; // Current GPIO state (ISR only)
93
97};
98
99// Singleton state container for PWM management
107
108// Access singleton state
112
113// ISR handler — services only ISR-backend entries
114void FL_IRAM pwm_isr_handler(void* user_data) {
115 (void)user_data;
116 PwmStateData& st = state();
117
118 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
119 PwmPinState& ch = st.channels[i];
120 if (ch.pin < 0 || ch.backend != PwmBackend::IsrSoftware) continue;
121
122 ch.tick_counter++;
123
124 // Turn LOW at duty cycle boundary
125 if (ch.tick_counter == ch.high_ticks && ch.pin_state) {
127 ch.pin_state = false;
128 }
129 // Start new period: reset counter, turn HIGH if duty > 0
130 else if (ch.tick_counter >= ch.period_ticks) {
131 ch.tick_counter = 0;
132 if (ch.high_ticks > 0) {
134 ch.pin_state = true;
135 }
136 }
137 }
138}
139
140// Find channel by pin number
142 PwmStateData& st = state();
143 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
144 if (st.channels[i].pin == pin) {
145 return &st.channels[i];
146 }
147 }
148 return nullptr;
149}
150
151// Allocate a free channel
153 PwmStateData& st = state();
154 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
155 if (st.channels[i].pin < 0) {
156 return &st.channels[i];
157 }
158 }
159 return nullptr;
160}
161
162// Count active ISR-backend channels
164 PwmStateData& st = state();
165 u8 count = 0;
166 for (u8 i = 0; i < MAX_PWM_CHANNELS; i++) {
167 if (st.channels[i].pin >= 0 && st.channels[i].backend == PwmBackend::IsrSoftware) {
168 count++;
169 }
170 }
171 return count;
172}
173
174// Ensure ISR timer is running (lazy init)
176 PwmStateData& st = state();
177 if (st.isr_active) return 0;
178
179 fl::isr::config cfg;
184
186 if (result != 0) {
187 FL_WARN("PWM: ISR attach failed: " << fl::isr::get_error_string(result));
188 return result;
189 }
190 st.isr_active = true;
191 return 0;
192}
193
194// Shutdown ISR if no ISR-backend channels remain
196 PwmStateData& st = state();
197 if (!st.isr_active) return;
198 if (countIsrChannels() > 0) return;
199
201 st.isr_active = false;
202}
203
204// Release a channel and cleanup
206 if (!ch || ch->pin < 0) return;
207
209
210 {
212 ch->pin = -1;
214 ch->frequency_hz = 0;
215 ch->duty_cycle = 0;
216 }
217
219}
220
221} // namespace pwm_state
222
223// ============================================================================
224// analogWrite / setPwm16 — route through PWM state when configured
225// ============================================================================
226
227void analogWrite(int pin, u16 val) {
229
231 // Route to ISR duty cycle update (scale 8-bit val to duty)
232 u8 duty = (val > 255) ? 255 : static_cast<u8>(val);
234 ch->duty_cycle = duty;
235 ch->high_ticks = (static_cast<u32>(ch->period_ticks) * duty) / 256;
236 return;
237 }
238
239 if (ch && ch->backend == pwm_state::PwmBackend::Native) {
240 // Native backend — forward to platform (frequency already configured)
241 platforms::analogWrite(pin, val);
242 return;
243 }
244
245 // No setPwmFrequency called — forward to platform as before
246 platforms::analogWrite(pin, val);
247}
248
249void setPwm16(int pin, u16 val) {
251
253 // Route to ISR duty cycle update (scale 16-bit to 8-bit)
254 u8 duty = static_cast<u8>(val >> 8);
256 ch->duty_cycle = duty;
257 ch->high_ticks = (static_cast<u32>(ch->period_ticks) * duty) / 256;
258 return;
259 }
260
261 if (ch && ch->backend == pwm_state::PwmBackend::Native) {
262 platforms::setPwm16(pin, val);
263 return;
264 }
265
266 // No setPwmFrequency called — forward to platform as before
267 platforms::setPwm16(pin, val);
268}
269
270// ============================================================================
271// PWM Frequency API
272// ============================================================================
273
274int setPwmFrequency(int pin, u32 frequency_hz) {
275 // Check if pin already has a channel
277
278 if (ch) {
279 // Pin already configured — release and reconfigure
281 ch = nullptr;
282 }
283
284 // Validate frequency
285 if (frequency_hz == 0) {
286 FL_WARN("setPwmFrequency: Frequency must be > 0");
287 return -1;
288 }
289
290 // Query platform: can it handle this natively?
291 bool needs_isr = platforms::needsPwmIsrFallback(pin, frequency_hz);
292
293 if (!needs_isr) {
294 // Native path
295 int result = platforms::setPwmFrequencyNative(pin, frequency_hz);
296 if (result != 0) {
297 FL_WARN("setPwmFrequency: Native backend failed: " << result);
298 return result;
299 }
300
301 // Allocate tracking slot
302 ch = pwm_state::allocate();
303 if (!ch) {
304 FL_WARN("setPwmFrequency: All " << static_cast<int>(pwm_state::MAX_PWM_CHANNELS) << " channels in use");
305 return -2;
306 }
307
308 ch->pin = pin;
309 ch->frequency_hz = frequency_hz;
311 ch->duty_cycle = 0;
312 return 0;
313 }
314
315 // ISR software fallback path
316 if (frequency_hz > pwm_state::MAX_ISR_PWM_FREQUENCY) {
317 FL_WARN("setPwmFrequency: ISR fallback max " << pwm_state::MAX_ISR_PWM_FREQUENCY << " Hz, requested " << frequency_hz);
318 return -1;
319 }
320
321 // Allocate channel
322 ch = pwm_state::allocate();
323 if (!ch) {
324 FL_WARN("setPwmFrequency: All " << static_cast<int>(pwm_state::MAX_PWM_CHANNELS) << " channels in use");
325 return -2;
326 }
327
328 // Ensure ISR is running
329 int isr_result = pwm_state::ensureIsrActive();
330 if (isr_result != 0) {
331 return -3;
332 }
333
334 // Configure GPIO
337
338 // Initialize channel (atomic)
339 {
341 ch->pin = pin;
342 ch->frequency_hz = frequency_hz;
344 ch->period_ticks = pwm_state::ISR_FREQUENCY_HZ / frequency_hz;
345 ch->duty_cycle = 0;
346 ch->high_ticks = 0;
347 ch->tick_counter = 0;
348 ch->pin_state = false;
349 }
350
351 return 0;
352}
353
354u32 getPwmFrequency(int pin) {
356 if (ch) {
357 return ch->frequency_hz;
358 }
359
360 // Not in our state — ask platform (may have been set externally)
361 return platforms::getPwmFrequencyNative(pin);
362}
363
364int pwmEnd(int pin) {
366 if (!ch) {
367 return -1;
368 }
369
371 return 0;
372}
373
374// ============================================================================
375// pinMode - releases PWM when pin mode changes
376// ============================================================================
377
378void pinMode(int pin, PinMode mode) {
379 // Release any active PWM channel on this pin
380 // When pinMode is called, the pin's function is being changed,
381 // so any existing PWM configuration should be cleared
383 if (ch) {
385 }
386
387 platforms::pinMode(pin, mode);
388}
389
390} // namespace fl
static T & instance() FL_NOEXCEPT
Definition singleton.h:41
RAII helper for critical sections (interrupt disable/enable) Automatically disables interrupts on con...
Umbrella header for ISR subsystem.
#define FL_WARN(X)
Definition log.h:276
Centralized logging categories for FastLED hardware interfaces and subsystems.
constexpr u32 ISR_FLAG_IRAM_SAFE
Definition constants.h:21
int detach_handler(handle &h)
Detach an ISR handler.
int attach_timer_handler(const config &cfg, handle *out_handle)
Attach a timer-based ISR handler.
const char * get_error_string(int error_code)
Get platform-specific error description.
constexpr u8 ISR_PRIORITY_MEDIUM
Definition constants.h:15
void maybeShutdownIsr()
Definition pin.cpp.hpp:195
constexpr u8 MAX_PWM_CHANNELS
Definition pin.cpp.hpp:79
constexpr u32 MAX_ISR_PWM_FREQUENCY
Definition pin.cpp.hpp:81
PwmPinState * allocate()
Definition pin.cpp.hpp:152
u8 countIsrChannels()
Definition pin.cpp.hpp:163
void releaseChannel(PwmPinState *ch)
Definition pin.cpp.hpp:205
constexpr u32 ISR_FREQUENCY_HZ
Definition pin.cpp.hpp:80
PwmStateData & state()
Definition pin.cpp.hpp:109
void FL_IRAM pwm_isr_handler(void *user_data)
Definition pin.cpp.hpp:114
PwmPinState * findByPin(int pin)
Definition pin.cpp.hpp:141
int ensureIsrActive()
Definition pin.cpp.hpp:175
unsigned char u8
Definition stdint.h:131
void setAdcRange(AdcRange range)
Set ADC voltage range.
Definition pin.cpp.hpp:63
u32 getPwmFrequency(int pin)
Query the configured PWM frequency for a pin.
Definition pin.cpp.hpp:354
void setPwm16(int pin, u16 val)
Set PWM duty cycle with 16-bit resolution.
Definition pin.cpp.hpp:249
PinValue
Digital pin value.
Definition pin.h:49
@ Low
Logic low (0V / GND)
Definition pin.h:50
@ High
Logic high (3.3V / 5V, platform-dependent)
Definition pin.h:51
int setPwmFrequency(int pin, u32 frequency_hz)
Set PWM frequency for a pin.
Definition pin.cpp.hpp:274
int pwmEnd(int pin)
Release PWM channel and stop output on a pin.
Definition pin.cpp.hpp:364
AdcRange
ADC voltage range configuration.
Definition pin.h:57
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
void pinMode(int pin, PinMode mode)
Set pin mode (input, output, pull-up, pull-down)
Definition pin.cpp.hpp:378
PinValue digitalRead(int pin)
Read digital value from pin.
Definition pin.cpp.hpp:55
void analogWrite(int pin, u16 val)
Write analog value to pin (PWM)
Definition pin.cpp.hpp:227
void digitalWrite(int pin, PinValue val)
Write digital value to pin.
Definition pin.cpp.hpp:51
u16 analogRead(int pin)
Read analog value from pin.
Definition pin.cpp.hpp:59
PinMode
Pin mode configuration.
Definition pin.h:41
@ Output
Digital output (push-pull)
Definition pin.h:43
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_IRAM
#define FL_NOEXCEPT
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_NOEXCEPT
Definition pin.cpp.hpp:94
fl::isr::handle isr_handle
Definition pin.cpp.hpp:102
PwmPinState channels[MAX_PWM_CHANNELS]
Definition pin.cpp.hpp:101