Skip to content

Commit d22effc

Browse files
committed
Fix legacy I2C clock handling and driver borrow detection on ESP-IDF 5.0/5.1
1 parent 1eb0b0d commit d22effc

2 files changed

Lines changed: 115 additions & 32 deletions

File tree

src/m5_unit_component/adapter_i2c.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,13 @@ AdapterI2C::ESPIDFLegacyBusImpl::ESPIDFLegacyBusImpl(const i2c_port_t port, cons
529529
const uint8_t addr, const uint32_t clock)
530530
: AdapterI2C::I2CImpl(addr, clock), _port(port), _sda(static_cast<int16_t>(sda)), _scl(static_cast<int16_t>(scl))
531531
{
532+
// The legacy driver has no per-device clock (unlike the IDF >= 5.2 master driver, which applies
533+
// each device's scl_speed_hz per transfer). The whole port shares one timing register set, so
534+
// every consumer must re-assert its own clock before each transfer. Compute this unit's period
535+
// once here (the driver is already installed by the wiring helper); transactions then re-apply it
536+
// via i2c_set_period. Each unit caches its own _high/_low, so units with different clocks on the
537+
// same port do not clobber each other.
538+
apply_clock();
532539
}
533540

534541
void AdapterI2C::ESPIDFLegacyBusImpl::apply_clock()
@@ -540,9 +547,20 @@ void AdapterI2C::ESPIDFLegacyBusImpl::apply_clock()
540547
conf.sda_pullup_en = GPIO_PULLUP_ENABLE;
541548
conf.scl_pullup_en = GPIO_PULLUP_ENABLE;
542549
conf.master.clk_speed = _clock;
550+
// i2c_param_config applies timing to the HAL even without the driver installed, but
551+
// i2c_get_period returns ESP_FAIL (without writing) when the driver object is absent.
552+
// Mark the cached period invalid (0) when it cannot be read back; transactions then skip
553+
// i2c_set_period and rely on the timing param_config just applied.
554+
_high = 0;
555+
_low = 0;
543556
if (i2c_param_config(_port, &conf) == ESP_OK) {
544-
i2c_get_period(_port, &_high, &_low);
557+
if (i2c_get_period(_port, &_high, &_low) != ESP_OK) {
558+
_high = 0;
559+
_low = 0;
560+
}
545561
}
562+
M5_LIB_LOGI("apply_clock port=%d clock=%u high=%d low=%d%s", (int)_port, (unsigned)_clock, _high, _low,
563+
(_high > 0 && _low > 0) ? "" : " (period unreadable; using param_config timing)");
546564
}
547565

