Conversation
The PM1 datasheet defines PWR_SRC as independent 5VIN, 5VINOUT, and battery-valid bits that may be set simultaneously. Return that bitmap directly instead of imposing an undocumented primary-source priority, and update the PaperMono charging check to test the external-power bits.
PM1: expose PWR_SRC as a bitmap
getBatteryCurrent() documents "+ = charge / - = discharge", but on the Tab5 the INA226 shunt is wired so that charging reads negative. Verified on real hardware: with a discharged pack under USB power (voltage rising, charge status asserted) the API returned about -650 mA. Invert the sign on the Tab5 path to match the documented convention.
Tab5: fix the battery current sign
The PM1 exposes two PWM channels, on GPIO3 and GPIO4. Follow the naming and the units of the standalone M5PM1 driver so that code can move between the two without surprises: setPwmDuty takes percent, setPwmDuty12bit takes the raw 12-bit value, and the two boolean arguments are ordered polarity then enable. The return value is bool for the I2C result, matching the rest of this class. Both channels share a single frequency register, so changing it affects a channel that is already running as well.
The ToughC5 buzzer is driven by PM1 PWM channel 1, so GPIO4 has to be set up for that function before an application can drive it at all. The PM1 keeps running across ESP resets and retains its PWM state, so the channel is also put off at boot, the same way the vibration motor is on the StopWatch. GPIO4 is normalized in an order that does not drive the buzzer on the way: the output latch is cleared before the pin becomes an output, and the PWM function is selected last. Selecting that function is also what would make a retained duty audible again, so it is only done once the channel is confirmed off. The write is retried a few times, and if it still cannot be confirmed the pin is left as a plain output driving low, which is silent whatever the retained PWM state is. Failing closed loses the buzzer until the next boot, which is the better direction for a step whose purpose is to guarantee silence. Stopping the channel on the way into sleep is deliberately left to the application. The PM1 stays powered while the ESP sleeps, so the application may intend the PWM output to remain active; whether to stop it is application policy rather than board initialization.
Add PM1 PWM control and use it for the ToughC5 buzzer
The pull resistor state was split across two virtuals: setPullMode picked up or down, and enablePull turned pulling on or off. What that pair means differs per expander. On the M5IOE1 the two share one register pair, so setPullMode alone already established the state and enablePull(pin, true) only set the pull-up bit without clearing the pull-down one, which could leave both enabled at once. On the PI4IOE5V6408 the registers are separate and the pair genuinely has to be used together, yet nothing in this repository ever enabled it: the pull-ups in use rely on the reset default of the enable register. setPullMode now takes gpio_pull_t and establishes the whole state in one call, so neither the ordering nor a reset default matters. The values match the standalone M5IOE1 driver constants. Writes are ordered so that two pulls are never enabled at the same time, and the enable register is now written explicitly on the PI4IOE5V6408 rather than assumed. The setter returns whether the requested state was fully established, since it takes more than one I2C write and a partial failure is what would leave a pin in the state this change is meant to rule out. The other virtuals in IOExpander_Base still return void. BREAKING: enablePull is gone and setPullMode takes an enum instead of a bool. Old calls do not compile, because neither bool nor an integer converts to the enum implicitly. | Old | New | | --- | --- | | enablePull(pin, false) | setPullMode(pin, pull_none) | | setPullMode(pin, true), with pulling enabled | setPullMode(pin, pull_up) | | setPullMode(pin, false), with pulling enabled | setPullMode(pin, pull_down) |
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.
Replace the IO expander pull enable API with explicit modes
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.
Give the PWM duty APIs explicit units and leave setPwmDuty vacant
Return the underlying I2C write status from setPwmFrequency and document that the configured frequency is shared by every PWM channel. BREAKING: the setPwmFrequency return type changes from void to bool. Ordinary calls that discard the result remain source-compatible, but code that depends on the exact member-function type must update from void (M5IOE1_Class::*)(uint16_t) to bool (M5IOE1_Class::*)(uint16_t).
Report M5IOE1 PWM frequency write results
Enable PM1 external power output(MBUS) for CoreMatrix.
Harden PWM initialization failure handling
Flush the final partial speaker buffer
Use m5gfx::delay for internal hardware waits
Return the write status from the IO expander virtuals
Fix PM1 button and wake IRQ handling and correct the power API docs
The helper that decides which board this is did not speak I2C. It made no start condition, clocked every address bit twice over twenty pulses, and read the ninth bit a millisecond after the clock had gone back down, when any device that acknowledged had long released the line. What came back was the pull-up on the pins, not an answer from the address, and the caller read that value as the device being present. Every board on the branch has pull-ups on its internal bus, so the first condition matched on all of them. AtomS3RExt is checked first and Atom VoiceS3R, which carries an ES8311 where the other carries a BMI270, was reported as AtomS3RExt. Send the address over the software I2C port instead, which drives the lines open drain, keeps away from the peripheral, and reports the acknowledge it actually receives. The return value now means what the five call sites always read it as. A stop is offered first, because a device left mid transfer needs one to let go and one board in this same file already records a device that needed exactly that; it is made by driving low and releasing, never by driving high, so a device holding a line is not fought over. Pins that carry no pulled up bus are left alone, the same test the display autodetection uses on pins that may not be I2C at all. A single retry covers a device that is slower to answer than the old test, which never waited for an answer in the first place. The name says what it does now: the same name in M5GFX belongs to a different function with a different contract.
The three boards that share the internal bus were separated by a probe on another pair of pins in between: BMI270, then the Stamp-S3Bat address on 48 and 47, then the ES8311. Atom VoiceS3R drives its speaker out of GPIO48, so a board fully identified by its own bus reached for the I2S data line before it got there. Take the internal bus first and only step off it when nothing there answered. Within that bus the BMI270 is asked first. The AtomS3R-Ext and the AtomS3RCam are the same board, and on the Ext the camera end is a small breadboard the owner is free to wire up, so an address that is not the BMI270 can appear on the internal bus of a board that is one. The sensor that is always fitted decides, and something added later cannot take the identity away. Measured on hardware: Atom VoiceS3R answers at 0x18 alone, AtomS3RExt and both AtomS3RCam variants at 0x68 alone, and StampS3Mini has a pull-up on one line of the pair and nothing on the other.
The probe held the pins low nine times over before it had established that they carry an I2C bus at all: the stop preamble ran first, and only then came the test for a pull-up. These pins are how the board is identified, so until that test passes they may be anything. Run the test first and offer the stop only once the pins have answered for themselves. Take the second software I2C slot rather than the one the display autodetection uses. The slots carry no ownership - opening one takes over whatever settings were there - so two libraries sharing a slot rests on nothing but the order they happen to run in. Drop the retry. Nothing between the two attempts changes the state of the device being asked: no reset, no power, no clock. It doubled the time spent on every address that is not answering, which the boards with nothing on their internal bus pay three times over.
library.json and idf_component.yml already ask for it; the Arduino manifest asked for M5GFX with no version at all. An older one has no software I2C behind the negative port numbers, and the port number reaches the hardware port table as an index instead.
Checking for the pull-up before offering a stop condition left the one bus that most needs the stop unable to get it. A device interrupted mid read holds the data line down, and a software reset does not take its power away, so the line is still held when the board is identified again. The check reads that as no bus and returns before the stop is ever made. The signature is specific enough to act on: the clock comes back high against the internal pull-down while the data line stays low. That is a held bus, not a pin without a pull-up, where neither line comes back. Offer the stop in that case and look again; every other answer returns as it did.
Every probe waited 50ms before it touched the pins. The old probe always answered on the first address, so that wait was paid once; now that an address which is not there says so, it is paid again for every address that misses - four times over on a board that answers nothing on its internal bus. The wait is about how long ago the board was powered, not about the address being asked, so it belongs at the entrance to the board check. Measured on an AtomS3RCam: one probe went from 49.8ms to 0.3ms, and the 50ms is now spent once for the whole identification.
The bus test set both pins to output while their latches were still high from the pull-up mode before it, so each pin drove a push-pull high for the moment before the test pulled it low. On pins that may not be an I2C bus at all - which is the whole point of the test - that is a driven high into whatever is on the other side. Put the latch low first. The lines are still driven low by the test after that, but a high is never driven at all.
Board detection: probe the address instead of the pull-up (#330)
Both setChargeVoltage implementations subtracted a bias from the argument before comparing it against their step table. The argument is unsigned, so a request below the lowest step wrapped around and then clamped to the opposite end of the table: on the AXP192 a request for 4.0V configured 4.36V, and on the AXP2101 the same request selected the reserved code 0. Asking for a gentler charge voltage gave a harsher one. The AXP2101 table also carried a 4.6V step that the part does not have - its constant-voltage register goes up to 4.4V - so a request that reached that entry wrapped the index back onto the reserved code as well. Compare in millivolts against the real steps instead, take the highest step that does not exceed the request, and fall back to the lowest step when the request is under all of them. Every request inside the supported range keeps its previous result.
Review pointed out that the AXP2101 constant-voltage register is documented differently across datasheet revisions: the current ones reserve code 0 while an early one gave it to 4.6V. The change had asserted flatly that no step above 4.4V exists, which only holds for the revisions available now. State what every revision agrees on instead, and give the reason for holding a high request at 4.4V: no battery this library runs on charges to 4.6V, so the code whose meaning depends on the silicon is worth avoiding. Both headers now carry the supported steps and what happens on either side of them, which nothing stated before.
Add ESP32C5 RTC IRQ clear and unsupported-sleep log warnings
Tab5X upgrades the ESP32-P4 silicon revision used by Tab5. Tab5X EXT interface power is controllable.
Stop a low charge voltage request from raising the charge voltage
Add ToughC5 charge current control
Add M5Tab5X board support
The register document describes two flags for this part: REG_READ0 bit3 to tell charging from discharging, and REG_READ1 bit3 to tell whether the cell has already been filled. Only the first was read. It stays set once charging is enabled and a supply is present - the completed charge included - so a finished charge was reported as an ongoing one, and so was a board running with no cell installed. Read both. They sit at adjacent addresses, but the document only ever shows a single-byte read and never states that the address auto-increments, so they are read separately rather than resting on unspecified behaviour. Measured on a Core BASIC v2.7 across every branch: supply removed (70=16 -> false), charging (70=19 71=70 -> true), charge disabled (70=11 -> false), and charge complete (71=A8 -> false, where the earlier form stayed true). Behaviour change: isCharging() now returns false once the cell is full, where it used to stay true for as long as a supply was present. Callers that watched for the transition as a sign that external power was lost will see it at full charge instead; the library has no way to express "full" through this return type yet.
Stop reporting a finished charge as an ongoing one on the IP5306
- Recognize board ID 31 and add pin mappings for internal I2C (SCL G9, SDA G11), Port.A, TF card, and MBUS. - Add M5IOE1@0x4F and M5PM1@0x6E power management. - Add ES8311/ES7210 audio, BMI270 IMU, and RX8130 RTC support.
Add M5Stack CoreP4X board support
…e-arm sequence The RX8130 wakeup timer releases /IRQ automatically after tRTN2, which is only 122us with the 4096Hz source clock but 7.57ms with the other clocks (Table 16). setTimerIRQ() picked 4096Hz for every period below 16s, so boards whose RTC /IRQ is routed through a scanning PMIC or IO expander (e.g. the M5PM1 GPIO IRQ path) missed the interrupt for common requests such as 1s or 10s. Clock selection now tries 64Hz, 1Hz, 1/60Hz and 1/3600Hz in that order and takes the first whose rounded count keeps the period within 1/256 and has at least 16 counts (the first countdown can be short by up to one source clock - 1s for the two slowest clocks - so this bounds that error to ~6% or less); 4096Hz is used only for periods no other clock can represent (below ~2s). The arithmetic stays in 32 bits: each clock carries its 65535-count range so msec*div cannot overflow, and the period error is the division remainder. When a finer clock runs out of range the coarser one rounds up, so the returned period never steps backwards as the request grows. The re-arm sequence follows the datasheet example flow: TE=0 (+TSEL) -> clear TF -> TIE=1 -> preset -> TE=1 last, so the first event can never precede the interrupt enable. The old code rewrote the counter with TE still set, which the datasheet does not allow and which left the timer running with the stale preset. The preset is read back while TE=0 and rewritten if it does not match. Every access in the arming path is checked; on failure the timer is stopped (TE=0/TIE=0, verified by read-back, retried) and 0 is returned - the return value cannot distinguish that from a requested stop, which the API comment now states. Flags in 0x1D are write-0-to-clear, so TF (and AF in clearIRQ/disableIRQ) is cleared with a single constant write instead of a read-modify-write that could drop a flag raised in between. The returned period is rounded to the nearest ms and is never 0 while the timer runs.
Keep the RX8130 timer /IRQ pulse catchable and follow the datasheet re-arm sequence
Bump version to 0.2.21 and require M5GFX 0.2.28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
New device support
Board detection
reported as an AtomS3R-Ext (Atom VoiceS3R detected as wrong device #330)
Power management fixes
setChargeVoltageraising the charge voltage when a value below the loweststep was requested
sequence
PM1 / IO expander API
setPwmDutyis left vacantinitialization checks them
Audio
Internal
m5gfx::delayfor internal hardware waits so short delays are not truncated tothe tick period on ESP-IDF