Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ description: Unified library for M5Stack series
issues: https://github.com/m5stack/M5Unified/issues
repository: https://github.com/m5stack/M5Unified.git
url: https://github.com/m5stack/M5Unified.git
version: 0.2.25
version: 0.2.26
dependencies:
m5stack/m5gfx:
version: ">=0.2.31"
2 changes: 1 addition & 1 deletion library.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"version": ">=0.2.31"
}
],
"version": "0.2.25",
"version": "0.2.26",
"frameworks": ["arduino", "espidf", "*"],
"platforms": ["espressif32", "native"],
"headers": "M5Unified.h"
Expand Down
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=M5Unified
version=0.2.25
version=0.2.26
author=M5Stack
maintainer=M5Stack
sentence=Unified library for M5Stack series
Expand Down
16 changes: 16 additions & 0 deletions src/M5Unified.inl
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,9 @@ static constexpr const uint8_t _pin_table_mbus[][31] = {
board = board_t::board_M5Capsule;
// 自動検出の際。PortAに余分な波形が出ているので、一度 I2C STOPコンディションを出しておく。
// ※ これをしないと正しく動作しないデバイスが存在した。UnitHEART MAX30100
// Release the Grove pins afterwards: left as push-pull outputs they keep driving High,
// and drivers that only enable the input (e.g. RMT RX on ESP-IDF 5.4+) cannot see the line.
m5gfx::gpio::pin_backup_t grove_backup[] = { GPIO_NUM_15, GPIO_NUM_13 };
m5gfx::gpio::command(
(const uint8_t[]) {
m5gfx::gpio::command_mode_output, GPIO_NUM_15,
Expand All @@ -1913,6 +1916,9 @@ static constexpr const uint8_t _pin_table_mbus[][31] = {
m5gfx::gpio::command_end
}
);
for (auto &backup : grove_backup) {
backup.restore();
}
}
}
for (auto &backup : pin_backup) {
Expand Down Expand Up @@ -2250,6 +2256,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = {
#if !defined (M5UNIFIED_PC_BUILD)
int led_count = 1;
int byte_per_led = 3;
int pin_power = -1;
switch (board)
{
#if defined (CONFIG_IDF_TARGET_ESP32S3)
Expand All @@ -2273,6 +2280,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = {
case board_t::board_M5PaperColor:
led_count = 2;
break;
#elif defined (CONFIG_IDF_TARGET_ESP32C6)
case board_t::board_M5NanoC6:
pin_power = GPIO_NUM_19; // RGB LED power switch
break;
#elif defined (CONFIG_IDF_TARGET_ESP32H2)
case board_t::board_M5NanoH2:
pin_power = GPIO_NUM_10; // RGB LED power switch
break;
#else
case board_t::board_M5AtomMatrix:
led_count = 25;
Expand All @@ -2288,6 +2303,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = {
auto busled = std::make_shared<m5::LedBus_RMT>();
auto buscfg = busled->getConfig();
buscfg.pin_data = pin_rgb_led;
buscfg.pin_power = pin_power;
busled->setConfig(buscfg);
auto led_strip = std::make_shared<m5::LED_Strip_Class>();
auto ledcfg = led_strip->getConfig();
Expand Down
2 changes: 1 addition & 1 deletion src/gitTagVersion.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#define M5UNIFIED_VERSION_MAJOR 0
#define M5UNIFIED_VERSION_MINOR 2
#define M5UNIFIED_VERSION_PATCH 25
#define M5UNIFIED_VERSION_PATCH 26
#define M5UNIFIED_VERSION F( M5UNIFIED_VERSION_MAJOR "." M5UNIFIED_VERSION_MINOR "." M5UNIFIED_VERSION_PATCH )
6 changes: 4 additions & 2 deletions src/utility/Power_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ namespace m5
/// @note On the CoreS3 family (CoreS3 / CoreS3 SE / StackChan), disabling an enabled output blocks for
/// about 200 ms (the boost converter is stopped first and the bus is left to discharge before the
/// switch-over), and enabling without a battery may block for up to 1 s while the protection check
/// waits for the TS reading to settle. The switch-over is serialized with setUsbOutput and the
/// waits for the TS reading to settle. Enabling also precharges the bus with short pulses first
/// (about 10 ms), so that the empty bus does not pull the PMIC under its DCDC under-voltage
/// threshold on a weak USB supply. The switch-over is serialized with setUsbOutput and the
/// internal speaker enable, so those may wait for it as well.
/// @note Core2 v1.1 (AXP2101; a Tough with the AXP2101 takes the same path): disabling
/// the output while powered from USB with no battery makes the ESP32 brownout and
Expand Down Expand Up @@ -180,7 +182,7 @@ namespace m5
/// set battery charge current
/// @param max_mA milli ampere.
/// @note CoreMatrix selects 180 mA below 650 mA, otherwise 650 mA.
/// @note ToughC5 selects 180 mA below 830 mA, otherwise 830 mA.
/// @note ToughC5 selects 180 mA below 650 mA, otherwise 650 mA.
/// @attention Non-functioning models : CoreInk , M5Paper , M5Stack(with non I2C IP5306)
void setChargeCurrent(std::uint16_t max_mA);

Expand Down
74 changes: 72 additions & 2 deletions src/utility/Power_Class.inl
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,14 @@ namespace m5
, 0x30, 0x0F // ADC enabled (for voltage measurement)
};
Axp2101.writeRegister8Array(reg_data_array, sizeof(reg_data_array));
// TS senses the 5V bus (no battery thermistor). Its power-on default comes from the
// EFUSE and differs between chips; when it is left as the temperature input, the
// charger treats the bus voltage as a cold battery and never charges.
// The charge voltage (0x64) is not always at the datasheet default either: a unit read 0x00,
// which is reserved (4.6V in an early datasheet revision). The cell is a 4.2V type.
// Set it before the TS change below lets the charger start.
Axp2101.setChargeVoltage(4200);
Axp2101.bitOn(0x50, 0x10); // TS pin: external input, does not affect the charger
// The touch INT is routed to the ESP32 as TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN
// -> I2C_INT -> GPIO21. The power key is wired to the AXP2101 PWRON only, and the
// AXP2101 IRQ pin is shared with the RTC INT, so neither reaches the ESP32.
Expand Down Expand Up @@ -863,6 +871,10 @@ namespace m5
// setExtOutput) makes the AXP2101 power itself off, and it stays off until the power
// key; let the ESP32 brown out and restart instead. Over-voltage (bit 5) stays enabled.
Axp2101.bitOff(0x23, 0x1F); // DCDC1-5 UVP power-off disable
// Charge voltage: the power-on value may be reserved; see the CoreS3 setup.
Axp2101.setChargeVoltage(4200);
// No battery thermistor on TS (reads about 2.9V); see the CoreS3 setup.
Axp2101.bitOn(0x50, 0x10); // TS pin: external input, does not affect the charger

// for Core2 v1.1 (AXP2101+INA3221)
if (Ina3221[0].begin())
Expand Down Expand Up @@ -908,6 +920,50 @@ namespace m5
~_core_s3_lock_t(void) { if (locked) { xSemaphoreGive(mutex); } }
};

// Before BUS_OUT_EN goes 0 -> 1, precharge BUS_OUT with short pulses of growing on-time.
// Connecting the empty BUS_OUT at once on a weak USB supply pulls the AXP2101 DCDC under
// its threshold through the inrush, and the PMIC powers the board off.
// BUS_OUT has a constant discharge path, so short fixed pulses do not accumulate. Once it
// passes about 1.7 V it is pulled up from VBUS even with BUS_OUT_EN = 0, and the final
// enable is a small step. Each on-time is one register write (about 0.3 ms at 100 kHz)
// plus i * 16 us; M5GFX's board detection runs the same sequence at 100 kHz.
// After a failed on- or off-pulse write, OFF is rewritten and read back up to three times:
// confirmed off aborts a failed on-pulse or continues the ramp after a failed off-pulse,
// still on goes to the final enable, and an unknown state returns false.
static bool _core_s3_bus_precharge(uint8_t port0_off)
{
static constexpr const uint8_t port0_reg = 0x02;
enum class off_state_t { unknown, off, on };
auto confirm_off = [&]() {
uint8_t cur = 0;
bool read_ok = false;
for (int retry = 0; retry < 3; ++retry)
{
M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off, i2c_freq);
read_ok = M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, &cur, 1, i2c_freq);
if (read_ok && !(cur & _core_s3_bus_en)) { return off_state_t::off; }
}
return read_ok ? off_state_t::on : off_state_t::unknown;
};
for (int i = 0; i < 8; ++i)
{
if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off | _core_s3_bus_en, i2c_freq))
{
if (confirm_off() == off_state_t::on) { break; }
return false;
}
m5gfx::delayMicroseconds(i * 16);
if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off, i2c_freq))
{
const auto state = confirm_off();
if (state == off_state_t::unknown) { return false; }
if (state == off_state_t::on) { break; }
}
m5gfx::delayMicroseconds(1000);
}
return true;
}