548566
bool AdapterI2C::ESPIDFLegacyBusImpl::begin()
@@ -571,7 +589,9 @@ m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::readWithTransaction(uin
571589
if (!data || !len) {
572590
return m5::hal::error::error_t::INVALID_ARGUMENT;
573591
}
574-
i2c_set_period(_port, _high, _low);
592+
if (_high > 0 && _low > 0) {
593+
i2c_set_period(_port, _high, _low);
594+
}
575595
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
576596
i2c_master_start(cmd);
577597
i2c_master_write_byte(cmd, static_cast<uint8_t>((_addr << 1) | I2C_MASTER_READ), true);
@@ -588,7 +608,9 @@ m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::readWithTransaction(uin
588608
m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::write_with_transaction(const uint8_t addr, const uint8_t* data,
589609
const size_t len, const uint32_t stop)
590610
{
591-
i2c_set_period(_port, _high, _low);
611+
if (_high > 0 && _low > 0) {
612+
i2c_set_period(_port, _high, _low);
613+
}
592614
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
593615
i2c_master_start(cmd);
594616
i2c_master_write_byte(cmd, static_cast<uint8_t>((addr << 1) | I2C_MASTER_WRITE), true);
@@ -612,7 +634,9 @@ m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::writeWithTransaction(co
612634
m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::writeWithTransaction(const uint8_t reg, const uint8_t* data,
613635
const size_t len, const uint32_t stop)
614636
{
615-
i2c_set_period(_port, _high, _low);
637+
if (_high > 0 && _low > 0) {
638+
i2c_set_period(_port, _high, _low);
639+
}
616640
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
617641
i2c_master_start(cmd);
618642
i2c_master_write_byte(cmd, static_cast<uint8_t>((_addr << 1) | I2C_MASTER_WRITE), true);
@@ -632,7 +656,9 @@ m5::hal::error::error_t AdapterI2C::ESPIDFLegacyBusImpl::writeWithTransaction(co
632656
const size_t len, const uint32_t stop)
633657
{
634658
m5::types::big_uint16_t r(reg);
635-
i2c_set_period(_port, _high, _low);
659+
if (_high > 0 && _low > 0) {
660+
i2c_set_period(_port, _high, _low);
661+
}
636662
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
637663
i2c_master_start(cmd);
638664
i2c_master_write_byte(cmd, static_cast<uint8_t>((_addr << 1) | I2C_MASTER_WRITE), true);

src/wiring/m5_unit_unified_wiring.hpp

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -333,17 +333,25 @@ inline bool i2cSoftware(UnitUnified& units, Component& unit, const int sda, cons
333333
@param nesso NessoN1 only: PortB (default) -> SoftwareI2C, PortA -> Wire (QWIIC)
334334
@note Dispatches via i2cPins() backend: Wire / SoftwareI2C / ExI2C.
335335
*/
336-
inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 100000,
336+
inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 0,
337337
const NessoPort nesso = NessoPort::PortB)
338338
{
339-
const auto p = i2cPins(nesso);
340-
M5_LIB_LOGI("wiring: addI2C board=0x%02x nesso=%d backend=%d sda=%d scl=%d", (int)M5.getBoard(), (int)nesso,
341-
(int)p.backend, (int)p.sda, (int)p.scl);
339+
// clock == 0: use the unit's own configured clock; clock > 0: override it. Either way the unit's
340+
// component_config().clock is the single source of truth applied per transaction by every backend.
341+
if (clock != 0) {
342+
auto cfg = unit.component_config();
343+
cfg.clock = clock;
344+
unit.component_config(cfg);
345+
}
346+
const uint32_t eff_clock = unit.component_config().clock;
347+
const auto p = i2cPins(nesso);
348+
M5_LIB_LOGI("wiring: addI2C board=0x%02x nesso=%d backend=%d sda=%d scl=%d clock=%lu", (int)M5.getBoard(),
349+
(int)nesso, (int)p.backend, (int)p.sda, (int)p.scl, (unsigned long)eff_clock);
342350
switch (p.backend) {
343351
case I2CPins::Backend::SoftwareI2C:
344352
return i2cSoftware(units, unit, p.sda, p.scl);
345353
case I2CPins::Backend::Wire:
346-
return i2cWire(units, unit, Wire, p.sda, p.scl, clock);
354+
return i2cWire(units, unit, Wire, p.sda, p.scl, eff_clock);
347355
case I2CPins::Backend::ExI2C:
348356
return i2cClass(units, unit, M5.Ex_I2C);
349357
}
@@ -468,13 +476,20 @@ inline bool addSPI(UnitUnified& units, Component& unit, const uint32_t clock_hz,
468476
@note Some Hats need extra pin pre-setup (e.g. HatHEART does pinMode(scl, OUTPUT)); do that in the
469477
caller before this call, or build the connection with the low-level i2cWire.
470478
*/
471-
inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock = 400000)
479+
inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock = 0)
472480
{
473481
const auto p = hatI2CPins();
474482
if (p.sda < 0 || p.scl < 0) {
475483
M5_LIB_LOGE("wiring: Hat I2C unsupported board=0x%02x", (int)M5.getBoard());
476484
return false;
477485
}
486+
// clock == 0: use the unit's own configured clock; clock > 0: override it.
487+
if (clock != 0) {
488+
auto cfg = unit.component_config();
489+
cfg.clock = clock;
490+
unit.component_config(cfg);
491+
}
492+
const uint32_t eff_clock = unit.component_config().clock;
478493
#if SOC_I2C_NUM > 1
479494
TwoWire& wire = p.useWire1 ? Wire1 : Wire;
480495
#else
@@ -486,8 +501,8 @@ inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock
486501
TwoWire& wire = Wire;
487502
#endif
488503
M5_LIB_LOGI("wiring: addHatI2C board=0x%02x sda=%d scl=%d clock=%lu wire=%s", (int)M5.getBoard(), (int)p.sda,
489-
(int)p.scl, (unsigned long)clock, p.useWire1 ? "Wire1" : "Wire");
490-
return i2cWire(units, unit, wire, p.sda, p.scl, clock);
504+
(int)p.scl, (unsigned long)eff_clock, p.useWire1 ? "Wire1" : "Wire");
505+
return i2cWire(units, unit, wire, p.sda, p.scl, eff_clock);
491506
}
492507

493508
/*!
@@ -663,15 +678,42 @@ inline bool ensureI2CLegacyDriver(const i2c_port_t port, const gpio_num_t sda, c
663678
M5_LIB_LOGE("wiring: I2C bus cache full (max %zu)", kI2CBusCacheSize);
664679
return false;
665680
}
666-
i2c_config_t conf{};
667-
conf.mode = I2C_MODE_MASTER;
668-
conf.sda_io_num = sda;
669-
conf.scl_io_num = scl;
670-
conf.sda_pullup_en = true;
671-
conf.scl_pullup_en = true;
672-
conf.master.clk_speed = clock;
673-
if (i2c_param_config(port, &conf) != ESP_OK || i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0) != ESP_OK) {
674-
M5_LIB_LOGE("wiring: legacy i2c driver install failed port=%d sda=%d scl=%d", (int)port, (int)sda, (int)scl);
681+
// i2c_driver_install returns ESP_FAIL for several distinct reasons in IDF 5.0/5.1 (already
682+
// installed by another owner / OOM / slave-setup failure). Probe i2c_get_period to disambiguate:
683+
// it writes high/low only when the driver object exists (p_i2c_obj != NULL), so high>0 && low>0
684+
// means the port is genuinely installed and can be borrowed.
685+
const esp_err_t install_err = i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0);
686+
if (install_err == ESP_OK) {
687+
// Freshly installed by us: configure pins and timing.
688+
i2c_config_t conf{};
689+
conf.mode = I2C_MODE_MASTER;
690+
conf.sda_io_num = sda;
691+
conf.scl_io_num = scl;
692+
conf.sda_pullup_en = true;
693+
conf.scl_pullup_en = true;
694+
conf.master.clk_speed = clock;
695+
if (i2c_param_config(port, &conf) != ESP_OK) {
696+
M5_LIB_LOGE("wiring: legacy i2c param_config failed port=%d sda=%d scl=%d", (int)port, (int)sda, (int)scl);
697+
i2c_driver_delete(port);
698+
return false;
699+
}
700+
M5_LIB_LOGI("wiring: legacy i2c INSTALLED(new) port=%d sda=%d scl=%d clock=%u", (int)port, (int)sda, (int)scl,
701+
(unsigned)clock);
702+
} else if (install_err == ESP_FAIL) {
703+
// Already installed by another owner -> borrow it (keep the owner's pins/timing; do NOT
704+
// re-run i2c_param_config and override them). Confirm the driver really exists first.
705+
int high = 0, low = 0;
706+
i2c_get_period(port, &high, &low);
707+
if (!(high > 0 && low > 0)) {
708+
M5_LIB_LOGE("wiring: legacy i2c driver install failed port=%d sda=%d scl=%d", (int)port, (int)sda,
709+
(int)scl);
710+
return false;
711+
}
712+
M5_LIB_LOGI("wiring: legacy i2c BORROW(already) port=%d sda=%d scl=%d high=%d low=%d", (int)port, (int)sda,
713+
(int)scl, high, low);
714+
} else {
715+
M5_LIB_LOGE("wiring: legacy i2c driver install error=0x%x port=%d sda=%d scl=%d", (int)install_err, (int)port,
716+
(int)sda, (int)scl);
675717
return false;
676718
}
677719
cache[count++] = key;
@@ -824,13 +866,21 @@ inline spi_device_handle_t spiDeviceHandle(const spi_host_device_t host, const g
824866
//! (M5Unified does NOT call Wire.begin() on these boards, so I2C_NUM_0 is free)
825867
//! - ExI2C (Core, NanoC6/NanoH2) -> borrow M5.Ex_I2C
826868
//! (Core: In_I2C/Ex_I2C share I2C_NUM_0, already installed by M5.begin(); borrow it)
827-
inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 100000,
869+
inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 0,
828870
const NessoPort nesso = NessoPort::PortB)
829871
{
830-
const auto pins = i2cPins(nesso);
831-
const auto board = M5.getBoard();
872+
// clock == 0: use the unit's own configured clock; clock > 0: override it. component_config().clock
873+
// is the single source of truth applied per transaction (Wire / I2C_Class / master device / legacy).
874+
if (clock != 0) {
875+
auto cfg = unit.component_config();
876+
cfg.clock = clock;
877+
unit.component_config(cfg);
878+
}
879+
const uint32_t eff_clock = unit.component_config().clock;
880+
const auto pins = i2cPins(nesso);
881+
const auto board = M5.getBoard();
832882
M5_LIB_LOGI("wiring(ESP-IDF): addI2C board=0x%02x nesso=%d sda=%d scl=%d clock=%u backend=%d", (int)board,
833-
(int)nesso, (int)pins.sda, (int)pins.scl, (unsigned)clock, (int)pins.backend);
883+
(int)nesso, (int)pins.sda, (int)pins.scl, (unsigned)eff_clock, (int)pins.backend);
834884
switch (pins.backend) {
835885
case I2CPins::Backend::SoftwareI2C:
836886
#if defined(M5_HAL_HPP)
@@ -848,11 +898,11 @@ inline bool addI2C(UnitUnified& units, Component& unit, const uint32_t clock = 1
848898
// Stick / S3 etc: M5Unified does not call Wire.begin(), so install ourselves
849899
// (Core is routed to ExI2C by i2cPins() and never reaches this branch)
850900
#if __has_include(<driver/i2c_master.h>)
851-
auto bus = i2cBusHandle(I2C_NUM_0, (gpio_num_t)pins.sda, (gpio_num_t)pins.scl, clock);
901+
auto bus = i2cBusHandle(I2C_NUM_0, (gpio_num_t)pins.sda, (gpio_num_t)pins.scl, eff_clock);
852902
if (!bus) return false;
853903
return units.add(unit, bus);
854904
#else // legacy driver (IDF 5.0 / 5.1): install legacy driver, then add by port
855-
if (!detail::ensureI2CLegacyDriver(I2C_NUM_0, (gpio_num_t)pins.sda, (gpio_num_t)pins.scl, clock)) {
905+
if (!detail::ensureI2CLegacyDriver(I2C_NUM_0, (gpio_num_t)pins.sda, (gpio_num_t)pins.scl, eff_clock)) {
856906
return false;
857907
}
858908
return units.add(unit, I2C_NUM_0, (gpio_num_t)pins.sda, (gpio_num_t)pins.scl);
@@ -902,13 +952,20 @@ inline bool addSPI(UnitUnified& units, Component& unit, const uint32_t clock_hz,
902952
}
903953

904954
//! @brief Add a unit on the board's Hat I2C header (NessoN1 uses Wire1, others Wire) (ESP-IDF native)
905-
inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock = 400000)
955+
inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock = 0)
906956
{
907957
const auto p = hatI2CPins();
908958
if (p.sda < 0 || p.scl < 0) {
909959
M5_LIB_LOGE("wiring: Hat I2C unsupported board=0x%02x", (int)M5.getBoard());
910960
return false;
911961
}
962+
// clock == 0: use the unit's own configured clock; clock > 0: override it.
963+
if (clock != 0) {
964+
auto cfg = unit.component_config();
965+
cfg.clock = clock;
966+
unit.component_config(cfg);
967+
}
968+
const uint32_t eff_clock = unit.component_config().clock;
912969
i2c_port_t port;
913970
if (p.useWire1) {
914971
#if SOC_HP_I2C_NUM >= 2
@@ -923,13 +980,13 @@ inline bool addHatI2C(UnitUnified& units, Component& unit, const uint32_t clock
923980
port = I2C_NUM_0;
924981
}
925982
M5_LIB_LOGI("wiring(ESP-IDF): addHatI2C port=%d sda=%d scl=%d clock=%u", (int)port, (int)p.sda, (int)p.scl,
926-
(unsigned)clock);
983+
(unsigned)eff_clock);
927984
#if __has_include(<driver/i2c_master.h>)
928-
auto bus = i2cBusHandle(port, (gpio_num_t)p.sda, (gpio_num_t)p.scl, clock);
985+
auto bus = i2cBusHandle(port, (gpio_num_t)p.sda, (gpio_num_t)p.scl, eff_clock);
929986
if (!bus) return false;
930987
return units.add(unit, bus);
931988
#else // legacy driver (IDF 5.0 / 5.1)
932-
if (!detail::ensureI2CLegacyDriver(port, (gpio_num_t)p.sda, (gpio_num_t)p.scl, clock)) return false;
989+
if (!detail::ensureI2CLegacyDriver(port, (gpio_num_t)p.sda, (gpio_num_t)p.scl, eff_clock)) return false;
933990
return units.add(unit, port, (gpio_num_t)p.sda, (gpio_num_t)p.scl);
934991
#endif
935992
}

0 commit comments

Comments
 (0)