idfxx 1.0.0
Modern C++23 components for ESP-IDF
Loading...
Searching...
No Matches
pwm.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 Chris Leishman
3
4#pragma once
5
24#include <idfxx/error>
25#include <idfxx/gpio>
26#include <idfxx/intr_alloc>
27
28#include <algorithm>
29#include <chrono>
30#include <frequency/frequency>
31#include <optional>
32#include <string>
33#include <utility>
34
35namespace idfxx::pwm {
36
37// ======================================================================
38// Enums
39// ======================================================================
40
48enum class speed_mode : int {
49 low_speed = 0,
50#if SOC_LEDC_SUPPORT_HS_MODE
52#endif
53};
54
62enum class channel : int {
63 ch_0 = 0,
64 ch_1,
65 ch_2,
66 ch_3,
67 ch_4,
68 ch_5,
69#if SOC_LEDC_CHANNEL_NUM > 6
70 ch_6,
71#endif
72#if SOC_LEDC_CHANNEL_NUM > 7
73 ch_7,
74#endif
75};
76
84enum class clk_source : int {
85 auto_select = 0,
86#if SOC_LEDC_SUPPORT_APB_CLOCK
87 apb,
88#endif
89#if SOC_LEDC_SUPPORT_REF_TICK
90 ref_tick,
91#endif
92#if SOC_LEDC_SUPPORT_PLL_DIV_CLOCK
93 pll_div,
94#endif
95#if SOC_LEDC_SUPPORT_XTAL_CLOCK
96 xtal,
97#endif
98};
99
104enum class fade_mode : int {
105 no_wait = 0,
106 wait_done,
107};
108
113enum class sleep_mode : int {
114 no_alive_no_pd = 0,
116 keep_alive,
117};
118
119// ======================================================================
120// Forward declarations
121// ======================================================================
122
124class output;
125struct output_config;
126template<int N, speed_mode M>
127struct timer_constant;
130// ======================================================================
131// timer (value type)
132// ======================================================================
133
155class timer {
156 template<int N, speed_mode M>
157 friend struct timer_constant;
158 friend std::optional<timer> get_timer(enum channel, enum speed_mode);
159
160public:
170
171 timer(const timer&) = default;
172 timer& operator=(const timer&) = default;
173 timer(timer&&) = default;
174 timer& operator=(timer&&) = default;
175
176 constexpr bool operator==(const timer& other) const = default;
177
180 [[nodiscard]] constexpr int idf_num() const noexcept { return _num; }
184 [[nodiscard]] constexpr enum speed_mode speed_mode() const noexcept { return _speed_mode; }
185
186#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
202 void configure(const struct config& cfg) { unwrap(try_configure(cfg)); }
203
217#endif
218
232
243 return try_configure({.frequency = frequency, .resolution_bits = resolution_bits});
244 }
245
255
264
273
283
291 [[nodiscard]] std::chrono::nanoseconds period() const;
292
302
303#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
314#endif
315
328
329#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
346 template<typename Rep, typename Period>
347 void set_period(const std::chrono::duration<Rep, Period>& period) {
349 }
350#endif
351
365 template<typename Rep, typename Period>
366 [[nodiscard]] result<void> try_set_period(const std::chrono::duration<Rep, Period>& period) {
367 auto ns = std::chrono::ceil<std::chrono::nanoseconds>(period).count();
368 if (ns <= 0) {
369 return error(errc::invalid_arg);
370 }
371 return try_set_frequency(freq::hertz{1'000'000'000 / ns});
372 }
373
374#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
381 void pause() { unwrap(try_pause()); }
382#endif
383
390
391#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
398 void resume() { unwrap(try_resume()); }
399#endif
400
407
408#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
415 void reset() { unwrap(try_reset()); }
416#endif
417
424
425private:
427
428 constexpr timer(int num, enum speed_mode mode) noexcept
429 : _num(num)
430 , _speed_mode(mode) {}
431
432 int _num;
433 enum speed_mode _speed_mode;
434};
435
437template<int N, speed_mode M>
438struct timer_constant {
439 static_assert(N >= 0 && N < SOC_LEDC_TIMER_NUM, "Invalid PWM timer number");
440 static constexpr timer value{N, M};
441};
450inline constexpr timer timer_0 = timer_constant<0, speed_mode::low_speed>::value;
451inline constexpr timer timer_1 = timer_constant<1, speed_mode::low_speed>::value;
452inline constexpr timer timer_2 = timer_constant<2, speed_mode::low_speed>::value;
453inline constexpr timer timer_3 = timer_constant<3, speed_mode::low_speed>::value;
454
455#if SOC_LEDC_SUPPORT_HS_MODE
456inline constexpr timer hs_timer_0 = timer_constant<0, speed_mode::high_speed>::value;
457inline constexpr timer hs_timer_1 = timer_constant<1, speed_mode::high_speed>::value;
458inline constexpr timer hs_timer_2 = timer_constant<2, speed_mode::high_speed>::value;
459inline constexpr timer hs_timer_3 = timer_constant<3, speed_mode::high_speed>::value;
460#endif
// end of Timer Constants
462
463// ======================================================================
464// start / try_start — free functions for starting PWM output
465// ======================================================================
466
477
478#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
508
512
534
538
541void stop(
542 enum channel ch,
545);
546#endif
547
576
591
617
631
641
650[[nodiscard]] std::optional<timer> get_timer(enum channel ch, enum speed_mode mode = speed_mode::low_speed);
651
665 enum channel ch,
668);
669
670// ======================================================================
671// output (RAII, move-only)
672// ======================================================================
673
698class output {
699public:
706
707 output(const output&) = delete;
708 output& operator=(const output&) = delete;
709 output(output&& other) noexcept;
710 output& operator=(output&& other) noexcept;
711
713 [[nodiscard]] enum channel channel() const noexcept { return _channel; }
714
716 [[nodiscard]] idfxx::gpio gpio() const noexcept { return _gpio; }
717
720
726 [[nodiscard]] float duty() const;
727
736
737#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
748#endif
749
762
763#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
774
786#endif
787
797
808
814 [[nodiscard]] std::chrono::nanoseconds pulse_width() const;
815
816#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
831 template<typename Rep, typename Period>
832 void set_pulse_width(const std::chrono::duration<Rep, Period>& width) {
834 }
835#endif
836
847 template<typename Rep, typename Period>
848 [[nodiscard]] result<void> try_set_pulse_width(const std::chrono::duration<Rep, Period>& width) {
849 return _try_set_pulse_width(std::chrono::ceil<std::chrono::nanoseconds>(width));
850 }
851
852#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
866 template<typename Rep, typename Period>
868 float target,
869 const std::chrono::duration<Rep, Period>& duration,
870 enum fade_mode mode = fade_mode::no_wait
871 ) {
872 unwrap(try_fade_to(target, duration, mode));
873 }
874#endif
875
891 template<typename Rep, typename Period>
893 float target,
894 const std::chrono::duration<Rep, Period>& duration,
895 enum fade_mode mode = fade_mode::no_wait
896 ) {
897 return _try_fade_to_duty(
898 static_cast<uint32_t>(std::clamp(target, 0.0f, 1.0f) * static_cast<float>(ticks_max())),
899 std::chrono::ceil<std::chrono::milliseconds>(duration),
900 mode
901 );
902 }
903
904#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
918 template<typename Rep, typename Period>
921 const std::chrono::duration<Rep, Period>& duration,
922 enum fade_mode mode = fade_mode::no_wait
923 ) {
924 unwrap(try_fade_to_duty_ticks(target_duty, duration, mode));
925 }
926#endif
927
943 template<typename Rep, typename Period>
946 const std::chrono::duration<Rep, Period>& duration,
947 enum fade_mode mode = fade_mode::no_wait
948 ) {
949 return _try_fade_to_duty(target_duty, std::chrono::ceil<std::chrono::milliseconds>(duration), mode);
950 }
951
952#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
968 template<typename Rep1, typename Period1, typename Rep2, typename Period2>
970 const std::chrono::duration<Rep1, Period1>& target_width,
971 const std::chrono::duration<Rep2, Period2>& duration,
972 enum fade_mode mode = fade_mode::no_wait
973 ) {
975 }
976#endif
977
995 template<typename Rep1, typename Period1, typename Rep2, typename Period2>
997 const std::chrono::duration<Rep1, Period1>& target_width,
998 const std::chrono::duration<Rep2, Period2>& duration,
999 enum fade_mode mode = fade_mode::no_wait
1000 ) {
1001 return _try_fade_to_pulse_width(
1002 std::chrono::ceil<std::chrono::nanoseconds>(target_width),
1003 std::chrono::ceil<std::chrono::milliseconds>(duration),
1004 mode
1005 );
1006 }
1007
1008#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
1021 void
1025#endif
1026
1045 enum fade_mode mode = fade_mode::no_wait
1046 );
1047
1048#if SOC_LEDC_SUPPORT_FADE_STOP
1049#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
1058 void fade_stop() { unwrap(try_fade_stop()); }
1059#endif
1060
1069#endif
1070
1071#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
1083 static void
1087#endif
1088
1101 [[nodiscard]] static result<void>
1103
1110
1111#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
1120#endif
1121
1129
1140
1144
1148private:
1150
1152
1155
1156 [[nodiscard]] result<void> _try_set_pulse_width(std::chrono::nanoseconds width);
1157
1158 [[nodiscard]] result<void> _try_fade_to_pulse_width(
1159 std::chrono::nanoseconds target_width,
1160 std::chrono::milliseconds duration,
1161 enum fade_mode mode
1162 );
1163
1164 [[nodiscard]] result<void>
1165 _try_fade_to_duty(uint32_t target_duty, std::chrono::milliseconds duration, enum fade_mode mode);
1166
1167 void _cleanup() noexcept;
1168
1169 class timer _timer;
1170 enum channel _channel;
1171 idfxx::gpio _gpio;
1172 std::optional<uint32_t> _gen;
1173};
1174
1175#ifdef CONFIG_COMPILER_CXX_EXCEPTIONS
1177 return unwrap(try_start(gpio, tmr, ch));
1178}
1180 return unwrap(try_start(gpio, tmr, ch, cfg));
1181}
1183 return unwrap(try_start(gpio, cfg));
1184}
1189 unwrap(try_stop(ch, mode, idle_level));
1190}
1191#endif
1192
// end of idfxx_pwm
1194
1195} // namespace idfxx::pwm
1196
1197namespace idfxx {
1198
1206[[nodiscard]] inline std::string to_string(const pwm::timer& t) {
1207 std::string s;
1208#if SOC_LEDC_SUPPORT_HS_MODE
1209 if (t.speed_mode() == pwm::speed_mode::high_speed) {
1210 s = "PWM_HS_TIMER_";
1211 } else {
1212 s = "PWM_TIMER_";
1213 }
1214#else
1215 s = "PWM_TIMER_";
1216#endif
1217 s += std::to_string(t.idf_num());
1218 return s;
1219}
1220
1228[[nodiscard]] inline std::string to_string(pwm::channel ch) {
1229 return "PWM_CH_" + std::to_string(std::to_underlying(ch));
1230}
1231
1239[[nodiscard]] inline std::string to_string(pwm::speed_mode m) {
1240 switch (m) {
1241 case pwm::speed_mode::low_speed:
1242 return "low_speed";
1243#if SOC_LEDC_SUPPORT_HS_MODE
1244 case pwm::speed_mode::high_speed:
1245 return "high_speed";
1246#endif
1247 default:
1248 return "unknown(" + std::to_string(std::to_underlying(m)) + ")";
1249 }
1250}
1251
1252} // namespace idfxx
1253
1254#include "sdkconfig.h"
1255#ifdef CONFIG_IDFXX_STD_FORMAT
1257#include <format>
1258namespace std {
1259template<>
1260struct formatter<idfxx::pwm::timer> {
1261 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1262
1263 template<typename FormatContext>
1264 auto format(const idfxx::pwm::timer& t, FormatContext& ctx) const {
1265 auto s = idfxx::to_string(t);
1266 return std::copy(s.begin(), s.end(), ctx.out());
1267 }
1268};
1269template<>
1270struct formatter<idfxx::pwm::channel> {
1271 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1272
1273 template<typename FormatContext>
1274 auto format(idfxx::pwm::channel ch, FormatContext& ctx) const {
1275 auto s = idfxx::to_string(ch);
1276 return std::copy(s.begin(), s.end(), ctx.out());
1277 }
1278};
1279template<>
1280struct formatter<idfxx::pwm::speed_mode> {
1281 constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
1282
1283 template<typename FormatContext>
1284 auto format(idfxx::pwm::speed_mode m, FormatContext& ctx) const {
1285 auto s = idfxx::to_string(m);
1286 return std::copy(s.begin(), s.end(), ctx.out());
1287 }
1288};
1289} // namespace std
1291#endif // CONFIG_IDFXX_STD_FORMAT
A GPIO pin.
Definition gpio.hpp:61
level
GPIO output/input level.
Definition gpio.hpp:67
@ low
Logic low (0)
An active PWM output binding a timer, channel, and GPIO pin.
Definition pwm.hpp:698
result< void > try_fade_to(float target, const std::chrono::duration< Rep, Period > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target duty ratio over the specified duration.
Definition pwm.hpp:892
friend result< output > try_start(idfxx::gpio, const timer::config &, const output_config &)
Starts PWM output with automatic allocation and custom output configuration.
enum channel release() noexcept
Releases ownership of the channel without stopping the PWM output.
static result< void > try_install_fade_service(idfxx::intr_levels levels=intr_level_lowmed, idfxx::flags< intr_flag > flags={})
Installs the PWM fade service.
result< void > try_fade_to_duty_ticks(uint32_t target_duty, const std::chrono::duration< Rep, Period > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target duty in ticks over the specified duration.
Definition pwm.hpp:944
void fade_to_duty_ticks(uint32_t target_duty, const std::chrono::duration< Rep, Period > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target duty in ticks over the specified duration.
Definition pwm.hpp:919
result< void > try_set_pulse_width(const std::chrono::duration< Rep, Period > &width)
Sets the PWM duty cycle as a pulse width duration.
Definition pwm.hpp:848
result< void > try_set_duty_ticks(uint32_t duty)
Sets the PWM duty cycle in ticks.
void set_duty_ticks(uint32_t duty)
Sets the PWM duty cycle in ticks.
Definition pwm.hpp:773
void fade_with_step(uint32_t target_duty, uint32_t scale, uint32_t cycle_num, enum fade_mode mode=fade_mode::no_wait)
Starts a fade to a target duty in ticks with step control.
Definition pwm.hpp:1022
uint32_t ticks_max() const noexcept
Returns the maximum duty ticks for the configured resolution.
result< void > try_stop(idfxx::gpio::level idle_level=idfxx::gpio::level::low)
Stops PWM output and sets the GPIO to an idle level.
result< void > try_set_duty(float duty)
Sets the PWM duty cycle as a ratio.
output(output &&other) noexcept
float duty() const
Returns the current duty cycle as a ratio.
output & operator=(output &&other) noexcept
~output()
Destroys the output, stopping PWM on the bound GPIO.
output(const output &)=delete
result< void > try_fade_with_step(uint32_t target_duty, uint32_t scale, uint32_t cycle_num, enum fade_mode mode=fade_mode::no_wait)
Starts a fade to the target duty with step control.
void stop(idfxx::gpio::level idle_level=idfxx::gpio::level::low)
Stops PWM output and sets the GPIO to an idle level.
Definition pwm.hpp:1119
result< void > try_fade_to_pulse_width(const std::chrono::duration< Rep1, Period1 > &target_width, const std::chrono::duration< Rep2, Period2 > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target pulse width over the specified duration.
Definition pwm.hpp:996
static void install_fade_service(idfxx::intr_levels levels=intr_level_lowmed, idfxx::flags< intr_flag > flags={})
Installs the PWM fade service.
Definition pwm.hpp:1084
void fade_to_pulse_width(const std::chrono::duration< Rep1, Period1 > &target_width, const std::chrono::duration< Rep2, Period2 > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target pulse width over the specified duration.
Definition pwm.hpp:969
std::chrono::nanoseconds pulse_width() const
Returns the current pulse width as a duration.
result< void > try_set_duty_ticks(uint32_t duty, uint32_t hpoint)
Sets the PWM duty cycle in ticks with high point.
enum channel channel() const noexcept
Returns the channel slot used by this output.
Definition pwm.hpp:713
idfxx::gpio gpio() const noexcept
Returns the GPIO pin used by this output.
Definition pwm.hpp:716
static void uninstall_fade_service()
Uninstalls the PWM fade service.
output & operator=(const output &)=delete
void set_pulse_width(const std::chrono::duration< Rep, Period > &width)
Sets the PWM duty cycle as a pulse width duration.
Definition pwm.hpp:832
void set_duty(float duty)
Sets the PWM duty cycle as a ratio.
Definition pwm.hpp:747
void set_duty_ticks(uint32_t duty, uint32_t hpoint)
Sets the PWM duty cycle in ticks with high point.
Definition pwm.hpp:785
uint32_t duty_ticks() const
Returns the current duty cycle in ticks.
void fade_to(float target, const std::chrono::duration< Rep, Period > &duration, enum fade_mode mode=fade_mode::no_wait)
Fades to a target duty ratio over the specified duration.
Definition pwm.hpp:867
A lightweight identifier for a hardware PWM timer.
Definition pwm.hpp:155
void configure(const struct config &cfg)
Configures the timer with the given parameters.
Definition pwm.hpp:202
result< void > try_configure(const struct config &cfg)
Configures the timer with the given parameters.
timer(timer &&)=default
uint32_t ticks_max() const noexcept
Returns the maximum duty ticks for the configured resolution.
uint8_t resolution_bits() const noexcept
Returns the configured resolution in bits.
timer & operator=(timer &&)=default
friend result< output > try_start(idfxx::gpio, const config &, const output_config &)
Starts PWM output with automatic allocation and custom output configuration.
freq::hertz frequency() const
Returns the current timer frequency.
result< void > try_set_frequency(freq::hertz frequency)
Changes the timer frequency.
friend struct timer_constant
Definition pwm.hpp:157
result< void > try_configure(freq::hertz frequency, uint8_t resolution_bits=13)
Configures the timer with frequency and resolution.
Definition pwm.hpp:242
constexpr enum speed_mode speed_mode() const noexcept
Returns the speed mode.
Definition pwm.hpp:184
void resume()
Resumes the timer counter.
Definition pwm.hpp:398
timer(const timer &)=default
std::chrono::nanoseconds tick_period() const
Returns the duration of a single timer tick.
timer & operator=(const timer &)=default
constexpr bool operator==(const timer &other) const =default
std::chrono::nanoseconds period() const
Returns the PWM period (1 / frequency) as a duration.
void set_period(const std::chrono::duration< Rep, Period > &period)
Changes the timer period.
Definition pwm.hpp:347
result< void > try_pause()
Pauses the timer counter.
result< void > try_resume()
Resumes the timer counter.
void pause()
Pauses the timer counter.
Definition pwm.hpp:381
void reset()
Resets the timer counter.
Definition pwm.hpp:415
void configure(freq::hertz frequency, uint8_t resolution_bits=13)
Configures the timer with frequency and resolution.
Definition pwm.hpp:214
result< void > try_reset()
Resets the timer counter.
void set_frequency(freq::hertz frequency)
Changes the timer frequency.
Definition pwm.hpp:313
result< void > try_set_period(const std::chrono::duration< Rep, Period > &period)
Changes the timer period.
Definition pwm.hpp:366
bool is_configured() const noexcept
Returns true if the timer has been configured.
friend std::optional< timer > get_timer(enum channel, enum speed_mode)
Returns the timer associated with an active channel, if any.
High-resolution timer with microsecond precision.
Definition timer.hpp:41
std::string to_string(core_id c)
Returns a string representation of a CPU core identifier.
Definition cpu.hpp:52
constexpr timer timer_2
Definition pwm.hpp:452
constexpr timer timer_1
Definition pwm.hpp:451
constexpr timer timer_0
Definition pwm.hpp:450
constexpr timer timer_3
Definition pwm.hpp:453
result< output > try_start(idfxx::gpio gpio, const timer &tmr, enum channel ch)
Starts PWM output on a GPIO pin.
sleep_mode
Channel behavior during light sleep.
Definition pwm.hpp:113
bool is_active(enum channel ch, enum speed_mode mode=speed_mode::low_speed)
Returns true if the specified channel currently has an active output.
speed_mode
PWM speed mode selection.
Definition pwm.hpp:48
void stop(enum channel ch, enum speed_mode mode=speed_mode::low_speed, idfxx::gpio::level idle_level=idfxx::gpio::level::low)
Stops PWM output on a channel and sets it to an idle level.
Definition pwm.hpp:1188
channel
PWM channel slot identifiers.
Definition pwm.hpp:62
output start(idfxx::gpio gpio, const timer &tmr, enum channel ch)
Starts PWM output on a GPIO pin.
Definition pwm.hpp:1176
fade_mode
Fade operation blocking mode.
Definition pwm.hpp:104
result< void > try_stop(enum channel ch, enum speed_mode mode=speed_mode::low_speed, idfxx::gpio::level idle_level=idfxx::gpio::level::low)
Stops PWM output on a channel and sets it to an idle level.
std::optional< timer > get_timer(enum channel ch, enum speed_mode mode=speed_mode::low_speed)
Returns the timer associated with an active channel, if any.
clk_source
PWM timer clock source.
Definition pwm.hpp:84
@ no_alive_no_pd
No output, keep power domain on (default)
@ keep_alive
Maintain PWM output during light sleep.
@ no_alive_allow_pd
No output, allow power domain off (saves power)
@ low_speed
Low-speed mode (available on all targets)
@ no_wait
Return immediately, fade runs in background.
@ wait_done
Block until the fade completes.
@ auto_select
Automatic clock source selection.
constexpr std::unexpected< std::error_code > error(E e) noexcept
Creates an unexpected error from an error code enum.
Definition error.hpp:187
T unwrap(result< T > result)
Throws a std::system_error if the result is an error.
Definition error.hpp:307
@ invalid_arg
Invalid argument.
std::expected< T, std::error_code > result
result type wrapping a value or error code.
Definition error.hpp:120
constexpr intr_levels intr_level_lowmed
Low and medium priority levels (1-3). These can be handled in C / C++.
Output configuration parameters.
Definition pwm.hpp:471
bool output_invert
Invert the output signal.
Definition pwm.hpp:474
float duty
Initial duty cycle ratio (0.0 to 1.0).
Definition pwm.hpp:472
int hpoint
High point in the PWM cycle.
Definition pwm.hpp:473
Timer configuration parameters.
Definition pwm.hpp:165
uint8_t resolution_bits
Duty resolution in bits (1 to SOC_LEDC_TIMER_BIT_WIDTH).
Definition pwm.hpp:167
freq::hertz frequency
PWM frequency.
Definition pwm.hpp:166