static void _core_s3_output_locked(uint8_t mask, bool enable)
{
static constexpr const uint8_t port0_reg = 0x02;
Expand Down Expand Up @@ -974,6 +1030,20 @@ namespace m5
if (enable)
{
if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq)) { return; }
if ((mask & _core_s3_bus_en) && !(orig[0] & _core_s3_bus_en))
{
if (!_core_s3_bus_precharge(orig[0])) { return; }
// A precharged BUS_OUT left with BUS_OUT_EN = 0 is pulled up from VBUS, and every later
// enable is then cancelled by the TS check; read the final write back, bounded retries.
for (int retry = 0; retry < 3; ++retry)
{
uint8_t cur;
if (M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq)
&& M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, &cur, 1, i2c_freq)
&& cur == buf[0]) { return; }
}
return;
}
M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq);
}
else
Expand Down Expand Up @@ -2646,11 +2716,11 @@ namespace m5
case pmic_t::pmic_m5pm1:
if (M5.getBoard() == board_t::board_M5ToughC5)
{
// ToughC5 CHG_PROG is IOE1 G1: low selects 830 mA, high selects 180 mA.
// ToughC5 CHG_PROG is IOE1 G1: low selects 650 mA, high selects 180 mA.
// Set the latch before enabling push-pull output to avoid a transient
// selection of the opposite current during the mode transition.
auto& ioe1 = M5.getIOExpander(0);
const bool select_180mA = max_mA < 830;
const bool select_180mA = max_mA < 650;
ioe1.setPullMode(M5IOE1_Class::gpio1, IOExpander_Base::pull_none);
ioe1.digitalWrite(M5IOE1_Class::gpio1, select_180mA);
ioe1.setHighImpedance(M5IOE1_Class::gpio1, false);
Expand Down
18 changes: 16 additions & 2 deletions src/utility/Speaker_Class.inl
Original file line number Diff line number Diff line change
Expand Up @@ -574,8 +574,22 @@ namespace m5
if (buf_cnt)
{ // (no data... wait for new data)
--buf_cnt;
uint32_t wait_msec = 1 + (self->_cfg.dma_buf_len / (spk_sample_rate_x256 >> 17));
flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, wait_msec ));
if (self->_cfg.buzzer)
{
uint32_t wait_msec = 1 + (self->_cfg.dma_buf_len / (spk_sample_rate_x256 >> 17));
flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, wait_msec ));
}
else
{ // Keep the DMA fed at the hold level instead of sleeping. A timed wait can let the
// DMA reach the auto-cleared buffers, which output 0 (on the DAC, a drop to the zero
// level and a jump back to the bias). The blocking write paces this loop.
uint32_t hold = self->_cfg.use_dac ? (uint32_t)(dac_offset | dac_offset << 16) : 0u;
size_t words = dma_buf_len >> 1;
for (size_t i = 0; i < words; ++i) { sound_buf32[i] = hold; }
size_t write_bytes;
_spk_i2s_write(i2s_port, sound_buf32, words * sizeof(int32_t), &write_bytes, portMAX_DELAY);
flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, 0 ));
}
}

