Give the PWM duty APIs explicit units and leave setPwmDuty vacant - #321
Conversation
Separate percentage and raw 12-bit duty values in the method names, and use a shared polarity type so boolean arguments cannot be transposed silently. BREAKING: setPwmDuty is removed and polarity arguments now use pwm_polarity_t. | Old | New | | --- | --- | | setPwmDuty(channel, percent, polarity, enable) | setPwmDutyPercent(channel, percent, polarity, enable) | | setPwmDuty12bit(channel, raw, polarity, enable) | setPwmDuty12bit(channel, raw, polarity, enable) | The duty parameters widen to uint32_t so that the documented range is what gets checked, rather than whatever is left after the value has been narrowed at the call boundary. Existing in-range calls are unaffected. The unqualified setPwmDuty name is intentionally left vacant to eliminate silent semantic changes. This change does not define or promise any future use for that name.
There was a problem hiding this comment.
Pull request overview
This PR refactors the PWM duty APIs on M5IOE1_Class and M5PM1_Class to make duty units explicit (percent vs raw 12-bit) and to prevent accidental boolean-parameter transposition by introducing a shared m5::pwm_polarity_t type, while intentionally leaving setPwmDuty undefined so old call sites fail to compile.
Changes:
- Replaces ambiguous
setPwmDutywithsetPwmDutyPercent(0–100)andsetPwmDuty12bit(0–4095)on both classes, and switches polarity frombooltom5::pwm_polarity_t. - Tightens validation by rejecting out-of-range duty inputs rather than masking/truncating.
- Updates internal call sites to the new APIs and adds a shared PWM polarity type header.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utility/pwm_types.hpp | Adds shared m5::pwm_polarity_t enum for explicit PWM polarity. |
| src/utility/power/M5PM1_Class.hpp | Renames/reshapes PM1 PWM duty APIs to explicit units and polarity type. |
| src/utility/power/M5PM1_Class.cpp | Implements setPwmDutyPercent and updates polarity handling. |
| src/utility/M5IOE1_Class.hpp | Introduces explicit-unit PWM duty APIs and polarity type; changes PWM method signatures. |
| src/utility/M5IOE1_Class.cpp | Implements percent/12-bit duty setters with range checks and explicit polarity. |
| src/utility/Power_Class.cpp | Updates internal PWM call sites to setPwmDuty12bit + explicit polarity. |
| src/utility/led/LED_PaperMono_Class.cpp | Updates IOE1 PWM call to the new 12-bit API with explicit polarity. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| bool M5IOE1_Class::setPwmFrequency(std::uint16_t frequency) | ||
| { | ||
| std::uint8_t data[2] = { static_cast<std::uint8_t>(frequency & 0xFF), static_cast<std::uint8_t>(frequency >> 8) }; | ||
| writeRegister(M5IOE1_REG_PWM_FREQ_L, data, sizeof(data)); | ||
| return writeRegister(M5IOE1_REG_PWM_FREQ_L, data, sizeof(data)); | ||
| } |
There was a problem hiding this comment.
Fair point, and it is now split out. The setPwmFrequency change is gone from
this branch, so this pull request is only about the duty renames again and
setPwmFrequency keeps its void signature here.
It was intentional rather than accidental -- the same method on M5PM1_Class
returns bool, and leaving this one void keeps the two disagreeing -- but you
are right that it is a separate public API decision and that the description did
not cover it. Deciding it on its own is better than carrying it along, so it goes
in its own change where the exact compatibility story can be stated: ordinary
calls that discard the result stay source-compatible, while code that depends on
the exact member-function type has to move from
void (M5IOE1_Class::*)(uint16_t) to bool (M5IOE1_Class::*)(uint16_t).
| /// set the PWM frequency in Hz. | ||
| /// @note The frequency is shared by all PWM channels, so changing it also | ||
| /// changes a channel that is already running. | ||
| bool setPwmFrequency(std::uint16_t frequency); |
There was a problem hiding this comment.
Fair point, and it is now split out. The setPwmFrequency change is gone from
this branch, so this pull request is only about the duty renames again and
setPwmFrequency keeps its void signature here.
It was intentional rather than accidental -- the same method on M5PM1_Class
returns bool, and leaving this one void keeps the two disagreeing -- but you
are right that it is a separate public API decision and that the description did
not cover it. Deciding it on its own is better than carrying it along, so it goes
in its own change where the exact compatibility story can be stated: ordinary
calls that discard the result stay source-compatible, while code that depends on
the exact member-function type has to move from
void (M5IOE1_Class::*)(uint16_t) to bool (M5IOE1_Class::*)(uint16_t).
Separate percentage and raw 12-bit duty values in the method names, reject out-of-range inputs, and migrate existing raw-duty call sites without changing their output behavior. BREAKING: setPwmDuty is removed. Use setPwmDuty12bit for existing raw values or setPwmDutyPercent for percentages; polarity now uses pwm_polarity_t and precedes enable. | Old | New | | --- | --- | | setPwmDuty(channel, raw, enable, polarity) | setPwmDuty12bit(channel, raw, polarity, enable) | | No percentage API | setPwmDutyPercent(channel, percent, polarity, enable) | The unqualified setPwmDuty name is intentionally left vacant to eliminate silent semantic changes. This change does not define or promise any future use for that name.
220a702 to
5325b9a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/utility/M5IOE1_Class.hpp:71
- PR description says the breaking change is that
setPwmDutyno longer exists, but forM5IOE1_Classthe channel parameter also changes fromstd::uint8_tto thepwm_channel_tenum. This is an additional source-breaking change for callers that previously passed a numeric channel (even after renaming tosetPwmDuty12bit/setPwmDutyPercent). Either document this explicitly in the PR description / release notes, or consider adding a compatibility overload that acceptsstd::uint8_tand validates 0-3 (or 1-4 if that’s the intended external numbering).
/// set PWM duty in percent.
/// @param channel PWM channel (pwm_ch1 - pwm_ch4).
/// @param duty duty cycle in percent (0-100).
/// @param polarity PWM output polarity.
/// @param enable true=enable / false=disable.
bool setPwmDutyPercent(pwm_channel_t channel, std::uint32_t duty,
pwm_polarity_t polarity = pwm_polarity_t::normal, bool enable = true);
|
The suppressed comment on the channel parameter is correct, and the description The The While rewriting the section, the remaining breaking items are enumerated instead The commits are unchanged; the description is the only thing that moved. |
Supersedes #319, which tried to solve this by renaming the raw entry point and
giving
setPwmDutythe percent meaning the standalone drivers use. The reviewthere showed why that does not work, so this takes a different route.
Problem
M5IOE1_Class::setPwmDutytakes a raw 12-bit value, while the standaloneM5IOE1 and M5PM1 drivers use that name for a percentage and order the two
booleans the other way round. Reading their documentation and writing
setPwmDuty(ch, 50, false, true)against this class compiles and quietly means"duty 50 out of 4095, disabled" instead of "50 percent, enabled".
Giving
setPwmDutythe percent meaning here would not fix that, it would moveit: existing raw calls would keep compiling and change meaning. A range check
cannot catch it either, because a raw duty that happens to land in 0-100 is a
valid percentage.
Fix
The name is the problem, so it is not reused. Both classes now offer
setPwmDutyPercentfor 0-100setPwmDuty12bitfor 0-4095and
setPwmDutyis gone from both, so a call written against either old meaningfails to compile rather than changing behaviour.
Two smaller changes come with it:
The duty parameters are
uint32_t. The point of a documented 0-100 range isthat values outside it are rejected, and with a narrower parameter the check only
sees whatever survived the conversion at the call boundary. Existing in-range
calls are unaffected.
Polarity is
m5::pwm_polarity_trather than abool, so the two adjacentbooleans can no longer be transposed. Enable stays a
bool; with only one leftthere is nothing to transpose it with. The type lives in a small shared header
because both classes use it, matching how the other cross-class enums in this
library are placed, while the channel enums stay per class because the numbering
follows each chip.
M5PM1_Classis included even though its percent API only landed recently andmatches the standalone drivers, because leaving it behind would mean one class
saying
setPwmDutyPercentand the other sayingsetPwmDutyfor the same thing.The register layouts are untouched: the enable and polarity bit positions stay
the ones each chip uses.
Breaking changes
Each item below is a separate source-level break.
setPwmDutyis gone from both classes. Duty goes through aname that states its unit instead:
setPwmDutyPercentfor 0-100 orsetPwmDuty12bitfor 0-4095.M5IOE1_Classduty methods now requirepwm_channel_tinstead ofstd::uint8_t. The enumeratorspwm_ch1throughpwm_ch4retain theexisting underlying values 0 through 3, so correctly migrated calls select the
same channels. Callers that passed numeric channels must replace them with the
corresponding enumerator; no
std::uint8_tcompatibility overload is providedbecause it would also accept unrelated unscoped enums, and numeric literals
would prefer it, which together would defeat the channel type checking this
change is for.
m5::pwm_polarity_tinstead ofbool, and onM5IOE1_Classthelast two arguments change order from
(enable, polarity)to(polarity, enable), matchingM5PM1_Classand the standalone drivers. Aboolwritten in either position no longer compiles, so the reordering cannotpass silently.
std::uint32_t. In-range calls areunaffected; code that names the exact member-function type has to follow. This
applies to
M5PM1_Class::setPwmDuty12bittoo, which keeps its name.bool. An out-of-range duty and an I2C failure arenow reportable instead of being masked or discarded.
M5IOE1_Class::setPwmDuty(0, raw, enable, polarity)setPwmDuty12bit(pwm_ch1, raw, polarity, enable)M5PM1_Class::setPwmDuty(ch, percent, polarity, enable)setPwmDutyPercent(ch, percent, polarity, enable)boolpolarity argumentm5::pwm_polarity_t::normal/::invertedOnly
M5IOE1_Class::setPwmDutyis a released signature; it has been presentsince 0.2.19. The
M5PM1_ClassPWM methods were added after 0.2.20 and havenever been in a release, so the changes to those affect development builds only.
The
setPwmDutyname is left vacant deliberately. Nothing here defines orpromises a future use for it, but keeping it out of the way means it could be
reintroduced later without repeating the situation this change is fixing.
Intended for the next release, bumping the minor version to 0.3.0.
Verification
Built for ESP32-S3, ESP32-P4, ESP32-C5 and ESP32, and the first commit builds on
its own. Old calls were confirmed to fail with "no member named setPwmDuty" on
both classes, and the guards were checked at 0, 100 and 101 percent and at 0,
4095 and 4096 raw. The five internal call sites all pass raw values and keep
their previous output.