if (flg_nodata && 0 == buf_cnt)
Expand Down
3 changes: 3 additions & 0 deletions src/utility/led/LED_Strip_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ namespace m5
uint16_t t1l_ns = 300;
uint16_t reset_us = 280;
int8_t pin_data = -1;
/// GPIO that switches the LED power supply on (active high); -1 if the LEDs are always powered.
/// It is driven at init(), i.e. when the LEDs are first used, so an unused LED stays unpowered.
int8_t pin_power = -1;
};
const config_t& config(void) const { return _config; }
const config_t& getConfig(void) const { return _config; }
Expand Down
12 changes: 12 additions & 0 deletions src/utility/led/LED_Strip_Class.inl
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ namespace m5
// The RMT driver does not clear a stale open-drain pad flag. (ESP-IDF v6 removed io_od_mode)
m5gfx::gpio_lo(_config.pin_data);
m5gfx::pinMode(_config.pin_data, m5gfx::pin_mode_t::output);
if (_config.pin_power >= 0)
{
m5gfx::gpio_hi(_config.pin_power);
m5gfx::pinMode(_config.pin_power, m5gfx::pin_mode_t::output);
// A frame sent right after switching the supply on is lost (measured on NanoC6 / NanoH2:
// 0 ms fails, 1 ms works); give the LEDs a few ms to come up.
m5gfx::delay(5);
}

rmt_tx_channel_config_t rmt_tx;
memset(&rmt_tx, 0, sizeof(rmt_tx));
Expand Down Expand Up @@ -174,6 +182,10 @@ namespace m5
}
void LedBus_RMT::release(void)
{
if (_config.pin_power >= 0)
{
m5gfx::gpio_lo(_config.pin_power);
}
#if M5UNIFIED_RMT_VERSION == 2
auto rmt_ch_handle = _rmt_ch_handle;
auto led_encoder = _led_encoder;
Expand Down
Loading