From 396e67a9a5cf1256e207bf473523b005c8589b16 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 15:57:12 -0500 Subject: [PATCH 01/16] feat(i2c): Update i2c to default to new driver, and update driver to be more backwards compatible to facilitate upgrade --- .../ads1x15/example/main/ads1x15_example.cpp | 17 +- .../ads7138/example/main/ads7138_example.cpp | 18 +- .../adxl345/example/main/adxl345_example.cpp | 16 +- .../as5600/example/main/as5600_example.cpp | 22 +- .../aw9523/example/main/aw9523_example.cpp | 36 +- .../example/main/bldc_haptics_example.cpp | 17 +- .../example/main/bldc_motor_example.cpp | 17 +- .../bm8563/example/main/bm8563_example.cpp | 17 +- .../bmi270/example/main/bmi270_example.cpp | 19 +- components/byte90/src/accelerometer.cpp | 21 +- .../chsc6x/example/main/chsc6x_example.cpp | 17 +- .../example/main/controller_example.cpp | 19 +- .../cst816/example/main/cst816_example.cpp | 17 +- .../drv2605/example/main/drv2605_example.cpp | 20 +- components/esp-box/src/audio.cpp | 20 +- components/esp-box/src/imu.cpp | 21 +- components/esp-box/src/touchpad.cpp | 53 +- components/esp-nimble-cpp | 2 +- .../esp32-timer-cam/src/esp32-timer-cam.cpp | 20 +- .../ft5x06/example/main/ft5x06_example.cpp | 18 +- .../gt911/example/main/gt911_example.cpp | 17 +- components/i2c/CMakeLists.txt | 2 +- components/i2c/Kconfig | 17 +- components/i2c/README.md | 35 +- components/i2c/example/main/i2c_example.cpp | 64 +- components/i2c/idf_component.yml | 2 +- components/i2c/include/i2c.hpp | 591 +++++++++++++++++- components/i2c/include/i2c_master.hpp | 2 +- .../i2c/include/i2c_master_device_menu.hpp | 55 +- components/i2c/include/i2c_master_menu.hpp | 67 +- components/i2c/include/i2c_menu.hpp | 21 +- components/i2c/include/i2c_slave.hpp | 83 ++- components/i2c/include/i2c_slave_menu.hpp | 18 +- components/i2c/src/i2c_master.cpp | 52 +- components/i2c/src/i2c_slave.cpp | 350 +++++++++-- .../example/main/icm20948_example.cpp | 17 +- .../example/main/icm42607_example.cpp | 18 +- .../ina226/example/main/ina226_example.cpp | 25 +- .../kts1622/example/main/kts1622_example.cpp | 19 +- .../lp5817/example/main/lp5817_example.cpp | 15 +- .../lsm6dso/example/main/lsm6dso_example.cpp | 19 +- components/m5stack-tab5/src/audio.cpp | 38 +- components/m5stack-tab5/src/imu.cpp | 18 +- components/m5stack-tab5/src/m5stack-tab5.cpp | 40 +- components/m5stack-tab5/src/power.cpp | 28 +- components/m5stack-tab5/src/rtc.cpp | 17 +- components/m5stack-tab5/src/touchpad.cpp | 18 +- .../src/matouch-rotary-display.cpp | 23 +- .../example/main/max1704x_example.cpp | 18 +- .../example/main/mcp23x17_example.cpp | 19 +- .../mt6701/example/main/mt6701_example.cpp | 17 +- .../example/main/pcf85063_example.cpp | 16 +- .../example/main/pi4ioe5v_example.cpp | 16 +- .../qmi8658/example/main/qmi8658_example.cpp | 17 +- .../example/main/qwiicnes_example.cpp | 18 +- .../example/main/rx8130ce_example.cpp | 16 +- .../src/seeed-studio-round-display.cpp | 23 +- .../src/smartpanlee-sc01-plus.cpp | 24 +- .../st25dv/example/main/st25dv_example.cpp | 42 +- components/t-deck/src/t-deck.cpp | 52 +- .../example/main/t_keyboard_example.cpp | 28 +- .../tla2528/example/main/tla2528_example.cpp | 18 +- .../tt21100/example/main/tt21100_example.cpp | 14 +- .../vl53l/example/main/vl53l_example.cpp | 23 +- components/ws-s3-touch/src/imu.cpp | 20 +- components/ws-s3-touch/src/rtc.cpp | 17 +- components/ws-s3-touch/src/touchpad.cpp | 23 +- doc/en/i2c.rst | 71 ++- 68 files changed, 2052 insertions(+), 518 deletions(-) diff --git a/components/ads1x15/example/main/ads1x15_example.cpp b/components/ads1x15/example/main/ads1x15_example.cpp index 08c28319c..af5b19260 100644 --- a/components/ads1x15/example/main/ads1x15_example.cpp +++ b/components/ads1x15/example/main/ads1x15_example.cpp @@ -21,13 +21,22 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, // pin 3 on the joybonnet .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, // pin 5 on the joybonnet }); + std::error_code ec; + auto ads_device = + i2c.add_device({.device_address = espp::Ads1x15::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!ads_device) { + logger.error("Failed to initialize ADS1x15 I2C device: {}", ec.message()); + return; + } // make the actual ads class espp::Ads1x15 ads(espp::Ads1x15::Ads1015Config{ .device_address = espp::Ads1x15::DEFAULT_ADDRESS, - .write = [&i2c](uint8_t addr, const uint8_t *data, - size_t len) { return i2c.write(addr, data, len); }, - .read = [&i2c](uint8_t addr, uint8_t *data, - size_t len) { return i2c.read(addr, data, len); }, + .write = espp::make_i2c_addressed_write(ads_device), + .read = espp::make_i2c_addressed_read(ads_device), }); // make the task which will get the raw data from the I2C ADC auto ads_read_task_fn = [&ads](std::mutex &m, std::condition_variable &cv) { diff --git a/components/ads7138/example/main/ads7138_example.cpp b/components/ads7138/example/main/ads7138_example.cpp index 001e294dd..acf7f34b1 100644 --- a/components/ads7138/example/main/ads7138_example.cpp +++ b/components/ads7138/example/main/ads7138_example.cpp @@ -87,6 +87,17 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto ads_device = + i2c.add_device({.device_address = espp::Ads7138::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!ads_device) { + logger.error("Failed to initialize ADS7138 I2C device: {}", ec.message()); + return; + } // make the actual ads class static int select_bit_mask = (1 << 5); @@ -101,15 +112,12 @@ extern "C" void app_main(void) { .digital_output_values = {{espp::Ads7138::Channel::CH7, 1}}, // start the LED off // enable oversampling / averaging .oversampling_ratio = espp::Ads7138::OversamplingRatio::OSR_32, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(ads_device), + .read = espp::make_i2c_addressed_read(ads_device), .log_level = espp::Logger::Verbosity::WARN, }); // calibrate the ADC - std::error_code ec; ads.calibrate(ec); if (ec) { logger.error("error calibrating: {}", ec.message()); diff --git a/components/adxl345/example/main/adxl345_example.cpp b/components/adxl345/example/main/adxl345_example.cpp index 0c5e34d13..b2df49fc0 100644 --- a/components/adxl345/example/main/adxl345_example.cpp +++ b/components/adxl345/example/main/adxl345_example.cpp @@ -40,6 +40,16 @@ extern "C" void app_main(void) { }); std::error_code ec; + auto accel_device = + i2c.add_device({.device_address = espp::Adxl345::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!accel_device) { + logger.error("ADXL345 I2C device initialization failed: {}", ec.message()); + return; + } ////////////////////// /// ADXL345 Configuration @@ -51,10 +61,8 @@ extern "C" void app_main(void) { .device_address = espp::Adxl345::DEFAULT_ADDRESS, .range = espp::Adxl345::RANGE_2G, .data_rate = espp::Adxl345::RATE_100_HZ, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(accel_device), + .read = espp::make_i2c_addressed_read(accel_device), .log_level = espp::Logger::Verbosity::WARN, }); diff --git a/components/as5600/example/main/as5600_example.cpp b/components/as5600/example/main/as5600_example.cpp index 2a2b32a42..51dcf96f3 100644 --- a/components/as5600/example/main/as5600_example.cpp +++ b/components/as5600/example/main/as5600_example.cpp @@ -20,6 +20,17 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto as5600_device = + i2c.add_device({.device_address = espp::As5600::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!as5600_device) { + fmt::print("AS5600 I2C device initialization failed: {}\n", ec.message()); + return; + } // make the velocity filter static constexpr float filter_cutoff_hz = 4.0f; @@ -31,13 +42,10 @@ extern "C" void app_main(void) { auto filter_fn = [&filter](float raw) -> float { return filter.update(raw); }; // now make the as5600 which decodes the data - espp::As5600 as5600( - {.write_then_read = - std::bind(&espp::I2c::write_read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - .velocity_filter = filter_fn, - .update_period = std::chrono::duration(encoder_update_period), - .log_level = espp::Logger::Verbosity::WARN}); + espp::As5600 as5600({.write_then_read = espp::make_i2c_addressed_write_then_read(as5600_device), + .velocity_filter = filter_fn, + .update_period = std::chrono::duration(encoder_update_period), + .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the as5600 and print the // state. NOTE: the As5600 runs its own task to maintain state, so we're diff --git a/components/aw9523/example/main/aw9523_example.cpp b/components/aw9523/example/main/aw9523_example.cpp index d2b5ae57c..dfe0873fe 100644 --- a/components/aw9523/example/main/aw9523_example.cpp +++ b/components/aw9523/example/main/aw9523_example.cpp @@ -25,22 +25,28 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); - // now make the aw9523 which handles GPIO - espp::Aw9523 aw9523( - {// since this uses LEDs, both of the address pins are pulled up in - // hardware to have the LEDs default to off - .device_address = espp::Aw9523::DEFAULT_ADDRESS | 0b11, - // set P0_0 - P0_5 to be inputs - .port_0_direction_mask = 0b00111111, - // set P1_0 - P1_1 to be inputs - .port_1_direction_mask = 0b00000011, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .write_then_read = - std::bind(&espp::I2c::write_read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), - .log_level = espp::Logger::Verbosity::WARN}); std::error_code ec; + auto aw9523_device = + i2c.add_device({.device_address = espp::Aw9523::DEFAULT_ADDRESS | 0b11, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!aw9523_device) { + fmt::print("aw9523 I2C device initialization failed: {}\n", ec.message()); + return; + } + // now make the aw9523 which handles GPIO + espp::Aw9523 aw9523({// since this uses LEDs, both of the address pins are pulled up in + // hardware to have the LEDs default to off + .device_address = espp::Aw9523::DEFAULT_ADDRESS | 0b11, + // set P0_0 - P0_5 to be inputs + .port_0_direction_mask = 0b00111111, + // set P1_0 - P1_1 to be inputs + .port_1_direction_mask = 0b00000011, + .write = espp::make_i2c_addressed_write(aw9523_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(aw9523_device), + .log_level = espp::Logger::Verbosity::WARN}); aw9523.initialize(ec); // Initialized separately from the constructor. if (ec) { fmt::print("aw9523 initialization failed: {}\n", ec.message()); diff --git a/components/bldc_haptics/example/main/bldc_haptics_example.cpp b/components/bldc_haptics/example/main/bldc_haptics_example.cpp index 3fca008a0..e7db74c08 100644 --- a/components/bldc_haptics/example/main/bldc_haptics_example.cpp +++ b/components/bldc_haptics/example/main/bldc_haptics_example.cpp @@ -30,11 +30,20 @@ extern "C" void app_main(void) { // now make the mt6701 which decodes the data using Encoder = espp::Mt6701<>; + std::error_code ec; + auto encoder_device = + i2c.add_device({.device_address = Encoder::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!encoder_device) { + logger.error("Failed to initialize MT6701 I2C device: {}", ec.message()); + return; + } std::shared_ptr mt6701 = std::make_shared( - Encoder::Config{.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + Encoder::Config{.write = espp::make_i2c_addressed_write(encoder_device), + .read = espp::make_i2c_addressed_read(encoder_device), .velocity_filter = nullptr, // no filtering .update_period = std::chrono::duration(core_update_period), .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/bldc_motor/example/main/bldc_motor_example.cpp b/components/bldc_motor/example/main/bldc_motor_example.cpp index f2c0d269c..5278994a8 100644 --- a/components/bldc_motor/example/main/bldc_motor_example.cpp +++ b/components/bldc_motor/example/main/bldc_motor_example.cpp @@ -35,11 +35,20 @@ extern "C" void app_main(void) { //! [bldc_motor example] // now make the mt6701 which decodes the data using Encoder = espp::Mt6701<>; + std::error_code ec; + auto encoder_device = + i2c.add_device({.device_address = Encoder::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!encoder_device) { + logger.error("Failed to initialize MT6701 I2C device: {}", ec.message()); + return; + } std::shared_ptr mt6701 = std::make_shared( - Encoder::Config{.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + Encoder::Config{.write = espp::make_i2c_addressed_write(encoder_device), + .read = espp::make_i2c_addressed_read(encoder_device), .velocity_filter = nullptr, // no filtering .update_period = std::chrono::duration(core_update_period), .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/bm8563/example/main/bm8563_example.cpp b/components/bm8563/example/main/bm8563_example.cpp index d3dd719c2..602056839 100644 --- a/components/bm8563/example/main/bm8563_example.cpp +++ b/components/bm8563/example/main/bm8563_example.cpp @@ -20,14 +20,23 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, }); + std::error_code ec; + auto bm8563_device = + i2c.add_device({.device_address = espp::Bm8563::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!bm8563_device) { + fmt::print("bm8563 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the bm8563 auto bm8563 = espp::Bm8563({ - .write = std::bind_front(&espp::I2c::write, &i2c), - .write_then_read = std::bind_front(&espp::I2c::write_read, &i2c), + .write = espp::make_i2c_addressed_write(bm8563_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(bm8563_device), }); - std::error_code ec; - // set the time espp::Bm8563::DateTime date_time = {.date = { diff --git a/components/bmi270/example/main/bmi270_example.cpp b/components/bmi270/example/main/bmi270_example.cpp index 6a4f545cc..f0b7e73b0 100644 --- a/components/bmi270/example/main/bmi270_example.cpp +++ b/components/bmi270/example/main/bmi270_example.cpp @@ -82,14 +82,23 @@ extern "C" void app_main(void) { logger.warn("No BMI270 found at address: 0x{:02X}", address); } } + std::error_code ec; + auto bmi270_device = + i2c.add_device({.device_address = bmi270_address, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!bmi270_device) { + logger.error("Failed to initialize BMI270 I2C device: {}", ec.message()); + return; + } // make the IMU config Imu::Config config{ .device_address = bmi270_address, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(bmi270_device), + .read = espp::make_i2c_addressed_read(bmi270_device), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_4G, @@ -119,8 +128,6 @@ extern "C" void app_main(void) { // Note: This assumes the device is flat on a table (Z-axis = 1g) // For a real application, you might want to trigger this based on a user action // or store the offsets in NVS. - std::error_code ec; - logger.info("Performing Accelerometer FOC..."); Imu::AccelFocGValue accel_foc_target = {.x = 0, .y = 0, .z = 1, .sign = 0}; // 1g on Z axis if (imu.perform_accel_foc(accel_foc_target, ec)) { diff --git a/components/byte90/src/accelerometer.cpp b/components/byte90/src/accelerometer.cpp index f8b4bca6e..8afb9d1fc 100644 --- a/components/byte90/src/accelerometer.cpp +++ b/components/byte90/src/accelerometer.cpp @@ -18,20 +18,29 @@ bool Byte90::initialize_accelerometer(const Byte90::accel_callback_t &callback) // store the callback accel_callback_ = callback; + std::error_code ec; + auto accelerometer_device = internal_i2c_.add_device( + { + .device_address = Accelerometer::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!accelerometer_device) { + logger_.error("Could not initialize accelerometer I2C device: {}", ec.message()); + return false; + } // create the accelerometer accelerometer_ = std::make_shared(Accelerometer::Config{ .device_address = Accelerometer::DEFAULT_ADDRESS, .range = Accelerometer::RANGE_2G, .data_rate = Accelerometer::RATE_100_HZ, - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + .write = espp::make_i2c_addressed_write(accelerometer_device), + .read = espp::make_i2c_addressed_read(accelerometer_device), .log_level = espp::Logger::Verbosity::WARN, }); - std::error_code ec; - // Disable measurement mode initially, so we can configure interrupts and such accelerometer_->set_measurement_mode(false, ec); diff --git a/components/chsc6x/example/main/chsc6x_example.cpp b/components/chsc6x/example/main/chsc6x_example.cpp index 70d4e353e..258b9cb20 100644 --- a/components/chsc6x/example/main/chsc6x_example.cpp +++ b/components/chsc6x/example/main/chsc6x_example.cpp @@ -25,12 +25,21 @@ extern "C" void app_main(void) { bool has_chsc6x = i2c.probe_device(espp::Chsc6x::DEFAULT_ADDRESS); fmt::print("Touchpad probe: {}\n", has_chsc6x); + std::error_code ec; + auto chsc6x_device = + i2c.add_device({.device_address = espp::Chsc6x::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!chsc6x_device) { + fmt::print("CHSC6X I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the chsc6x which decodes the data - espp::Chsc6x chsc6x({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + espp::Chsc6x chsc6x({.write = espp::make_i2c_addressed_write(chsc6x_device), + .read = espp::make_i2c_addressed_read(chsc6x_device), .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the chsc6x and print diff --git a/components/controller/example/main/controller_example.cpp b/components/controller/example/main/controller_example.cpp index c36c2cc99..cd1b43aba 100644 --- a/components/controller/example/main/controller_example.cpp +++ b/components/controller/example/main/controller_example.cpp @@ -187,12 +187,21 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto ads_device = + i2c.add_device({.device_address = espp::Ads1x15::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!ads_device) { + fmt::print("ADS1x15 I2C device initialization failed: {}\n", ec.message()); + return; + } // make the actual ads class - espp::Ads1x15 ads(espp::Ads1x15::Ads1015Config{ - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3)}); + espp::Ads1x15 ads( + espp::Ads1x15::Ads1015Config{.write = espp::make_i2c_addressed_write(ads_device), + .read = espp::make_i2c_addressed_read(ads_device)}); // make the task which will get the raw data from the I2C ADC and convert to // uncalibrated [-1,1] std::atomic joystick_x{0}; diff --git a/components/cst816/example/main/cst816_example.cpp b/components/cst816/example/main/cst816_example.cpp index cd374ed2c..9b3d8b517 100644 --- a/components/cst816/example/main/cst816_example.cpp +++ b/components/cst816/example/main/cst816_example.cpp @@ -25,12 +25,21 @@ extern "C" void app_main(void) { bool has_cst816 = i2c.probe_device(espp::Cst816::DEFAULT_ADDRESS); fmt::print("Touchpad probe: {}\n", has_cst816); + std::error_code ec; + auto cst816_device = + i2c.add_device({.device_address = espp::Cst816::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!cst816_device) { + fmt::print("CST816 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the cst816 which decodes the data - espp::Cst816 cst816({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + espp::Cst816 cst816({.write = espp::make_i2c_addressed_write(cst816_device), + .read = espp::make_i2c_addressed_read(cst816_device), .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the cst816 and print diff --git a/components/drv2605/example/main/drv2605_example.cpp b/components/drv2605/example/main/drv2605_example.cpp index bba0547d3..268baddd0 100644 --- a/components/drv2605/example/main/drv2605_example.cpp +++ b/components/drv2605/example/main/drv2605_example.cpp @@ -23,21 +23,27 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); - using Driver = espp::Drv2605; + std::error_code ec; + auto drv2605_device = + i2c.add_device({.device_address = Driver::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!drv2605_device) { + logger.error("Could not initialize DRV2605 I2C device: {}", ec.message()); + return; + } // make the actual drv2605 class Driver drv2605(Driver::Config{ .device_address = Driver::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + .write = espp::make_i2c_addressed_write(drv2605_device), + .read_register = espp::make_i2c_addressed_read_register(drv2605_device), .motor_type = Driver::MotorType::LRA, .log_level = espp::Logger::Verbosity::INFO, }); - std::error_code ec; // we're using an ERM motor, so select an ERM library (1-5). // drv2605.select_library(1, ec); // we're using an LRA motor, so select an LRA library (6). diff --git a/components/esp-box/src/audio.cpp b/components/esp-box/src/audio.cpp index 28eecd731..209318a58 100644 --- a/components/esp-box/src/audio.cpp +++ b/components/esp-box/src/audio.cpp @@ -9,10 +9,22 @@ using namespace espp; bool EspBox::initialize_codec() { logger_.info("initializing codec"); - set_es8311_write(std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3)); - set_es8311_read(std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + std::error_code ec; + auto codec_device = internal_i2c_.add_device( + { + .device_address = 0x18, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!codec_device) { + logger_.error("Could not initialize codec I2C device: {}", ec.message()); + return false; + } + + set_es8311_write(espp::make_i2c_addressed_write(codec_device)); + set_es8311_read(espp::make_i2c_addressed_read_register(codec_device)); esp_err_t ret_val = ESP_OK; audio_hal_codec_config_t cfg; diff --git a/components/esp-box/src/imu.cpp b/components/esp-box/src/imu.cpp index e38464606..7990f2c6b 100644 --- a/components/esp-box/src/imu.cpp +++ b/components/esp-box/src/imu.cpp @@ -9,12 +9,24 @@ bool EspBox::initialize_imu(const EspBox::Imu::filter_fn &orientation_filter, return false; } + std::error_code ec; + auto imu_device = internal_i2c_.add_device( + { + .device_address = Imu::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!imu_device) { + logger_.error("Could not initialize IMU I2C device: {}", ec.message()); + return false; + } + Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = imu_config, .orientation_filter = orientation_filter, .auto_init = true, @@ -24,7 +36,6 @@ bool EspBox::initialize_imu(const EspBox::Imu::filter_fn &orientation_filter, imu_ = std::make_shared(config); // configure the dmp - std::error_code ec; // turn on DMP if (!imu_->set_dmp_power_save(false, ec)) { logger_.error("Failed to set DMP power save mode: {}", ec.message()); diff --git a/components/esp-box/src/touchpad.cpp b/components/esp-box/src/touchpad.cpp index 5817b4394..6d9658429 100644 --- a/components/esp-box/src/touchpad.cpp +++ b/components/esp-box/src/touchpad.cpp @@ -13,24 +13,45 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { } switch (box_type_) { - case BoxType::BOX3: + case BoxType::BOX3: { + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = espp::Gt911::DEFAULT_ADDRESS_1, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize GT911 I2C device: {}", ec.message()); + return false; + } logger_.info("Initializing GT911"); - gt911_ = std::make_unique(espp::Gt911::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); - break; - case BoxType::BOX: + gt911_ = std::make_unique( + espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); + } break; + case BoxType::BOX: { + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = espp::Tt21100::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize TT21100 I2C device: {}", ec.message()); + return false; + } logger_.info("Initializing TT21100"); - tt21100_ = std::make_unique(espp::Tt21100::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); - break; + tt21100_ = std::make_unique( + espp::Tt21100::Config{.read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); + } break; default: return false; } diff --git a/components/esp-nimble-cpp b/components/esp-nimble-cpp index 3266951ae..1eddc2851 160000 --- a/components/esp-nimble-cpp +++ b/components/esp-nimble-cpp @@ -1 +1 @@ -Subproject commit 3266951aeea7249fd717f33182235e23db9acd8f +Subproject commit 1eddc28515bbb2ef29ccc2494d14584c40b400ad diff --git a/components/esp32-timer-cam/src/esp32-timer-cam.cpp b/components/esp32-timer-cam/src/esp32-timer-cam.cpp index 727e5394f..e5b7c69b8 100644 --- a/components/esp32-timer-cam/src/esp32-timer-cam.cpp +++ b/components/esp32-timer-cam/src/esp32-timer-cam.cpp @@ -117,12 +117,22 @@ bool EspTimerCam::initialize_rtc() { logger_.error("RTC already initialized"); return false; } + std::error_code ec; + auto rtc_device = internal_i2c_.add_device( + { + .device_address = EspTimerCam::Rtc::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!rtc_device) { + logger_.error("Could not initialize RTC I2C device: {}", ec.message()); + return false; + } rtc_ = std::make_shared(EspTimerCam::Rtc::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .write_then_read = std::bind(&espp::I2c::write_read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, - std::placeholders::_4, std::placeholders::_5), + .write = espp::make_i2c_addressed_write(rtc_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(rtc_device), .log_level = espp::Logger::Verbosity::WARN}); return true; } diff --git a/components/ft5x06/example/main/ft5x06_example.cpp b/components/ft5x06/example/main/ft5x06_example.cpp index def438e89..c2c3379cc 100644 --- a/components/ft5x06/example/main/ft5x06_example.cpp +++ b/components/ft5x06/example/main/ft5x06_example.cpp @@ -20,12 +20,20 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, }); + std::error_code ec; + auto ft5x06_device = + i2c.add_device({.device_address = espp::Ft5x06::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!ft5x06_device) { + fmt::print("ft5x06 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the ft5x06 which decodes the data - espp::Ft5x06 ft5x06({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read_register = std::bind(&espp::I2c::read_at_register, &i2c, - std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4), + espp::Ft5x06 ft5x06({.write = espp::make_i2c_addressed_write(ft5x06_device), + .read_register = espp::make_i2c_addressed_read_register(ft5x06_device), .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the ft5x06 and print // the state diff --git a/components/gt911/example/main/gt911_example.cpp b/components/gt911/example/main/gt911_example.cpp index 0d8baea72..9ea3b7d2f 100644 --- a/components/gt911/example/main/gt911_example.cpp +++ b/components/gt911/example/main/gt911_example.cpp @@ -28,12 +28,21 @@ extern "C" void app_main(void) { uint8_t address = has_gt911_5d ? 0x5d : 0x14; fmt::print("Touchpad probe: {}\n", has_gt911_5d || has_gt911_14); fmt::print(" address: {:#02x}\n", address); + std::error_code ec; + auto gt911_device = + i2c.add_device({.device_address = address, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!gt911_device) { + fmt::print("GT911 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the gt911 which decodes the data - espp::Gt911 gt911({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + espp::Gt911 gt911({.write = espp::make_i2c_addressed_write(gt911_device), + .read = espp::make_i2c_addressed_read(gt911_device), .address = address, .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/i2c/CMakeLists.txt b/components/i2c/CMakeLists.txt index 205d7bfb5..95f48c62a 100644 --- a/components/i2c/CMakeLists.txt +++ b/components/i2c/CMakeLists.txt @@ -1,5 +1,5 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES "driver" "base_component" "cli" "task" + REQUIRES "base_component" "driver" "cli" "esp_driver_i2c" "task" ) diff --git a/components/i2c/Kconfig b/components/i2c/Kconfig index ac9730347..0b8e96d3d 100644 --- a/components/i2c/Kconfig +++ b/components/i2c/Kconfig @@ -2,21 +2,24 @@ menu "ESPP I2C Component Configuration" choice prompt "I2C API Selection" - default ESPP_I2C_USE_LEGACY_API + default ESPP_I2C_USE_NEW_API config ESPP_I2C_USE_LEGACY_API bool "Use legacy I2C API (i2c.hpp)" help - Use the legacy I2C API (i2c.hpp). This is compatible with ESP-IDF versions < 6.0. - This is the default for compatibility with existing code. + Use the deprecated ESP-IDF legacy I2C driver through i2c.hpp. + This is only intended for older environments that still require the + legacy driver. config ESPP_I2C_USE_NEW_API - bool "Use new I2C API (i2c_master.hpp/i2c_slave.hpp)" + bool "Use new I2C API (i2c.hpp, i2c_master.hpp, i2c_slave.hpp)" help - Use the new I2C API (i2c_master.hpp/i2c_slave.hpp). This is only available for ESP-IDF >= 5.4.0. - This API is modern, type-safe, and supports the latest ESP-IDF features. + Use the ESP-IDF master/slave bus APIs. In this mode, i2c.hpp keeps the + familiar address-based espp::I2c helpers for compatibility while also + exposing explicit device handles via add_device(). - This API will be required for ESP-IDF 6.0 and later, as the legacy API is deprecated. + This API is available on ESP-IDF >= 5.4.0 and is required for ESP-IDF + 6.0 and later. endchoice diff --git a/components/i2c/README.md b/components/i2c/README.md index 4985da705..c1bd8767a 100644 --- a/components/i2c/README.md +++ b/components/i2c/README.md @@ -2,16 +2,35 @@ [![Badge](https://components.espressif.com/components/espp/i2c/badge.svg)](https://components.espressif.com/components/espp/i2c) -The `I2C` class provides a simple interface to the I2C bus. It is a wrapper -around the esp-idf I2C driver. +The `i2c` component provides C++ wrappers around ESP-IDF's I2C drivers. -A helper `I2cMenu` is also provided which can be used to interactively test -I2C buses - scanning the bus, probing devices, reading and writing to devices. +For ESP-IDF v6.x and newer ESPP defaults to the new master/slave bus API. The +primary `espp::I2c` class keeps the familiar address-based helper methods +(`probe_device`, `read`, `write`, `write_read`, `read_at_register`, etc.) for +backwards compatibility, while also allowing explicit per-device handles via +`add_device()`. -Note that the `I2CMenu` is only available if you compile with `exception support -enabled` as it relies on the `Cli` component, which requires exceptions. +If you want direct access to the bus/device model, the component also exposes: + +- `espp::I2cMasterBus` +- `espp::I2cMasterDevice` +- `espp::I2cSlaveDevice` + +A helper `I2cMenu` is provided for the bus-compatible `espp::I2c` API, and the +new-device API also includes `I2cMasterMenu`, `I2cMasterDeviceMenu`, and +`I2cSlaveMenu`. + +`espp::I2cSlaveDevice` now matches ESP-IDF's callback-driven slave model more +closely: `read()` returns the next complete master-write transaction buffered by +the driver callback path, `write()` stages bytes for the next master read, and +optional request / receive callbacks are dispatched in task context rather than +directly from the ISR callback. ## Example -The [example](./example) shows how to use the `I2C` component to communicate with -peripherals on the I2C bus. +The [example](./example) shows both styles: + +- using `espp::I2c` as a backwards-compatible bus helper +- creating an explicit device with `i2c.add_device()` + +There is not yet a dedicated slave example. diff --git a/components/i2c/example/main/i2c_example.cpp b/components/i2c/example/main/i2c_example.cpp index 7480a26a1..b68dd3182 100644 --- a/components/i2c/example/main/i2c_example.cpp +++ b/components/i2c/example/main/i2c_example.cpp @@ -2,14 +2,13 @@ #include #include -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) #include "i2c.hpp" #include "i2c_menu.hpp" -#endif #if defined(CONFIG_ESPP_I2C_USE_NEW_API) #include "i2c_master.hpp" #include "i2c_master_device_menu.hpp" #include "i2c_master_menu.hpp" +#include "i2c_slave_menu.hpp" #endif #include "logger.hpp" @@ -19,10 +18,9 @@ extern "C" void app_main(void) { espp::Logger logger({.tag = "I2C Example", .level = espp::Logger::Verbosity::INFO}); logger.info("Starting"); -////////////////////////////////// -// I2C Master Bus/Device Example using the Legacy API -////////////////////////////////// -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) + ////////////////////////////////// + // I2C Example using the primary bus-compatible API + ////////////////////////////////// { //! [i2c menu example] espp::I2c i2c({ @@ -34,9 +32,29 @@ extern "C" void app_main(void) { .clk_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, .log_level = espp::Logger::Verbosity::INFO, }); - // now make a menu for it + auto root_menu = std::make_unique("main", "I2C Main Menu"); espp::I2cMenu i2c_menu(i2c); - cli::Cli cli(i2c_menu.get()); + root_menu->Insert(i2c_menu.get()); +#if defined(CONFIG_ESPP_I2C_USE_NEW_API) + std::error_code ec; + //! [i2c device creation example] + auto device = i2c.add_device( + espp::I2c::DeviceConfig{ + .device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR, + .timeout_ms = 50, + .scl_speed_hz = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, + .log_level = espp::Logger::Verbosity::INFO, + }, + ec); + //! [i2c device creation example] + if (!device) { + logger.error("Failed to add compatibility device: {}", ec.message()); + } else { + espp::I2cMasterDeviceMenu i2c_device_menu(device); + root_menu->Insert(i2c_device_menu.get()); + } +#endif + cli::Cli cli(std::move(root_menu)); cli::SetColor(); cli.ExitAction([](auto &out) { out << "Goodbye and thanks for all the fish.\n"; }); espp::Cli input(cli); @@ -81,8 +99,33 @@ extern "C" void app_main(void) { logger.error("Could not find device with address {:#02x}", device_address); } //! [i2c example] + +#if defined(CONFIG_ESPP_I2C_USE_NEW_API) + std::error_code ec; + auto device = i2c.add_device( + espp::I2c::DeviceConfig{ + .device_address = device_address, + .timeout_ms = 50, + .scl_speed_hz = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, + .log_level = espp::Logger::Verbosity::INFO, + }, + ec); + if (!device) { + logger.error("Could not create explicit device with address {:#02x}: {}", device_address, + ec.message()); + } else { + //! [i2c device read example] + std::vector read_data(CONFIG_EXAMPLE_I2C_DEVICE_REG_SIZE, 0); + bool success = device->read_register(register_address, read_data, ec); + if (success) { + logger.info("explicit device read data: {::#02x}", read_data); + } else { + logger.error("explicit device read failed: {}", ec.message()); + } + //! [i2c device read example] + } +#endif } -#endif // CONFIG_ESPP_I2C_USE_LEGACY_API ////////////////////////////////// // I2C Master Bus/Device Example using the New API @@ -95,7 +138,7 @@ extern "C" void app_main(void) { using espp::I2cMasterDevice; std::error_code ec; I2cMasterBus bus({ - .port = -1, // auto-select + .port = I2C_NUM_0, .sda_io_num = CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = CONFIG_EXAMPLE_I2C_SCL_GPIO, .clk_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, @@ -112,6 +155,7 @@ extern "C" void app_main(void) { auto dev = bus.add_device( espp::I2cMasterDevice::Config{ .device_address = CONFIG_EXAMPLE_I2C_DEVICE_ADDR, + .timeout_ms = 50, .scl_speed_hz = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, .log_level = espp::Logger::Verbosity::DEBUG, }, diff --git a/components/i2c/idf_component.yml b/components/i2c/idf_component.yml index 1beb94fc3..8d08402ba 100644 --- a/components/i2c/idf_component.yml +++ b/components/i2c/idf_component.yml @@ -1,6 +1,6 @@ ## IDF Component Manager Manifest File license: "MIT" -description: "I2C (Legacy) component for ESP-IDF" +description: "I2C bus and device wrappers for ESP-IDF" url: "https://github.com/esp-cpp/espp/tree/main/components/i2c" repository: "git://github.com/esp-cpp/espp.git" maintainers: diff --git a/components/i2c/include/i2c.hpp b/components/i2c/include/i2c.hpp index 6a3e3fc32..c48f08e11 100644 --- a/components/i2c/include/i2c.hpp +++ b/components/i2c/include/i2c.hpp @@ -2,10 +2,12 @@ #include -// Only include this header if the legacy API is selected -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) || defined(_DOXYGEN_) +#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) +#include +#include #include +#include #include #include @@ -33,6 +35,180 @@ namespace espp { /// \snippet i2c_example.cpp i2c example class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { public: + template class Device : public espp::BaseComponent { + public: + struct Config { + uint16_t device_address = 0; + int timeout_ms = 10; + int addr_bit_len = 7; + uint32_t scl_speed_hz = 400000; + bool auto_init = true; + Logger::Verbosity log_level = Logger::Verbosity::WARN; + }; + + explicit Device(I2c &bus, const Config &config) + : BaseComponent("I2C Device", config.log_level) + , bus_(&bus) + , config_(config) { + if (config.auto_init) { + std::error_code ec; + if (!init(ec)) { + logger_.error("auto init failed"); + } + } + } + + ~Device() { + std::error_code ec; + deinit(ec); + if (ec) { + logger_.error("deinit failed"); + } + } + + bool init(std::error_code &ec) { + std::lock_guard lock(mutex_); + if (initialized_) { + ec = std::make_error_code(std::errc::already_connected); + return false; + } + if (!bus_ || !bus_->initialized()) { + ec = std::make_error_code(std::errc::not_connected); + return false; + } + initialized_ = true; + ec.clear(); + return true; + } + + bool deinit(std::error_code &ec) { + std::lock_guard lock(mutex_); + initialized_ = false; + ec.clear(); + return true; + } + + int get_timeout_ms() const { return config_.timeout_ms; } + void set_timeout_ms(int timeout_ms) { config_.timeout_ms = timeout_ms; } + + uint16_t get_device_address() const { return config_.device_address; } + void set_device_address(uint16_t device_address, int addr_bit_len = 7) { + config_.device_address = device_address; + config_.addr_bit_len = addr_bit_len; + } + + bool probe(std::error_code &ec) { return probe(config_.timeout_ms, ec); } + + bool probe(int32_t timeout_ms, std::error_code &ec) { + (void)timeout_ms; + std::lock_guard lock(mutex_); + if (!initialized_ || !bus_) { + ec = std::make_error_code(std::errc::not_connected); + return false; + } + bool found = bus_->probe_device(config_.device_address); + if (found) { + ec.clear(); + } else { + ec = std::make_error_code(std::errc::io_error); + } + return found; + } + + bool write(const uint8_t *data, size_t len, std::error_code &ec) { + std::lock_guard lock(mutex_); + if (!initialized_ || !bus_) { + ec = std::make_error_code(std::errc::not_connected); + return false; + } + bool ok = bus_->write(config_.device_address, data, len); + if (ok) { + ec.clear(); + } else { + ec = std::make_error_code(std::errc::io_error); + } + return ok; + } + + bool read(uint8_t *data, size_t len, std::error_code &ec) { + std::lock_guard lock(mutex_); + if (!initialized_ || !bus_) { + ec = std::make_error_code(std::errc::not_connected); + return false; + } + bool ok = bus_->read(config_.device_address, data, len); + if (ok) { + ec.clear(); + } else { + ec = std::make_error_code(std::errc::io_error); + } + return ok; + } + + bool write_read(const uint8_t *wdata, size_t wlen, uint8_t *rdata, size_t rlen, + std::error_code &ec) { + std::lock_guard lock(mutex_); + if (!initialized_ || !bus_) { + ec = std::make_error_code(std::errc::not_connected); + return false; + } + bool ok = bus_->write_read(config_.device_address, wdata, wlen, rdata, rlen); + if (ok) { + ec.clear(); + } else { + ec = std::make_error_code(std::errc::io_error); + } + return ok; + } + + bool write(const std::vector &data, std::error_code &ec) { + return write(data.data(), data.size(), ec); + } + + bool read(std::vector &data, std::error_code &ec) { + return read(data.data(), data.size(), ec); + } + + bool write_read(const std::vector &wdata, std::vector &rdata, + std::error_code &ec) { + return write_read(wdata.data(), wdata.size(), rdata.data(), rdata.size(), ec); + } + + bool write_register(RegisterType reg, const uint8_t *data, size_t len, std::error_code &ec) { + std::vector buffer(sizeof(RegisterType) + len); + std::memcpy(buffer.data(), ®, sizeof(RegisterType)); + if (len > 0 && data) { + std::memcpy(buffer.data() + sizeof(RegisterType), data, len); + } + return write(buffer.data(), buffer.size(), ec); + } + + bool write_register(RegisterType reg, const std::vector &data, std::error_code &ec) { + return write_register(reg, data.data(), data.size(), ec); + } + + bool read_register(RegisterType reg, uint8_t *data, size_t len, std::error_code &ec) { + return write_read(reinterpret_cast(®), sizeof(RegisterType), data, len, + ec); + } + + bool read_register(RegisterType reg, std::vector &data, std::error_code &ec) { + return read_register(reg, data.data(), data.size(), ec); + } + + const Config &config() const { return config_; } + bool initialized() const { return initialized_; } + + protected: + I2c *bus_ = nullptr; + Config config_; + bool initialized_ = false; + mutable std::recursive_mutex mutex_; + }; + + template + using DeviceConfig = typename Device::Config; + /// Configuration for I2C struct Config { int isr_core_id = -1; ///< The core to install the I2C interrupt on. If -1, then the I2C @@ -64,6 +240,9 @@ class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { } } + const Config &config() const { return config_; } + bool initialized() const { return initialized_; } + /// Destructor ~I2c() { std::error_code ec; @@ -298,6 +477,23 @@ class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { return success; } + template + std::shared_ptr> add_device(const DeviceConfig &dev_config, + std::error_code &ec) { + if (!initialized_) { + logger_.error("not initialized"); + ec = std::make_error_code(std::errc::not_connected); + return nullptr; + } + auto device = std::make_shared>(*this, dev_config); + if (dev_config.auto_init && !device->initialized()) { + ec = std::make_error_code(std::errc::io_error); + return nullptr; + } + ec.clear(); + return device; + } + protected: Config config_; bool initialized_ = false; @@ -305,6 +501,329 @@ class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { }; } // namespace espp +#elif defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) + +#include +#include +#include +#include +#include + +#include "base_component.hpp" +#include "i2c_format_helpers.hpp" +#include "i2c_master.hpp" + +namespace espp { +/// @brief I2C master bus wrapper with backwards-compatible helpers. +/// @details +/// On ESP-IDF v6.x this class is backed by the new master bus/device API, but it +/// keeps the familiar address-based helper methods for existing code. New code +/// can create explicit per-device handles with add_device(). +/// +/// \section i2c_ex1 Example +/// \snippet i2c_example.cpp i2c example +/// \section i2c_ex2 Add a device +/// \snippet i2c_example.cpp i2c device creation example +class I2c : public BaseComponent { +public: + /// Configuration for the I2C master bus. + struct Config { + int isr_core_id = -1; ///< Kept for compatibility; ignored by the new ESP-IDF API. + i2c_port_t port = I2C_NUM_0; ///< I2C port + gpio_num_t sda_io_num = GPIO_NUM_NC; ///< SDA pin + gpio_num_t scl_io_num = GPIO_NUM_NC; ///< SCL pin + gpio_pullup_t sda_pullup_en = GPIO_PULLUP_DISABLE; ///< SDA pullup + gpio_pullup_t scl_pullup_en = GPIO_PULLUP_DISABLE; ///< SCL pullup + uint32_t timeout_ms = 10; ///< Default timeout in milliseconds + uint32_t clk_speed = 400 * 1000; ///< I2C clock speed in hertz + bool auto_init = true; ///< Automatically initialize I2C on construction + Logger::Verbosity log_level = Logger::Verbosity::WARN; ///< Verbosity of logger + }; + + template using Device = I2cMasterDevice; + template + using DeviceConfig = typename Device::Config; + + /// Construct I2C driver + /// \param config Configuration for I2C + explicit I2c(const Config &config) + : BaseComponent("I2C", config.log_level) + , config_(config) + , master_bus_(make_master_bus_config(config)) { + if (config.auto_init) { + std::error_code ec; + init(ec); + if (ec) { + logger_.error("auto init failed"); + } + } + } + + /// Destructor + ~I2c() { + std::error_code ec; + deinit(ec); + if (ec) { + logger_.error("deinit failed"); + } + } + + /// Set the logger verbosity for the bus and cached compatibility devices. + /// \param log_level The desired logger verbosity. + void set_log_level(Logger::Verbosity log_level) { + BaseComponent::set_log_level(log_level); + std::lock_guard lock(mutex_); + master_bus_.set_log_level(log_level); + for (auto &[address, device] : compatibility_devices_) { + (void)address; + if (device) { + device->set_log_level(get_compatibility_device_log_level(log_level)); + } + } + } + + /// Get the active configuration. + /// \return The active configuration. + const Config &config() const { return config_; } + + /// Initialize the I2C bus. + /// \param ec Error code populated on failure. + void init(std::error_code &ec) { + std::lock_guard lock(mutex_); + if (initialized_) { + logger_.warn("already initialized"); + ec = std::make_error_code(std::errc::protocol_error); + return; + } + + if (config_.isr_core_id != -1) { + logger_.warn("isr_core_id is ignored by the ESP-IDF v6 I2C master bus API"); + } + if (config_.sda_pullup_en != config_.scl_pullup_en) { + logger_.warn("ESP-IDF v6 uses a single internal-pullup flag; enabling internal pullups " + "when either SDA or SCL requests them"); + } + + master_bus_.init(ec); + if (ec) { + return; + } + master_bus_.set_log_level(get_log_level()); + initialized_ = true; + ec.clear(); + } + + /// Deinitialize the I2C bus. + /// \param ec Error code populated on failure. + void deinit(std::error_code &ec) { + std::map>> compatibility_devices; + { + std::lock_guard lock(mutex_); + if (!initialized_) { + ec.clear(); + return; + } + compatibility_devices.swap(compatibility_devices_); + } + + bool device_deinit_failed = false; + for (auto &[address, device] : compatibility_devices) { + if (!device) { + continue; + } + std::error_code device_ec; + device->deinit(device_ec); + if (device_ec) { + logger_.error("could not deinitialize cached I2C device at address {:#04x}: {}", address, + device_ec.message()); + device_deinit_failed = true; + } + } + + std::error_code bus_ec; + master_bus_.deinit(bus_ec); + + std::lock_guard lock(mutex_); + initialized_ = bus_ec ? true : false; + if (bus_ec) { + ec = bus_ec; + return; + } + if (device_deinit_failed) { + ec = std::make_error_code(std::errc::io_error); + return; + } + ec.clear(); + } + + /// Check whether the bus is initialized. + /// \return True if the bus is initialized. + bool initialized() const { + std::lock_guard lock(mutex_); + return initialized_; + } + + /// Add an explicit I2C device to the bus. + /// \tparam RegisterType Register address type for the device. + /// \param dev_config Device configuration. + /// \param ec Error code populated on failure. + /// \return Shared pointer to the created device, or nullptr on failure. + template + std::shared_ptr> add_device(const DeviceConfig &dev_config, + std::error_code &ec) { + return master_bus_.add_device(dev_config, ec); + } + + /// Write data to an addressed I2C device. + bool write(const uint8_t dev_addr, const uint8_t *data, const size_t data_len) { + std::error_code ec; + auto device = get_compatibility_device(dev_addr, ec); + if (!device) { + return false; + } + return device->write(data, data_len, ec); + } + + /// Write data to an addressed I2C device. + bool write_vector(const uint8_t dev_addr, const std::vector &data) { + return write(dev_addr, data.data(), data.size()); + } + + /// Write to and then read from an addressed I2C device. + bool write_read(const uint8_t dev_addr, const uint8_t *write_data, const size_t write_size, + uint8_t *read_data, size_t read_size) { + std::error_code ec; + auto device = get_compatibility_device(dev_addr, ec); + if (!device) { + return false; + } + return device->write_read(write_data, write_size, read_data, read_size, ec); + } + + /// Write to and then read from an addressed I2C device. + bool write_read_vector(const uint8_t dev_addr, const std::vector &write_data, + std::vector &read_data) { + return write_read(dev_addr, write_data.data(), write_data.size(), read_data.data(), + read_data.size()); + } + + /// Read from a register on an addressed I2C device. + bool read_at_register(const uint8_t dev_addr, const uint8_t reg_addr, uint8_t *data, + size_t data_len) { + std::error_code ec; + auto device = get_compatibility_device(dev_addr, ec); + if (!device) { + return false; + } + return device->read_register(reg_addr, data, data_len, ec); + } + + /// Read from a register on an addressed I2C device. + bool read_at_register_vector(const uint8_t dev_addr, const uint8_t reg_addr, + std::vector &data) { + return read_at_register(dev_addr, reg_addr, data.data(), data.size()); + } + + /// Read data from an addressed I2C device. + bool read(const uint8_t dev_addr, uint8_t *data, size_t data_len) { + std::error_code ec; + auto device = get_compatibility_device(dev_addr, ec); + if (!device) { + return false; + } + return device->read(data, data_len, ec); + } + + /// Read data from an addressed I2C device. + bool read_vector(const uint8_t dev_addr, std::vector &data) { + return read(dev_addr, data.data(), data.size()); + } + + /// Probe an addressed I2C device. + bool probe_device(const uint8_t dev_addr) { + std::lock_guard lock(mutex_); + if (!initialized_) { + logger_.error("not initialized"); + return false; + } + std::error_code ec; + auto previous_log_level = master_bus_.get_log_level(); + master_bus_.set_log_level(Logger::Verbosity::ERROR); + bool found = master_bus_.probe(dev_addr, static_cast(config_.timeout_ms), ec); + master_bus_.set_log_level(previous_log_level); + return found; + } + +protected: + static Logger::Verbosity get_compatibility_device_log_level(Logger::Verbosity log_level) { + return log_level == Logger::Verbosity::DEBUG ? Logger::Verbosity::DEBUG + : Logger::Verbosity::WARN; + } + + static I2cMasterBus::Config make_master_bus_config(const Config &config) { + I2cMasterBus::Config master_bus_config{ + .port = config.port, + .sda_io_num = config.sda_io_num, + .scl_io_num = config.scl_io_num, + .clk_speed = config.clk_speed, + .enable_internal_pullup = config.sda_pullup_en == GPIO_PULLUP_ENABLE || + config.scl_pullup_en == GPIO_PULLUP_ENABLE, + .log_level = config.log_level, + }; + return master_bus_config; + } + + DeviceConfig make_compatibility_device_config(uint16_t device_address) const { + return DeviceConfig{ + .device_address = device_address, + .timeout_ms = static_cast(config_.timeout_ms), + .scl_speed_hz = config_.clk_speed, + .log_level = get_compatibility_device_log_level(get_log_level()), + }; + } + + std::shared_ptr> get_compatibility_device(uint16_t device_address, + std::error_code &ec) { + std::lock_guard lock(mutex_); + if (!initialized_) { + logger_.error("not initialized"); + ec = std::make_error_code(std::errc::not_connected); + return nullptr; + } + + auto it = compatibility_devices_.find(device_address); + if (it != compatibility_devices_.end() && it->second) { + ec.clear(); + return it->second; + } + + auto device = + master_bus_.add_device(make_compatibility_device_config(device_address), ec); + if (!device) { + logger_.error("could not create compatibility I2C device at address {:#04x}: {}", + device_address, ec.message()); + return nullptr; + } + device->set_log_level(get_compatibility_device_log_level(get_log_level())); + compatibility_devices_[device_address] = device; + ec.clear(); + return device; + } + + Config config_; + mutable std::recursive_mutex mutex_; + I2cMasterBus master_bus_; + bool initialized_ = false; + std::map>> compatibility_devices_; +}; + +} // namespace espp + +#else +#error \ + "i2c.hpp included but neither CONFIG_ESPP_I2C_USE_LEGACY_API nor CONFIG_ESPP_I2C_USE_NEW_API is set. Please select the correct I2C API in KConfig." +#endif + // for printing the I2C::Config using fmt template <> struct fmt::formatter { constexpr auto parse(format_parse_context &ctx) const { return ctx.begin(); } @@ -332,7 +851,67 @@ template <> struct fmt::formatter { } }; -#else -#error \ - "i2c.hpp included but CONFIG_ESPP_I2C_USE_LEGACY_API is not set. Please select the correct I2C API in KConfig." -#endif +namespace espp { + +template +auto make_i2c_addressed_probe( + const std::shared_ptr> &device) { + return [device](uint8_t) -> bool { + if (!device) { + return false; + } + std::error_code ec; + return device->probe(ec); + }; +} + +template +auto make_i2c_addressed_write( + const std::shared_ptr> &device) { + return [device](uint8_t, const uint8_t *data, size_t len) -> bool { + if (!device) { + return false; + } + std::error_code ec; + return device->write(data, len, ec); + }; +} + +template +auto make_i2c_addressed_read( + const std::shared_ptr> &device) { + return [device](uint8_t, uint8_t *data, size_t len) -> bool { + if (!device) { + return false; + } + std::error_code ec; + return device->read(data, len, ec); + }; +} + +template +auto make_i2c_addressed_write_then_read( + const std::shared_ptr> &device) { + return [device](uint8_t, const uint8_t *write_data, size_t write_len, uint8_t *read_data, + size_t read_len) -> bool { + if (!device) { + return false; + } + std::error_code ec; + return device->write_read(write_data, write_len, read_data, read_len, ec); + }; +} + +template +auto make_i2c_addressed_read_register( + const std::shared_ptr> &device) { + return [device](uint8_t, RegisterType reg, uint8_t *data, size_t len) -> bool { + if (!device) { + return false; + } + std::error_code ec; + return device->read_register(reg, data, len, ec); + }; +} + +} // namespace espp diff --git a/components/i2c/include/i2c_master.hpp b/components/i2c/include/i2c_master.hpp index a63495a75..70fcacbf9 100644 --- a/components/i2c/include/i2c_master.hpp +++ b/components/i2c/include/i2c_master.hpp @@ -161,7 +161,7 @@ template class I2cMasterDevice : public BaseCo /// @brief Expose config for CLI menu /// @return Reference to config - const Config &config() const; + const Config &config() const { return config_; } protected: Config config_; diff --git a/components/i2c/include/i2c_master_device_menu.hpp b/components/i2c/include/i2c_master_device_menu.hpp index 544c67b4c..ff8741e85 100644 --- a/components/i2c/include/i2c_master_device_menu.hpp +++ b/components/i2c/include/i2c_master_device_menu.hpp @@ -4,6 +4,7 @@ #if defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) +#include #include #include #include @@ -40,32 +41,33 @@ template class I2cMasterDeviceMenu { std::unique_ptr get(std::string_view name = "i2c_device", std::string_view description = "I2c Device menu") { auto menu = std::make_unique(std::string(name), std::string(description)); + auto device = device_; // Set the log verbosity for the I2c device menu->Insert( "log", {"verbosity"}, - [this](std::ostream &out, const std::string &verbosity) -> void { - set_log_level(out, verbosity); + [device](std::ostream &out, const std::string &verbosity) -> void { + set_log_level(out, device, verbosity); }, "Set the log verbosity for the I2c device."); // Probe the device menu->Insert( - "probe", {}, [this](std::ostream &out) -> void { probe_device(out); }, + "probe", {}, [device](std::ostream &out) -> void { probe_device(out, device); }, "Probe for the device on the bus."); // Read from a register menu->Insert( "read", {"register", "length (number of bytes to read)"}, - [this](std::ostream &out, RegisterType reg, uint8_t len) -> void { - read_register(out, reg, len); + [device](std::ostream &out, RegisterType reg, uint8_t len) -> void { + read_register(out, device, reg, len); }, "Read len bytes from a register."); // Write to a register menu->Insert( "write", {"register", "data byte (hex)", "data byte (hex)", "..."}, - [this](std::ostream &out, const std::vector &args) -> void { + [device](std::ostream &out, const std::vector &args) -> void { if (args.size() < 2) { out << "Not enough arguments.\n"; return; @@ -74,7 +76,7 @@ template class I2cMasterDeviceMenu { std::vector data; std::transform(args.begin() + 1, args.end(), std::back_inserter(data), [](const std::string &s) -> uint8_t { return std::stoi(s, nullptr, 0); }); - write_register(out, reg, data); + write_register(out, device, reg, data); }, "Write bytes to a register."); @@ -85,21 +87,23 @@ template class I2cMasterDeviceMenu { /// @brief Set the log level for the I2c device. /// @param out The output stream to write to. /// @param verbosity The verbosity level to set. - void set_log_level(std::ostream &out, const std::string &verbosity) { - if (!device_) { + static void set_log_level(std::ostream &out, + const std::shared_ptr> &device, + const std::string &verbosity) { + if (!device) { out << "Device not set.\n"; return; } if (verbosity == "debug") { - device_->set_log_level(espp::Logger::Verbosity::DEBUG); + device->set_log_level(espp::Logger::Verbosity::DEBUG); } else if (verbosity == "info") { - device_->set_log_level(espp::Logger::Verbosity::INFO); + device->set_log_level(espp::Logger::Verbosity::INFO); } else if (verbosity == "warn") { - device_->set_log_level(espp::Logger::Verbosity::WARN); + device->set_log_level(espp::Logger::Verbosity::WARN); } else if (verbosity == "error") { - device_->set_log_level(espp::Logger::Verbosity::ERROR); + device->set_log_level(espp::Logger::Verbosity::ERROR); } else if (verbosity == "none") { - device_->set_log_level(espp::Logger::Verbosity::NONE); + device->set_log_level(espp::Logger::Verbosity::NONE); } else { out << "Invalid log level.\n"; return; @@ -109,13 +113,14 @@ template class I2cMasterDeviceMenu { /// @brief Probe for the device on the bus. /// @param out The output stream to write to. - void probe_device(std::ostream &out) { - if (!device_) { + static void probe_device(std::ostream &out, + const std::shared_ptr> &device) { + if (!device) { out << "Device not set.\n"; return; } std::error_code ec; - if (device_->probe(ec)) { + if (device->probe(50, ec)) { out << "Device found on the bus.\n"; } else { out << fmt::format("Device not found: {}\n", ec.message()); @@ -126,14 +131,16 @@ template class I2cMasterDeviceMenu { /// @param out The output stream to write to. /// @param reg The register to read from. /// @param len The number of bytes to read. - void read_register(std::ostream &out, RegisterType reg, uint8_t len) { - if (!device_) { + static void read_register(std::ostream &out, + const std::shared_ptr> &device, + RegisterType reg, uint8_t len) { + if (!device) { out << "Device not set.\n"; return; } std::error_code ec; std::vector data(len); - bool ok = device_->read_register(reg, data, ec); + bool ok = device->read_register(reg, data, ec); if (ok) { out << fmt::format("Read {} bytes from register {:#x}: {::#02x}\n", data.size(), reg, data); } else { @@ -145,13 +152,15 @@ template class I2cMasterDeviceMenu { /// @param out The output stream to write to. /// @param reg The register to write to. /// @param data The data to write. - void write_register(std::ostream &out, RegisterType reg, const std::vector &data) { - if (!device_) { + static void write_register(std::ostream &out, + const std::shared_ptr> &device, + RegisterType reg, const std::vector &data) { + if (!device) { out << "Device not set.\n"; return; } std::error_code ec; - bool ok = device_->write_register(reg, data, ec); + bool ok = device->write_register(reg, data, ec); if (ok) { out << fmt::format("Wrote {} bytes to register {:#x}: {::#02x}\n", data.size(), reg, data); } else { diff --git a/components/i2c/include/i2c_master_menu.hpp b/components/i2c/include/i2c_master_menu.hpp index e328caf6d..bb9c6f264 100644 --- a/components/i2c/include/i2c_master_menu.hpp +++ b/components/i2c/include/i2c_master_menu.hpp @@ -5,6 +5,7 @@ // Only include this menu if the new API is selected #if defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) +#include #include #include #include @@ -36,52 +37,53 @@ class I2cMasterMenu { std::unique_ptr get(std::string_view name = "i2c_master", std::string_view description = "I2c Master menu") { auto menu = std::make_unique(std::string(name), std::string(description)); + auto *bus = &bus_.get(); // Set the log verbosity for the I2c master bus menu->Insert( "log", {"verbosity"}, - [this](std::ostream &out, const std::string &verbosity) -> void { - set_log_level(out, verbosity); + [bus](std::ostream &out, const std::string &verbosity) -> void { + set_log_level(out, *bus, verbosity); }, "Set the log verbosity for the I2c master bus."); // Scan the bus for devices menu->Insert( - "scan", [this](std::ostream &out) -> void { scan_bus(out); }, + "scan", [bus](std::ostream &out) -> void { scan_bus(out, *bus); }, "Scan the I2c master bus for devices."); // Probe for a device (hexadecimal address string) menu->Insert( "probe", {"address (hex)"}, - [this](std::ostream &out, const std::string &address_string) -> void { - uint16_t address = std::stoi(address_string, nullptr, 16); - probe_device(out, address); + [bus](std::ostream &out, const std::string &address_string) -> void { + uint16_t address = std::stoi(address_string, nullptr, 0); + probe_device(out, *bus, address); }, "Probe for a device at a specific address, given as a hexadecimal string."); // Read from a device menu->Insert( "read", {"address (hex)", "register", "length (number of bytes to read)"}, - [this](std::ostream &out, const std::string &address_string, uint8_t reg, - uint8_t len) -> void { - uint16_t address = std::stoi(address_string, nullptr, 16); - read_device(out, address, reg, len); + [bus](std::ostream &out, const std::string &address_string, uint8_t reg, + uint8_t len) -> void { + uint16_t address = std::stoi(address_string, nullptr, 0); + read_device(out, *bus, address, reg, len); }, "Read len bytes from a device at a specific address and register."); // Write to a device menu->Insert( "write", {"address (hex)", "register (hex)", "data byte (hex)", "data byte (hex)", "..."}, - [this](std::ostream &out, const std::vector &args) -> void { + [bus](std::ostream &out, const std::vector &args) -> void { if (args.size() < 3) { out << "Not enough arguments.\n"; return; } - uint16_t address = std::stoi(args[0], nullptr, 16); + uint16_t address = std::stoi(args[0], nullptr, 0); std::vector data; std::transform(args.begin() + 1, args.end(), std::back_inserter(data), [](const std::string &s) -> uint8_t { return std::stoi(s, nullptr, 0); }); - write_device(out, address, data); + write_device(out, *bus, address, data); }, "Write bytes to a device at a specific address and register."); @@ -92,17 +94,18 @@ class I2cMasterMenu { /// @brief Set the log level for the I2c master bus. /// @param out The output stream to write to. /// @param verbosity The verbosity level to set. - void set_log_level(std::ostream &out, const std::string &verbosity) { + static void set_log_level(std::ostream &out, espp::I2cMasterBus &bus, + const std::string &verbosity) { if (verbosity == "debug") { - bus_.get().set_log_level(espp::Logger::Verbosity::DEBUG); + bus.set_log_level(espp::Logger::Verbosity::DEBUG); } else if (verbosity == "info") { - bus_.get().set_log_level(espp::Logger::Verbosity::INFO); + bus.set_log_level(espp::Logger::Verbosity::INFO); } else if (verbosity == "warn") { - bus_.get().set_log_level(espp::Logger::Verbosity::WARN); + bus.set_log_level(espp::Logger::Verbosity::WARN); } else if (verbosity == "error") { - bus_.get().set_log_level(espp::Logger::Verbosity::ERROR); + bus.set_log_level(espp::Logger::Verbosity::ERROR); } else if (verbosity == "none") { - bus_.get().set_log_level(espp::Logger::Verbosity::NONE); + bus.set_log_level(espp::Logger::Verbosity::NONE); } else { out << "Invalid log level.\n"; return; @@ -112,19 +115,19 @@ class I2cMasterMenu { /// @brief Scan the I2c master bus for devices. /// @param out The output stream to write to. - void scan_bus(std::ostream &out) { + static void scan_bus(std::ostream &out, espp::I2cMasterBus &bus) { out << "Scanning I2c master bus. This may take a while.\n"; - auto prev_log_level = bus_.get().get_log_level(); + auto prev_log_level = bus.get_log_level(); // NOTE: we turn off logging for this so we don't spam the console - bus_.get().set_log_level(espp::Logger::Verbosity::ERROR); + bus.set_log_level(espp::Logger::Verbosity::ERROR); std::vector found_addresses; - for (uint8_t address = 0; address < 128; address++) { + for (uint8_t address = 1; address < 128; address++) { std::error_code ec; - if (bus_.get().probe(address, ec)) { + if (bus.probe(address, 50, ec)) { found_addresses.push_back(address); } } - bus_.get().set_log_level(prev_log_level); + bus.set_log_level(prev_log_level); if (found_addresses.empty()) { out << "No devices found.\n"; } else { @@ -136,9 +139,9 @@ class I2cMasterMenu { /// @brief Probe for a device at a specific address. /// @param out The output stream to write to. /// @param address The address to probe for. - void probe_device(std::ostream &out, uint16_t address) { + static void probe_device(std::ostream &out, espp::I2cMasterBus &bus, uint16_t address) { std::error_code ec; - if (bus_.get().probe(address, ec)) { + if (bus.probe(address, 50, ec)) { out << fmt::format("Device found at address {:#02x}.\n", address); } else { out << fmt::format("No device found at address {:#02x}.\n", address); @@ -150,9 +153,10 @@ class I2cMasterMenu { /// @param address The address to read from. /// @param reg The register to read from. /// @param len The number of bytes to read. - void read_device(std::ostream &out, uint16_t address, uint8_t reg, uint8_t len) { + static void read_device(std::ostream &out, espp::I2cMasterBus &bus, uint16_t address, uint8_t reg, + uint8_t len) { std::error_code ec; - auto dev = bus_.get().add_device({.device_address = address}, ec); + auto dev = bus.add_device({.device_address = address, .timeout_ms = 50}, ec); if (!dev) { out << "Failed to create device.\n"; return; @@ -172,13 +176,14 @@ class I2cMasterMenu { /// @param out The output stream to write to. /// @param address The address to write to. /// @param data The data to write (first byte is register, rest is data). - void write_device(std::ostream &out, uint16_t address, const std::vector &data) { + static void write_device(std::ostream &out, espp::I2cMasterBus &bus, uint16_t address, + const std::vector &data) { if (data.empty()) { out << "No register/data provided.\n"; return; } std::error_code ec; - auto dev = bus_.get().add_device({.device_address = address}, ec); + auto dev = bus.add_device({.device_address = address, .timeout_ms = 50}, ec); if (!dev) { out << "Failed to create device.\n"; return; diff --git a/components/i2c/include/i2c_menu.hpp b/components/i2c/include/i2c_menu.hpp index 45c05f786..36547e6b7 100644 --- a/components/i2c/include/i2c_menu.hpp +++ b/components/i2c/include/i2c_menu.hpp @@ -2,9 +2,6 @@ #include -// Only include this menu if the legacy API is selected -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) || defined(_DOXYGEN_) - #include #include #include @@ -48,10 +45,9 @@ class I2cMenu { // scan the bus for devices // - // NOTE: this will take a while to run, as it will probe all 128 - // possible and the hard-coded timeout on the I2C (inside ESP-IDF) is 1 - // second (I2C_CMD_ALIVE_INTERVAL_TICK within - // esp-idf/components/driver/i2c/i2c.c). + // NOTE: this will probe the full 7-bit address space, so it can still take + // noticeable time depending on the configured bus timeout and any missing + // pullups or disconnected devices. i2c_menu->Insert( "scan", [this](std::ostream &out) -> void { scan_bus(out); }, "Scan the I2c bus for devices."); @@ -146,13 +142,10 @@ class I2cMenu { /// @brief Scan the I2c bus for devices. /// @param out The output stream to write to. - /// @note This will take a while to run, as it will probe all 128 possible - /// addresses and the hard-coded timeout on the I2C (inside ESP-IDF) is - /// 1 second (I2C_CMD_ALIVE_INTERVAL_TICK within - /// esp-idf/components/driver/i2c/i2c.c). + /// @note This probes the full 7-bit address space, so it can still take a + /// while depending on the configured timeout and bus conditions. void scan_bus(std::ostream &out) { - out << "Scanning I2c bus. This may take a while if you have not updated your ESP-IDF's " - "I2C_CMD_ALIVE_INTERVAL_TICK.\n"; + out << "Scanning I2c bus. This may take a while.\n"; std::vector found_addresses; for (uint8_t address = 1; address < 128; address++) { if (i2c_.get().probe_device(address)) { @@ -213,5 +206,3 @@ class I2cMenu { std::reference_wrapper i2c_; }; } // namespace espp - -#endif // CONFIG_ESPP_I2C_USE_LEGACY_API diff --git a/components/i2c/include/i2c_slave.hpp b/components/i2c/include/i2c_slave.hpp index 212170feb..a3c6884ba 100644 --- a/components/i2c/include/i2c_slave.hpp +++ b/components/i2c/include/i2c_slave.hpp @@ -5,6 +5,7 @@ // Only include this header if the new API is selected #if defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) +#include #include #include #include @@ -12,9 +13,11 @@ #include #include +#include #include #include "base_component.hpp" +#include "task.hpp" extern "C" { #include @@ -24,25 +27,31 @@ extern "C" { namespace espp { -/// @brief I2C Slave Device (C++ wrapper for ESP-IDF new I2C slave API) +/// @brief I2C slave device wrapper for ESP-IDF's callback-driven slave API. /// @details -/// This class is a wrapper around the ESP-IDF I2C slave device API. -/// It provides thread-safe, modern C++ access to I2C slave device operations. +/// ESP-IDF's slave driver is event/callback based: master writes arrive through +/// receive callbacks, and master reads can trigger request callbacks when the +/// slave transmit FIFO needs more data. This wrapper provides: /// -/// @note There is no example for this yet as this code is untested. +/// - queued `read()` access to complete master-write transactions +/// - blocking `write()` access for staging data back to the master +/// - task-context request / receive callbacks so user code does not have to run +/// inside the ISR callback context +/// +/// @note No dedicated example exists yet. /// /// Usage: -/// - Construct with a config -/// - Use read, write, and callback registration methods -/// - All methods are thread-safe +/// - Construct with a config, then call `init()` +/// - Call `read()` to receive the next complete master-write transaction +/// - Call `write()` to stage response bytes for the next master read +/// - Register callbacks if you want task-context notifications /// /// \note This class is intended for use with the new ESP-IDF I2C slave API (>=5.4.0) class I2cSlaveDevice : public BaseComponent { public: - using RequestCallback = - std::function; ///< Callback for data requests + using RequestCallback = std::function; ///< Callback for master read requests. using ReceiveCallback = - std::function; ///< Callback for data received + std::function; ///< Callback for master writes. /// @brief Callbacks for I2C slave events struct Callbacks { @@ -52,14 +61,18 @@ class I2cSlaveDevice : public BaseComponent { /// @brief Configuration for I2C Slave Device struct Config { - int port = 0; ///< I2C port number - int sda_io_num = -1; ///< SDA pin - int scl_io_num = -1; ///< SCL pin - uint16_t slave_address = 0; ///< I2C slave address - i2c_addr_bit_len_t addr_bit_len = I2C_ADDR_BIT_LEN_7; ///< Address bit length - uint32_t clk_speed = 100000; ///< I2C clock speed in hertz - bool enable_internal_pullup = true; ///< Enable internal pullups - int intr_priority = 0; ///< Interrupt priority + int port = 0; ///< I2C port number + int sda_io_num = -1; ///< SDA pin + int scl_io_num = -1; ///< SCL pin + uint16_t slave_address = 0; ///< I2C slave address + i2c_addr_bit_len_t addr_bit_len = I2C_ADDR_BIT_LEN_7; ///< Address bit length + int timeout_ms = 10; ///< Read/write timeout in milliseconds + uint32_t receive_buffer_depth = 256; ///< Max bytes per received transaction + uint32_t send_buffer_depth = 256; ///< Depth of driver TX buffer + size_t event_queue_depth = 8; ///< Number of queued request/receive events + bool enable_internal_pullup = true; ///< Enable internal pullups + int intr_priority = 0; ///< Interrupt priority + espp::Task::BaseConfig task_config = {.name = "I2C Slave Task"}; Logger::Verbosity log_level = Logger::Verbosity::WARN; ///< Logger verbosity }; @@ -88,9 +101,9 @@ class I2cSlaveDevice : public BaseComponent { bool write(const uint8_t *data, size_t len, std::error_code &ec); /// @brief Read data from the master /// @param data Pointer to buffer - /// @param len Length to read + /// @param len Maximum transaction length to read /// @param ec Error code output - /// @return True if successful + /// @return True if a complete master-write transaction was received bool read(uint8_t *data, size_t len, std::error_code &ec); /// @brief Register callbacks for slave events @@ -101,18 +114,40 @@ class I2cSlaveDevice : public BaseComponent { /// @brief Expose config for CLI menu /// @return Reference to config - const Config &config() const; + const Config &config() const { return config_; } protected: + enum class EventType : uint8_t { STOP, REQUEST, RECEIVE }; + + struct Event { + EventType type; + }; + + static bool IRAM_ATTR request_callback_trampoline(i2c_slave_dev_handle_t i2c_slave, + const i2c_slave_request_event_data_t *evt_data, + void *user_data); + static bool IRAM_ATTR receive_callback_trampoline(i2c_slave_dev_handle_t i2c_slave, + const i2c_slave_rx_done_event_data_t *evt_data, + void *user_data); + + static size_t message_buffer_capacity(size_t max_message_size, size_t max_messages); + static TickType_t timeout_ticks(int timeout_ms); + + bool event_task_callback(std::mutex &m, std::condition_variable &cv, bool ¬ified); + void log_pending_overflows(); + Config config_; i2c_slave_dev_handle_t dev_handle_ = nullptr; bool initialized_ = false; std::recursive_mutex mutex_; Callbacks callbacks_{}; - // FreeRTOS queue for request/receive events QueueHandle_t event_queue_ = nullptr; - // TODO: create a espp::Task which waits on a queue to be notified from ISR - // for request/receive events + MessageBufferHandle_t read_buffer_ = nullptr; + MessageBufferHandle_t callback_buffer_ = nullptr; + std::unique_ptr event_task_; + std::atomic read_buffer_overflowed_{false}; + std::atomic callback_buffer_overflowed_{false}; + std::atomic event_queue_overflowed_{false}; }; } // namespace espp diff --git a/components/i2c/include/i2c_slave_menu.hpp b/components/i2c/include/i2c_slave_menu.hpp index 1e51c3783..156eaaec6 100644 --- a/components/i2c/include/i2c_slave_menu.hpp +++ b/components/i2c/include/i2c_slave_menu.hpp @@ -5,6 +5,7 @@ // Only include this menu if the new API is selected #if defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) +#include #include #include #include @@ -19,16 +20,14 @@ namespace espp { /// @brief CLI menu for I2C Slave Device /// @details -/// This class provides a command-line interface (CLI) menu for interacting with the I2C slave -/// device using the new ESP-IDF I2C slave API. -/// -/// \section i2c_slave_menu_ex1 Example -/// \snippet i2c_example.cpp NEW SLAVE API MENU +/// This class provides a command-line interface (CLI) menu for interacting with +/// an I2C slave device using the new ESP-IDF I2C slave API. /// /// Usage: /// - Construct with a shared pointer to an I2cSlaveDevice -/// - Use add_to_menu() to add I2C slave commands to a CLI menu -/// - Supports reading, writing, and callback registration +/// - Use get() to add I2C slave commands to a CLI menu +/// - Supports reading queued master-write transactions and staging slave +/// responses back to the master /// /// \note This class is intended for use with the new ESP-IDF I2C slave API (>=5.4.0) class I2cSlaveMenu { @@ -69,7 +68,10 @@ class I2cSlaveMenu { menu->Insert( "write", {"data byte", "data byte", "..."}, - [this](std::ostream &out, const std::vector &data) -> void { + [this](std::ostream &out, const std::vector &args) -> void { + std::vector data; + std::transform(args.begin(), args.end(), std::back_inserter(data), + [](const std::string &s) -> uint8_t { return std::stoi(s, nullptr, 0); }); std::error_code ec; bool success = device_->write(data.data(), data.size(), ec); if (success) { diff --git a/components/i2c/src/i2c_master.cpp b/components/i2c/src/i2c_master.cpp index a5dcbffe8..f39f83e10 100644 --- a/components/i2c/src/i2c_master.cpp +++ b/components/i2c/src/i2c_master.cpp @@ -6,6 +6,27 @@ #include "i2c_master.hpp" namespace espp { +namespace { +std::error_code make_error_code(esp_err_t err) { + switch (err) { + case ESP_OK: + return {}; + case ESP_ERR_INVALID_ARG: + return std::make_error_code(std::errc::invalid_argument); + case ESP_ERR_NO_MEM: + return std::make_error_code(std::errc::not_enough_memory); + case ESP_ERR_TIMEOUT: + return std::make_error_code(std::errc::timed_out); + case ESP_ERR_NOT_FOUND: + case ESP_ERR_INVALID_RESPONSE: + return std::make_error_code(std::errc::no_such_device_or_address); + case ESP_ERR_INVALID_STATE: + return std::make_error_code(std::errc::operation_not_permitted); + default: + return std::make_error_code(std::errc::io_error); + } +} +} // namespace I2cMasterBus::I2cMasterBus(const Config &config) : BaseComponent("I2cMasterBus", config.log_level) @@ -97,12 +118,12 @@ bool I2cMasterBus::probe(uint16_t device_address, int32_t timeout_ms, std::error } else if (err == ESP_ERR_TIMEOUT) { logger_.error("Probe timeout for device 0x{:02x} on bus {}: {}", device_address, config_.port, esp_err_to_name(err)); - ec = std::make_error_code(std::errc::timed_out); + ec = make_error_code(err); return false; } logger_.warn("Device 0x{:02x} not found on bus {}: {}", device_address, config_.port, esp_err_to_name(err)); - ec = std::make_error_code(std::errc::io_error); + ec = make_error_code(err); return false; } @@ -303,37 +324,26 @@ bool I2cMasterDevice::read_register(RegisterType reg, std::vector< } template bool I2cMasterDevice::probe(std::error_code &ec) { - logger_.info("Probing for device at address 0x{:02x}", config_.device_address); - if (!bus_handle_) { - logger_.error("Bus handle is null"); - ec = std::make_error_code(std::errc::not_connected); - return false; - } - esp_err_t err = i2c_master_probe(bus_handle_, config_.device_address, config_.timeout_ms); - if (err == ESP_OK) { - logger_.info("Device 0x{:02x} found", config_.device_address); - ec.clear(); - return true; - } - logger_.warn("Device 0x{:02x} not found: {}", config_.device_address, esp_err_to_name(err)); - ec = std::make_error_code(std::errc::io_error); - return false; + return probe(config_.timeout_ms, ec); } template bool I2cMasterDevice::probe(int32_t timeout_ms, std::error_code &ec) { - // NOTE: This uses the bus_handle_ provided at construction. If the bus is deleted, this will - // fail. - if (!bus_handle_) { + logger_.info("Probing for device at address 0x{:02x}", config_.device_address); + std::lock_guard lock(mutex_); + if (!initialized_ || !bus_handle_ || !dev_handle_) { + logger_.error("Device not initialized or handle is null"); ec = std::make_error_code(std::errc::not_connected); return false; } esp_err_t err = i2c_master_probe(bus_handle_, config_.device_address, timeout_ms); if (err == ESP_OK) { + logger_.info("Device 0x{:02x} found", config_.device_address); ec.clear(); return true; } - ec = std::make_error_code(std::errc::io_error); + logger_.warn("Device 0x{:02x} not found: {}", config_.device_address, esp_err_to_name(err)); + ec = make_error_code(err); return false; } diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index d05f3a367..495ff3e26 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -1,11 +1,37 @@ #include -// Only compile this file if the new API is selected #if defined(CONFIG_ESPP_I2C_USE_NEW_API) +#include +#include +#include + +#include +#include + #include "i2c_slave.hpp" namespace espp { +namespace { +std::error_code make_error_code(esp_err_t err) { + switch (err) { + case ESP_OK: + return {}; + case ESP_ERR_INVALID_ARG: + return std::make_error_code(std::errc::invalid_argument); + case ESP_ERR_NO_MEM: + return std::make_error_code(std::errc::not_enough_memory); + case ESP_ERR_TIMEOUT: + return std::make_error_code(std::errc::timed_out); + case ESP_ERR_INVALID_STATE: + return std::make_error_code(std::errc::operation_not_permitted); + case ESP_ERR_NOT_FOUND: + return std::make_error_code(std::errc::no_such_device_or_address); + default: + return std::make_error_code(std::errc::io_error); + } +} +} // namespace I2cSlaveDevice::I2cSlaveDevice(const Config &config) : BaseComponent("I2cSlaveDevice", config.log_level) @@ -14,6 +40,22 @@ I2cSlaveDevice::I2cSlaveDevice(const Config &config) I2cSlaveDevice::~I2cSlaveDevice() { std::error_code ec; deinit(ec); + if (ec) { + logger_.error("failed to deinitialize slave device: {}", ec.message()); + } +} + +size_t I2cSlaveDevice::message_buffer_capacity(size_t max_message_size, size_t max_messages) { + size_t message_size = std::max(1, max_message_size); + size_t num_messages = std::max(1, max_messages); + return num_messages * (message_size + sizeof(size_t)); +} + +TickType_t I2cSlaveDevice::timeout_ticks(int timeout_ms) { + if (timeout_ms < 0) { + return portMAX_DELAY; + } + return pdMS_TO_TICKS(timeout_ms); } bool I2cSlaveDevice::init(std::error_code &ec) { @@ -22,98 +64,334 @@ bool I2cSlaveDevice::init(std::error_code &ec) { ec = std::make_error_code(std::errc::already_connected); return false; } + if (config_.sda_io_num < 0 || config_.scl_io_num < 0 || config_.receive_buffer_depth == 0 || + config_.send_buffer_depth == 0 || config_.event_queue_depth == 0) { + ec = std::make_error_code(std::errc::invalid_argument); + return false; + } + + event_queue_ = xQueueCreate(config_.event_queue_depth, sizeof(Event)); + if (!event_queue_) { + ec = std::make_error_code(std::errc::not_enough_memory); + return false; + } + + read_buffer_ = xMessageBufferCreate( + message_buffer_capacity(config_.receive_buffer_depth, config_.event_queue_depth)); + callback_buffer_ = xMessageBufferCreate( + message_buffer_capacity(config_.receive_buffer_depth, config_.event_queue_depth)); + if (!read_buffer_ || !callback_buffer_) { + if (read_buffer_) { + vMessageBufferDelete(read_buffer_); + read_buffer_ = nullptr; + } + if (callback_buffer_) { + vMessageBufferDelete(callback_buffer_); + callback_buffer_ = nullptr; + } + vQueueDelete(event_queue_); + event_queue_ = nullptr; + ec = std::make_error_code(std::errc::not_enough_memory); + return false; + } + i2c_slave_config_t slave_cfg = {}; slave_cfg.i2c_port = config_.port; slave_cfg.sda_io_num = static_cast(config_.sda_io_num); slave_cfg.scl_io_num = static_cast(config_.scl_io_num); slave_cfg.clk_source = I2C_CLK_SRC_DEFAULT; + slave_cfg.send_buf_depth = config_.send_buffer_depth; + slave_cfg.receive_buf_depth = config_.receive_buffer_depth; slave_cfg.slave_addr = config_.slave_address; - slave_cfg.addr_bit_len = I2C_ADDR_BIT_LEN_7; - slave_cfg.send_buf_depth = 128; -#if CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 - slave_cfg.flags.enable_internal_pullup = config_.enable_internal_pullup; -#endif + slave_cfg.addr_bit_len = config_.addr_bit_len; + slave_cfg.intr_priority = config_.intr_priority; slave_cfg.flags.allow_pd = 0; + slave_cfg.flags.enable_internal_pullup = config_.enable_internal_pullup; + esp_err_t err = i2c_new_slave_device(&slave_cfg, &dev_handle_); if (err != ESP_OK) { - ec = std::make_error_code(std::errc::io_error); + logger_.error("could not create I2C slave device: {}", esp_err_to_name(err)); + vMessageBufferDelete(read_buffer_); + vMessageBufferDelete(callback_buffer_); + vQueueDelete(event_queue_); + read_buffer_ = nullptr; + callback_buffer_ = nullptr; + event_queue_ = nullptr; + ec = make_error_code(err); return false; } + + i2c_slave_event_callbacks_t cbs = {}; + cbs.on_request = &I2cSlaveDevice::request_callback_trampoline; + cbs.on_receive = &I2cSlaveDevice::receive_callback_trampoline; + err = i2c_slave_register_event_callbacks(dev_handle_, &cbs, this); + if (err != ESP_OK) { + logger_.error("could not register I2C slave callbacks: {}", esp_err_to_name(err)); + i2c_del_slave_device(dev_handle_); + dev_handle_ = nullptr; + vMessageBufferDelete(read_buffer_); + vMessageBufferDelete(callback_buffer_); + vQueueDelete(event_queue_); + read_buffer_ = nullptr; + callback_buffer_ = nullptr; + event_queue_ = nullptr; + ec = make_error_code(err); + return false; + } + + event_task_ = espp::Task::make_unique( + {.callback = std::bind(&I2cSlaveDevice::event_task_callback, this, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3), + .task_config = config_.task_config, + .log_level = config_.log_level}); + if (!event_task_ || !event_task_->start()) { + logger_.error("could not start I2C slave event task"); + i2c_del_slave_device(dev_handle_); + dev_handle_ = nullptr; + event_task_.reset(); + vMessageBufferDelete(read_buffer_); + vMessageBufferDelete(callback_buffer_); + vQueueDelete(event_queue_); + read_buffer_ = nullptr; + callback_buffer_ = nullptr; + event_queue_ = nullptr; + ec = std::make_error_code(std::errc::resource_unavailable_try_again); + return false; + } + + read_buffer_overflowed_ = false; + callback_buffer_overflowed_ = false; + event_queue_overflowed_ = false; initialized_ = true; ec.clear(); return true; } bool I2cSlaveDevice::deinit(std::error_code &ec) { - std::lock_guard lock(mutex_); - if (!initialized_) - return true; - if (dev_handle_) { - esp_err_t err = i2c_del_slave_device(dev_handle_); + i2c_slave_dev_handle_t dev_handle = nullptr; + { + std::lock_guard lock(mutex_); + if (!initialized_) { + ec.clear(); + return true; + } + initialized_ = false; + dev_handle = dev_handle_; + dev_handle_ = nullptr; + } + + if (dev_handle) { + esp_err_t err = i2c_del_slave_device(dev_handle); if (err != ESP_OK) { - ec = std::make_error_code(std::errc::io_error); + logger_.error("could not delete I2C slave device: {}", esp_err_to_name(err)); + ec = make_error_code(err); return false; } - dev_handle_ = nullptr; } - initialized_ = false; + + if (event_queue_) { + Event stop_event{EventType::STOP}; + xQueueSend(event_queue_, &stop_event, 0); + } + if (event_task_) { + event_task_->stop(); + event_task_.reset(); + } + + std::lock_guard lock(mutex_); + if (event_queue_) { + vQueueDelete(event_queue_); + event_queue_ = nullptr; + } + if (read_buffer_) { + vMessageBufferDelete(read_buffer_); + read_buffer_ = nullptr; + } + if (callback_buffer_) { + vMessageBufferDelete(callback_buffer_); + callback_buffer_ = nullptr; + } + callbacks_ = {}; ec.clear(); return true; } bool I2cSlaveDevice::write(const uint8_t *data, size_t len, std::error_code &ec) { -#if CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 std::lock_guard lock(mutex_); if (!initialized_ || !dev_handle_) { ec = std::make_error_code(std::errc::not_connected); return false; } + if (len == 0) { + ec.clear(); + return true; + } + if (!data) { + ec = std::make_error_code(std::errc::invalid_argument); + return false; + } + uint32_t write_len = 0; - esp_err_t err = i2c_slave_write(dev_handle_, data, len, &write_len, -1); - if (err != ESP_OK || write_len != len) { + esp_err_t err = i2c_slave_write(dev_handle_, data, len, &write_len, config_.timeout_ms); + if (err != ESP_OK) { + logger_.error("I2C slave write failed: {}", esp_err_to_name(err)); + ec = make_error_code(err); + return false; + } + if (write_len != len) { + logger_.error("I2C slave write was truncated: wrote {} of {} bytes", write_len, len); ec = std::make_error_code(std::errc::io_error); return false; } + ec.clear(); return true; -#else - logger_.error("I2cSlaveDevice::write() is not supported in this configuration. Please ENABLE " - "CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2."); - ec = std::make_error_code(std::errc::not_supported); - return false; -#endif } bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { -#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 std::lock_guard lock(mutex_); - if (!initialized_ || !dev_handle_) { + if (!initialized_ || !read_buffer_) { ec = std::make_error_code(std::errc::not_connected); return false; } - esp_err_t err = i2c_slave_receive(dev_handle_, data, len); - if (err != ESP_OK) { - ec = std::make_error_code(std::errc::io_error); + if (len == 0) { + ec.clear(); + return true; + } + if (!data) { + ec = std::make_error_code(std::errc::invalid_argument); + return false; + } + + log_pending_overflows(); + + size_t read_len = + xMessageBufferReceive(read_buffer_, data, len, timeout_ticks(config_.timeout_ms)); + if (read_len == 0) { + size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); + if (next_length > len) { + logger_.error("I2C slave read buffer too small for next transaction (need {}, have {})", + next_length, len); + ec = std::make_error_code(std::errc::message_size); + } else if (read_buffer_overflowed_.exchange(false)) { + logger_.error("I2C slave dropped received data because the read queue overflowed"); + ec = std::make_error_code(std::errc::no_buffer_space); + } else { + ec = std::make_error_code(std::errc::timed_out); + } return false; } + ec.clear(); return true; -#else - logger_.error("I2cSlaveDevice::read() is not supported in this configuration. Please DISABLE " - "CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2."); - ec = std::make_error_code(std::errc::not_supported); - return false; -#endif } bool I2cSlaveDevice::register_callbacks(const Callbacks &cb, std::error_code &ec) { std::lock_guard lock(mutex_); callbacks_ = cb; - // TODO: Implement registration with ESP-IDF and dispatch in task context ec.clear(); return true; } +bool IRAM_ATTR I2cSlaveDevice::request_callback_trampoline(i2c_slave_dev_handle_t, + const i2c_slave_request_event_data_t *, + void *user_data) { + auto *device = static_cast(user_data); + if (!device || !device->event_queue_) { + return false; + } + + BaseType_t task_woken = pdFALSE; + Event event{EventType::REQUEST}; + if (xQueueSendFromISR(device->event_queue_, &event, &task_woken) != pdPASS) { + device->event_queue_overflowed_ = true; + } + return task_woken == pdTRUE; +} + +bool IRAM_ATTR I2cSlaveDevice::receive_callback_trampoline( + i2c_slave_dev_handle_t, const i2c_slave_rx_done_event_data_t *evt_data, void *user_data) { + auto *device = static_cast(user_data); + if (!device || !device->event_queue_ || !evt_data || !evt_data->buffer || evt_data->length == 0) { + return false; + } + + BaseType_t task_woken = pdFALSE; + size_t sent = xMessageBufferSendFromISR(device->read_buffer_, evt_data->buffer, evt_data->length, + &task_woken); + if (sent != evt_data->length) { + device->read_buffer_overflowed_ = true; + } + + sent = xMessageBufferSendFromISR(device->callback_buffer_, evt_data->buffer, evt_data->length, + &task_woken); + if (sent != evt_data->length) { + device->callback_buffer_overflowed_ = true; + } + + Event event{EventType::RECEIVE}; + if (xQueueSendFromISR(device->event_queue_, &event, &task_woken) != pdPASS) { + device->event_queue_overflowed_ = true; + } + return task_woken == pdTRUE; +} + +void I2cSlaveDevice::log_pending_overflows() { + if (read_buffer_overflowed_.exchange(false)) { + logger_.error("I2C slave dropped received data because the read queue overflowed"); + } + if (callback_buffer_overflowed_.exchange(false)) { + logger_.error("I2C slave dropped callback data because the callback queue overflowed"); + } + if (event_queue_overflowed_.exchange(false)) { + logger_.error("I2C slave dropped events because the event queue overflowed"); + } +} + +bool I2cSlaveDevice::event_task_callback(std::mutex &, std::condition_variable &, bool &) { + Event event{}; + if (!event_queue_ || !xQueueReceive(event_queue_, &event, portMAX_DELAY)) { + return false; + } + + log_pending_overflows(); + + switch (event.type) { + case EventType::STOP: + return true; + case EventType::REQUEST: { + RequestCallback callback; + { + std::lock_guard lock(mutex_); + callback = callbacks_.on_request; + } + if (callback) { + callback(); + } + break; + } + case EventType::RECEIVE: { + std::vector data(config_.receive_buffer_depth); + size_t length = + callback_buffer_ ? xMessageBufferReceive(callback_buffer_, data.data(), data.size(), 0) : 0; + if (length == 0) { + break; + } + ReceiveCallback callback; + { + std::lock_guard lock(mutex_); + callback = callbacks_.on_receive; + } + if (callback) { + callback(data.data(), length); + } + break; + } + } + + return false; +} + } // namespace espp #endif // CONFIG_ESPP_I2C_USE_NEW_API diff --git a/components/icm20948/example/main/icm20948_example.cpp b/components/icm20948/example/main/icm20948_example.cpp index 74a962d8d..d07511366 100644 --- a/components/icm20948/example/main/icm20948_example.cpp +++ b/components/icm20948/example/main/icm20948_example.cpp @@ -42,6 +42,17 @@ extern "C" void app_main(void) { // set the address of the IMU uint8_t imu_address = found_addresses[0]; + std::error_code ec; + auto imu_device = + i2c.add_device({.device_address = imu_address, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!imu_device) { + logger.error("Failed to initialize ICM20948 I2C device: {}", ec.message()); + return; + } // make the orientation filter to compute orientation from accel + gyro static constexpr float angle_noise = 0.001f; @@ -88,10 +99,8 @@ extern "C" void app_main(void) { // make the IMU config Imu::Config config{ .device_address = imu_address, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_2G, diff --git a/components/icm42607/example/main/icm42607_example.cpp b/components/icm42607/example/main/icm42607_example.cpp index 136c1af95..6da3da901 100644 --- a/components/icm42607/example/main/icm42607_example.cpp +++ b/components/icm42607/example/main/icm42607_example.cpp @@ -26,6 +26,17 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = i2c_clock_speed}); + std::error_code ec; + auto imu_device = + i2c.add_device({.device_address = Imu::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!imu_device) { + logger.error("Failed to initialize ICM42607 I2C device: {}", ec.message()); + return; + } // make the orientation filter to compute orientation from accel + gyro static constexpr float angle_noise = 0.001f; @@ -70,10 +81,8 @@ extern "C" void app_main(void) { // make the IMU config Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_2G, @@ -88,7 +97,6 @@ extern "C" void app_main(void) { // create the IMU logger.info("Creating IMU"); Imu imu(config); - std::error_code ec; // turn on DMP logger.info("Turning on DMP"); diff --git a/components/ina226/example/main/ina226_example.cpp b/components/ina226/example/main/ina226_example.cpp index 7747ee1c7..23cb820a5 100644 --- a/components/ina226/example/main/ina226_example.cpp +++ b/components/ina226/example/main/ina226_example.cpp @@ -35,6 +35,17 @@ extern "C" void app_main(void) { #endif logger.info("Using INA226 address 0x{:02X}", address); + std::error_code ec; + auto ina_device = + i2c.add_device({.device_address = address, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!ina_device) { + logger.info("INA226 I2C device initialization failed: {}", ec.message()); + return; + } // Configure INA226 with reasonable defaults and known shunt resistor espp::Ina226 ina({ @@ -45,19 +56,13 @@ extern "C" void app_main(void) { .mode = espp::Ina226::Mode::SHUNT_BUS_CONT, .current_lsb = 0.001f, // 1mA / LSB .shunt_resistance_ohms = 0.1f, // 0.1 ohm - .probe = std::bind(&espp::I2c::probe_device, &i2c, std::placeholders::_1), - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - .write_then_read = - std::bind(&espp::I2c::write_read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + .probe = espp::make_i2c_addressed_probe(ina_device), + .write = espp::make_i2c_addressed_write(ina_device), + .read_register = espp::make_i2c_addressed_read_register(ina_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(ina_device), .log_level = espp::Logger::Verbosity::WARN, }); - std::error_code ec; // Optional explicit initialize ina.initialize(ec); if (ec) { diff --git a/components/kts1622/example/main/kts1622_example.cpp b/components/kts1622/example/main/kts1622_example.cpp index 93dc8fb7e..5ae46f9ca 100644 --- a/components/kts1622/example/main/kts1622_example.cpp +++ b/components/kts1622/example/main/kts1622_example.cpp @@ -25,6 +25,17 @@ extern "C" void app_main(void) { .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = 1000 * 1000, // 1MHz }); + std::error_code ec; + auto kts1622_device = + i2c.add_device({.device_address = espp::Kts1622::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!kts1622_device) { + logger.error("kts1622 I2C device initialization failed: {}", ec.message()); + return; + } // now make the kts1622 which handles GPIO espp::Kts1622 kts1622( @@ -33,14 +44,10 @@ extern "C" void app_main(void) { .port_0_direction_mask = 0b11111111, // set P1_0 - P1_7 to be inputs .port_1_direction_mask = 0b11111111, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .write_then_read = - std::bind(&espp::I2c::write_read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + .write = espp::make_i2c_addressed_write(kts1622_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(kts1622_device), .auto_init = false, .log_level = espp::Logger::Verbosity::INFO}); - std::error_code ec; // Initialized separately from the constructor since we set auto_init to false if (!kts1622.initialize(ec)) { logger.error("kts1622 initialization failed: {}", ec.message()); diff --git a/components/lp5817/example/main/lp5817_example.cpp b/components/lp5817/example/main/lp5817_example.cpp index 0f1c519e3..11f228b39 100644 --- a/components/lp5817/example/main/lp5817_example.cpp +++ b/components/lp5817/example/main/lp5817_example.cpp @@ -22,12 +22,21 @@ extern "C" void app_main(void) { .clk_speed = CONFIG_EXAMPLE_I2C_CLOCK_SPEED_HZ, .log_level = espp::Logger::Verbosity::INFO, }); + std::error_code ec; + auto lp_device = i2c.add_device({.device_address = espp::Lp5817::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!lp_device) { + logger.error("Failed to initialize LP5817 I2C device: {}", ec.message()); + return; + } espp::Lp5817 lp({.device_address = espp::Lp5817::DEFAULT_ADDRESS, - .write = std::bind_front(&espp::I2c::write, &i2c), - .write_then_read = std::bind_front(&espp::I2c::write_read, &i2c), + .write = espp::make_i2c_addressed_write(lp_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(lp_device), .log_level = espp::Logger::Verbosity::WARN}); - std::error_code ec; if (!lp.initialize(ec)) { logger.error("Failed to initialize LP5817: {}", ec.message()); return; diff --git a/components/lsm6dso/example/main/lsm6dso_example.cpp b/components/lsm6dso/example/main/lsm6dso_example.cpp index e71cb9f86..227cccaa7 100644 --- a/components/lsm6dso/example/main/lsm6dso_example.cpp +++ b/components/lsm6dso/example/main/lsm6dso_example.cpp @@ -28,6 +28,17 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = i2c_clock_speed}); + std::error_code ec; + auto imu_device = + i2c.add_device({.device_address = Imu::DEFAULT_I2C_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!imu_device) { + logger.error("Failed to initialize LSM6DSO I2C device: {}", ec.message()); + return; + } // make the orientation filter to compute orientation from accel + gyro static constexpr float angle_noise = 0.001f; @@ -72,10 +83,8 @@ extern "C" void app_main(void) { // IMU config Imu::Config config{ .device_address = Imu::DEFAULT_I2C_ADDRESS, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = { .accel_range = Imu::AccelRange::RANGE_2G, @@ -91,8 +100,6 @@ extern "C" void app_main(void) { logger.info("Creating LSM6DSO IMU"); Imu imu(config); - std::error_code ec; - // set the accel / gyro on-chip filters static constexpr uint8_t accel_filter_bandwidth = 0b001; // ODR / 10 static constexpr uint8_t gyro_lpf_bandwidth = 0b001; // ODR / 3 diff --git a/components/m5stack-tab5/src/audio.cpp b/components/m5stack-tab5/src/audio.cpp index 85470eb1e..18db44e8f 100644 --- a/components/m5stack-tab5/src/audio.cpp +++ b/components/m5stack-tab5/src/audio.cpp @@ -24,15 +24,37 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, return true; } + std::error_code ec; + auto es8388_device = internal_i2c_.add_device( + { + .device_address = 0x10, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!es8388_device) { + logger_.error("Could not initialize ES8388 I2C device: {}", ec.message()); + return false; + } + auto es7210_device = internal_i2c_.add_device( + { + .device_address = 0x40, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!es7210_device) { + logger_.error("Could not initialize ES7210 I2C device: {}", ec.message()); + return false; + } + // Wire codec register access over internal I2C - set_es8388_write(std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3)); - set_es8388_read(std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); - set_es7210_write(std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3)); - set_es7210_read(std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); + set_es8388_write(espp::make_i2c_addressed_write(es8388_device)); + set_es8388_read(espp::make_i2c_addressed_read_register(es8388_device)); + set_es7210_write(espp::make_i2c_addressed_write(es7210_device)); + set_es7210_read(espp::make_i2c_addressed_read_register(es7210_device)); // I2S standard channel for TX (playback) logger_.info("Creating I2S channel for playback (TX)"); diff --git a/components/m5stack-tab5/src/imu.cpp b/components/m5stack-tab5/src/imu.cpp index a1e9180e3..6e55fd204 100644 --- a/components/m5stack-tab5/src/imu.cpp +++ b/components/m5stack-tab5/src/imu.cpp @@ -10,10 +10,24 @@ bool M5StackTab5::initialize_imu(const Imu::filter_fn &orientation_filter) { logger_.info("Initializing BMI270 6-axis IMU"); + std::error_code ec; + auto imu_device = internal_i2c_.add_device( + { + .device_address = Imu::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!imu_device) { + logger_.error("Could not initialize IMU I2C device: {}", ec.message()); + return false; + } + // Create BMI270 instance imu_ = std::make_shared(Imu::Config{ - .write = std::bind_front(&I2c::write, &internal_i2c_), - .read = std::bind_front(&I2c::read, &internal_i2c_), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_4G, diff --git a/components/m5stack-tab5/src/m5stack-tab5.cpp b/components/m5stack-tab5/src/m5stack-tab5.cpp index 76a575a36..a8ec9d518 100644 --- a/components/m5stack-tab5/src/m5stack-tab5.cpp +++ b/components/m5stack-tab5/src/m5stack-tab5.cpp @@ -15,7 +15,31 @@ M5StackTab5::M5StackTab5() bool M5StackTab5::initialize_io_expanders() { logger_.info("Initializing IO expanders (0x43, 0x44)"); - // Create instances + std::error_code ec; + auto ioexp_0x43_device = internal_i2c_.add_device( + { + .device_address = 0x43, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = Logger::Verbosity::INFO, + }, + ec); + if (!ioexp_0x43_device) { + logger_.error("Could not initialize IO expander 0x43 I2C device: {}", ec.message()); + return false; + } + auto ioexp_0x44_device = internal_i2c_.add_device( + { + .device_address = 0x44, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = Logger::Verbosity::INFO, + }, + ec); + if (!ioexp_0x44_device) { + logger_.error("Could not initialize IO expander 0x44 I2C device: {}", ec.message()); + return false; + } ioexp_0x43_ = std::make_shared(IoExpander::Config{ .device_address = 0x43, .direction_mask = IOX_0x43_DIRECTION_MASK, @@ -23,11 +47,8 @@ bool M5StackTab5::initialize_io_expanders() { .high_z_mask = IOX_0x43_HIGH_Z_MASK, .pull_up_mask = IOX_0x43_PULL_UPS, .pull_down_mask = IOX_0x43_PULL_DOWNS, - .write = std::bind(&I2c::write, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .write_then_read = - std::bind(&I2c::write_read, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + .write = espp::make_i2c_addressed_write(ioexp_0x43_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x43_device), .log_level = Logger::Verbosity::INFO}); ioexp_0x44_ = std::make_shared(IoExpander::Config{ .device_address = 0x44, @@ -36,11 +57,8 @@ bool M5StackTab5::initialize_io_expanders() { .high_z_mask = IOX_0x44_HIGH_Z_MASK, .pull_up_mask = IOX_0x44_PULL_UPS, .pull_down_mask = IOX_0x44_PULL_DOWNS, - .write = std::bind(&I2c::write, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .write_then_read = - std::bind(&I2c::write_read, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), + .write = espp::make_i2c_addressed_write(ioexp_0x44_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x44_device), .log_level = Logger::Verbosity::INFO}); logger_.info("IO expanders initialized"); diff --git a/components/m5stack-tab5/src/power.cpp b/components/m5stack-tab5/src/power.cpp index 568750644..642c83a48 100644 --- a/components/m5stack-tab5/src/power.cpp +++ b/components/m5stack-tab5/src/power.cpp @@ -9,6 +9,20 @@ namespace espp { bool M5StackTab5::initialize_battery_monitoring() { logger_.info("Initializing battery monitoring (INA226)"); + std::error_code ec; + auto battery_device = internal_i2c_.add_device( + { + .device_address = 0x41, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!battery_device) { + logger_.error("Could not initialize battery monitor I2C device: {}", ec.message()); + return false; + } + // INA226 connected to the internal I2C bus; setup with typical values. BatteryMonitor::Config cfg{ .device_address = 0x41, // NOTE: not the default address, the ES7210 uses 0x40 @@ -18,22 +32,16 @@ bool M5StackTab5::initialize_battery_monitoring() { .mode = BatteryMonitor::Mode::SHUNT_BUS_CONT, .current_lsb = 0.0005f, // 0.5 mA / LSB .shunt_resistance_ohms = 0.005f, // 5 mΩ (adjust if different on board) - .probe = std::bind(&espp::I2c::probe_device, &internal_i2c(), std::placeholders::_1), - .write = std::bind(&espp::I2c::write, &internal_i2c(), std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &internal_i2c(), std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - .write_then_read = std::bind(&espp::I2c::write_read, &internal_i2c(), std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, - std::placeholders::_4, std::placeholders::_5), + .probe = espp::make_i2c_addressed_probe(battery_device), + .write = espp::make_i2c_addressed_write(battery_device), + .read_register = espp::make_i2c_addressed_read_register(battery_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(battery_device), .auto_init = true, .log_level = espp::Logger::Verbosity::WARN, }; battery_monitor_ = std::make_shared(cfg); - std::error_code ec; if (!battery_monitor_->initialize(ec) || ec) { logger_.error("Battery monitor (INA226) initialization failed: {}", ec.message()); battery_monitor_.reset(); diff --git a/components/m5stack-tab5/src/rtc.cpp b/components/m5stack-tab5/src/rtc.cpp index 293d4073b..71b2b3b0d 100644 --- a/components/m5stack-tab5/src/rtc.cpp +++ b/components/m5stack-tab5/src/rtc.cpp @@ -14,10 +14,23 @@ bool M5StackTab5::initialize_rtc() { return true; } + std::error_code ec; + auto rtc_device = internal_i2c_.add_device( + { + .device_address = Rtc::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = Logger::Verbosity::WARN, + }, + ec); + if (!rtc_device) { + logger_.error("Could not initialize RTC I2C device: {}", ec.message()); + return false; + } rtc_ = std::make_shared(Rtc::Config{ .device_address = Rtc::DEFAULT_ADDRESS, - .write = std::bind_front(&espp::I2c::write, &internal_i2c_), - .read = std::bind_front(&espp::I2c::read, &internal_i2c_), + .write = espp::make_i2c_addressed_write(rtc_device), + .read = espp::make_i2c_addressed_read(rtc_device), .auto_init = true, .log_level = Logger::Verbosity::WARN, }); diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp index 3c51469b2..87710845f 100644 --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -22,10 +22,24 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { touch_reset(false); std::this_thread::sleep_for(50ms); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = TouchDriver::DEFAULT_ADDRESS_2, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } + // Create touch driver instance touch_driver_ = std::make_shared( - TouchDriver::Config{.write = std::bind_front(&I2c::write, &internal_i2c_), - .read = std::bind_front(&I2c::read, &internal_i2c_), + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 address .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/matouch-rotary-display/src/matouch-rotary-display.cpp b/components/matouch-rotary-display/src/matouch-rotary-display.cpp index f9f1ce875..98d62288e 100644 --- a/components/matouch-rotary-display/src/matouch-rotary-display.cpp +++ b/components/matouch-rotary-display/src/matouch-rotary-display.cpp @@ -85,12 +85,23 @@ bool MatouchRotaryDisplay::initialize_touch( } logger_.info("Initializing touch input"); - cst816_ = std::make_shared(espp::Cst816::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = espp::Cst816::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } + cst816_ = std::make_shared( + espp::Cst816::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); // save the callback touch_callback_ = callback; diff --git a/components/max1704x/example/main/max1704x_example.cpp b/components/max1704x/example/main/max1704x_example.cpp index e4d0f466a..514181ed4 100644 --- a/components/max1704x/example/main/max1704x_example.cpp +++ b/components/max1704x/example/main/max1704x_example.cpp @@ -19,11 +19,20 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto max1704x_device = + i2c.add_device({.device_address = espp::Max1704x::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!max1704x_device) { + logger.error("MAX1704x I2C device initialization failed: {}", ec.message()); + return; + } // now make the max1704x which handles GPIO - espp::Max1704x max1704x({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + espp::Max1704x max1704x({.write = espp::make_i2c_addressed_write(max1704x_device), + .read = espp::make_i2c_addressed_read(max1704x_device), .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the max1704x and print @@ -40,7 +49,6 @@ extern "C" void app_main(void) { static auto start = std::chrono::high_resolution_clock::now(); auto now = std::chrono::high_resolution_clock::now(); auto seconds = std::chrono::duration(now - start).count(); - std::error_code ec; auto voltage = max1704x.get_battery_voltage(ec); if (ec) { return false; diff --git a/components/mcp23x17/example/main/mcp23x17_example.cpp b/components/mcp23x17/example/main/mcp23x17_example.cpp index 33c05f35e..b0919015d 100644 --- a/components/mcp23x17/example/main/mcp23x17_example.cpp +++ b/components/mcp23x17/example/main/mcp23x17_example.cpp @@ -19,20 +19,27 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto mcp23x17_device = + i2c.add_device({.device_address = espp::Mcp23x17::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!mcp23x17_device) { + fmt::print("MCP23X17 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the mcp23x17 which handles GPIO espp::Mcp23x17 mcp23x17( {.port_0_direction_mask = (1 << 0), // input on A0 .port_0_interrupt_mask = (1 << 0), // interrupt on A0 .port_1_direction_mask = (1 << 7), // input on B7 .port_1_interrupt_mask = (1 << 7), // interrupt on B7 - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + .write = espp::make_i2c_addressed_write(mcp23x17_device), + .read_register = espp::make_i2c_addressed_read_register(mcp23x17_device), .log_level = espp::Logger::Verbosity::WARN}); // set pull up on the input pins - std::error_code ec; mcp23x17.set_pull_up(espp::Mcp23x17::Port::PORT0, (1 << 0), ec); if (ec) { fmt::print("set_pull_up failed: {}\n", ec.message()); diff --git a/components/mt6701/example/main/mt6701_example.cpp b/components/mt6701/example/main/mt6701_example.cpp index 0e0d33aca..e99c134c2 100644 --- a/components/mt6701/example/main/mt6701_example.cpp +++ b/components/mt6701/example/main/mt6701_example.cpp @@ -25,6 +25,17 @@ extern "C" void app_main(void) { .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, .clk_speed = 1 * 1000 * 1000, // MT6701 supports 1 MHz I2C }); + std::error_code ec; + auto mt6701_device = + i2c.add_device({.device_address = espp::Mt6701<>::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!mt6701_device) { + fmt::print("MT6701 I2C device initialization failed: {}\n", ec.message()); + return; + } // make the velocity filter static constexpr float filter_cutoff_hz = 10.0f; @@ -36,10 +47,8 @@ extern "C" void app_main(void) { // now make the mt6701 which decodes the data using Mt6701 = espp::Mt6701; Mt6701 mt6701( - Mt6701::Config{.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + Mt6701::Config{.write = espp::make_i2c_addressed_write(mt6701_device), + .read = espp::make_i2c_addressed_read(mt6701_device), .velocity_filter = filter_fn, .update_period = std::chrono::duration(encoder_update_period), .run_task = true, // run a task which calls the update function at the update diff --git a/components/pcf85063/example/main/pcf85063_example.cpp b/components/pcf85063/example/main/pcf85063_example.cpp index c265a4df0..41b4a18cd 100644 --- a/components/pcf85063/example/main/pcf85063_example.cpp +++ b/components/pcf85063/example/main/pcf85063_example.cpp @@ -24,11 +24,22 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = i2c_clock_speed}); + std::error_code ec; + auto rtc_device = + i2c.add_device({.device_address = espp::Pcf85063::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!rtc_device) { + logger.error("Failed to initialize PCF85063 I2C device: {}", ec.message()); + return; + } // now make the pcf85063 espp::Pcf85063 rtc({.device_address = espp::Pcf85063::DEFAULT_ADDRESS, - .write = std::bind_front(&espp::I2c::write, &i2c), - .read = std::bind_front(&espp::I2c::read, &i2c)}); + .write = espp::make_i2c_addressed_write(rtc_device), + .read = espp::make_i2c_addressed_read(rtc_device)}); // set the time std::tm time; @@ -38,7 +49,6 @@ extern "C" void app_main(void) { time.tm_mday = 1; time.tm_mon = 0; time.tm_year = 2023 - 1900; - std::error_code ec; rtc.set_time(time, ec); if (ec) { logger.error("Failed to set time: {}", ec.message()); diff --git a/components/pi4ioe5v/example/main/pi4ioe5v_example.cpp b/components/pi4ioe5v/example/main/pi4ioe5v_example.cpp index 6af600be1..c742626af 100644 --- a/components/pi4ioe5v/example/main/pi4ioe5v_example.cpp +++ b/components/pi4ioe5v/example/main/pi4ioe5v_example.cpp @@ -23,6 +23,17 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, }); + std::error_code ec; + auto exp_device = + i2c.add_device({.device_address = espp::Pi4ioe5v::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!exp_device) { + logger.error("PI4IOE5V I2C device initialization failed: {}", ec.message()); + return; + } // Configure PI4IOE5V: 8-bit port with mixed inputs/outputs espp::Pi4ioe5v exp({ @@ -30,13 +41,12 @@ extern "C" void app_main(void) { .direction_mask = 0xF0, // upper 4 bits as outputs, lower 4 bits as inputs .interrupt_mask = 0xFF, // all interrupts disabled for now .initial_output = 0xA0, // initial pattern for outputs - .write = std::bind_front(&espp::I2c::write, &i2c), - .write_then_read = std::bind_front(&espp::I2c::write_read, &i2c), + .write = espp::make_i2c_addressed_write(exp_device), + .write_then_read = espp::make_i2c_addressed_write_then_read(exp_device), .auto_init = false, .log_level = espp::Logger::Verbosity::INFO, }); - std::error_code ec; // Initialize separately from the constructor since we set auto_init to false if (!exp.initialize(ec)) { logger.error("PI4IOE5V initialization failed: {}", ec.message()); diff --git a/components/qmi8658/example/main/qmi8658_example.cpp b/components/qmi8658/example/main/qmi8658_example.cpp index 5227de69d..3961fce7f 100644 --- a/components/qmi8658/example/main/qmi8658_example.cpp +++ b/components/qmi8658/example/main/qmi8658_example.cpp @@ -26,6 +26,17 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = i2c_clock_speed}); + std::error_code ec; + auto imu_device = + i2c.add_device({.device_address = Imu::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!imu_device) { + logger.error("Failed to initialize QMI8658 I2C device: {}", ec.message()); + return; + } // make the orientation filter to compute orientation from accel + gyro static constexpr float angle_noise = 0.001f; @@ -70,10 +81,8 @@ extern "C" void app_main(void) { // make the IMU config Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_8G, diff --git a/components/qwiicnes/example/main/qwiicnes_example.cpp b/components/qwiicnes/example/main/qwiicnes_example.cpp index be4ae4dee..3ffa36aba 100644 --- a/components/qwiicnes/example/main/qwiicnes_example.cpp +++ b/components/qwiicnes/example/main/qwiicnes_example.cpp @@ -19,13 +19,21 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); + std::error_code ec; + auto qwiicnes_device = + i2c.add_device({.device_address = espp::QwiicNes::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!qwiicnes_device) { + fmt::print("QwiicNes I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the qwiicnes which decodes the data espp::QwiicNes qwiicnes( - {.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), + {.write = espp::make_i2c_addressed_write(qwiicnes_device), + .read_register = espp::make_i2c_addressed_read_register(qwiicnes_device), .log_level = espp::Logger::Verbosity::WARN}); // and finally, make the task to periodically poll the qwiicnes and print // the state diff --git a/components/rx8130ce/example/main/rx8130ce_example.cpp b/components/rx8130ce/example/main/rx8130ce_example.cpp index 747359570..7c994d2c6 100644 --- a/components/rx8130ce/example/main/rx8130ce_example.cpp +++ b/components/rx8130ce/example/main/rx8130ce_example.cpp @@ -24,11 +24,22 @@ extern "C" void app_main(void) { .clk_speed = i2c_clock_speed}); using Rtc = espp::Rx8130ce<>; + std::error_code ec; + auto rtc_device = + i2c.add_device({.device_address = Rtc::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!rtc_device) { + logger.error("Failed to initialize RTC I2C device: {}", ec.message()); + return; + } // Configure the RX8130CE Rtc::Config config{.device_address = Rtc::DEFAULT_ADDRESS, - .write = std::bind_front(&espp::I2c::write, &i2c), - .read = std::bind_front(&espp::I2c::read, &i2c), + .write = espp::make_i2c_addressed_write(rtc_device), + .read = espp::make_i2c_addressed_read(rtc_device), .log_level = espp::Logger::Verbosity::INFO}; // Create RTC instance @@ -47,7 +58,6 @@ extern "C" void app_main(void) { time.tm_sec = 45; time.tm_wday = 1; // Monday - std::error_code ec; if (rtc.set_time(time, ec)) { logger.info("Time set successfully"); } else { diff --git a/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp b/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp index 33b3d480a..0678d3ac8 100644 --- a/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp +++ b/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp @@ -39,12 +39,23 @@ bool SsRoundDisplay::initialize_touch(const SsRoundDisplay::touch_callback_t &ca } logger_.info("Initializing Touch Driver"); - touch_ = std::make_unique(TouchDriver::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = TouchDriver::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } + touch_ = std::make_unique( + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); // store the callback touch_callback_ = callback; diff --git a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp index 92799e4e9..76922ff39 100644 --- a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp +++ b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp @@ -32,13 +32,23 @@ bool SmartPanleeSc01Plus::initialize_touch(const SmartPanleeSc01Plus::touch_call return false; } - touch_driver_ = std::make_shared(TouchDriver::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read_register = - std::bind(&espp::I2c::read_at_register, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), - .log_level = espp::Logger::Verbosity::WARN}); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = TouchDriver::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } + touch_driver_ = std::make_shared( + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read_register = espp::make_i2c_addressed_read_register(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); touch_callback_ = callback; interrupts_.add_interrupt(touch_interrupt_pin_); diff --git a/components/st25dv/example/main/st25dv_example.cpp b/components/st25dv/example/main/st25dv_example.cpp index 51e4cecc6..a993744f1 100644 --- a/components/st25dv/example/main/st25dv_example.cpp +++ b/components/st25dv/example/main/st25dv_example.cpp @@ -70,16 +70,46 @@ extern "C" void app_main(void) { .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, .clk_speed = 1000 * 1000, }); + std::error_code ec; + auto st25dv_data_device = + i2c.add_device({.device_address = espp::St25dv::DATA_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!st25dv_data_device) { + fmt::print("Failed to initialize ST25DV data I2C device: {}\n", ec.message()); + return; + } + auto st25dv_syst_device = + i2c.add_device({.device_address = espp::St25dv::SYST_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::INFO}, + ec); + if (!st25dv_syst_device) { + fmt::print("Failed to initialize ST25DV system I2C device: {}\n", ec.message()); + return; + } // now make the st25dv which decodes the data - espp::St25dv st25dv({.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::INFO}); + espp::St25dv st25dv( + {.write = + [st25dv_data_device, st25dv_syst_device](uint8_t addr, const uint8_t *data, + size_t len) { + auto device = + addr == espp::St25dv::SYST_ADDRESS ? st25dv_syst_device : st25dv_data_device; + return device->write(data, len); + }, + .read = + [st25dv_data_device, st25dv_syst_device](uint8_t addr, uint8_t *data, size_t len) { + auto device = + addr == espp::St25dv::SYST_ADDRESS ? st25dv_syst_device : st25dv_data_device; + return device->read(data, len); + }, + .log_level = espp::Logger::Verbosity::INFO}); std::array programmed_data; - std::error_code ec; st25dv.read(programmed_data.data(), programmed_data.size(), ec); if (ec) { fmt::print("Failed to read st25dv: {}\n", ec.message()); diff --git a/components/t-deck/src/t-deck.cpp b/components/t-deck/src/t-deck.cpp index b74cbac5f..b789db6db 100644 --- a/components/t-deck/src/t-deck.cpp +++ b/components/t-deck/src/t-deck.cpp @@ -57,15 +57,26 @@ bool TDeck::initialize_keyboard(bool start_task, const TDeck::keypress_callback_ return false; } logger_.info("Initializing keyboard input"); - keyboard_ = std::make_shared(espp::TKeyboard::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .key_cb = key_cb, - .polling_interval = poll_interval, - .auto_start = start_task, - .log_level = espp::Logger::Verbosity::WARN}); + std::error_code ec; + auto keyboard_device = internal_i2c_.add_device( + { + .device_address = espp::TKeyboard::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!keyboard_device) { + logger_.error("Could not initialize keyboard I2C device: {}", ec.message()); + return false; + } + keyboard_ = std::make_shared( + espp::TKeyboard::Config{.write = espp::make_i2c_addressed_write(keyboard_device), + .read = espp::make_i2c_addressed_read(keyboard_device), + .key_cb = key_cb, + .polling_interval = poll_interval, + .auto_start = start_task, + .log_level = espp::Logger::Verbosity::WARN}); return true; } @@ -148,13 +159,24 @@ bool TDeck::initialize_touch(const TDeck::touch_callback_t &touch_cb) { } logger_.info("Initializing touch input"); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = espp::Gt911::DEFAULT_ADDRESS_1, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } - gt911_ = std::make_unique(espp::Gt911::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); + gt911_ = std::make_unique( + espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); // store the callback touch_callback_ = touch_cb; diff --git a/components/t_keyboard/example/main/t_keyboard_example.cpp b/components/t_keyboard/example/main/t_keyboard_example.cpp index a7f6f2ce2..435cb4f13 100644 --- a/components/t_keyboard/example/main/t_keyboard_example.cpp +++ b/components/t_keyboard/example/main/t_keyboard_example.cpp @@ -20,16 +20,6 @@ extern "C" void app_main(void) { .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, }); - // now make the tkeyboard which decodes the data - espp::TKeyboard tkeyboard( - {.write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .key_cb = [](uint8_t key) { fmt::print("'{}' Pressed!\n", (char)key); }, - .auto_start = false, // can't auto start since we need to provide power - .log_level = espp::Logger::Verbosity::WARN}); - // on the LilyGo T-Deck, the peripheral power control pin must be set high // to enable peripheral power auto power_ctrl = GPIO_NUM_10; @@ -42,6 +32,24 @@ extern "C" void app_main(void) { } while (!i2c.probe_device(espp::TKeyboard::DEFAULT_ADDRESS)); fmt::print("Tkeyboard ready!\n"); + std::error_code ec; + auto tkeyboard_device = + i2c.add_device({.device_address = espp::TKeyboard::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!tkeyboard_device) { + fmt::print("TKeyboard I2C device initialization failed: {}\n", ec.message()); + return; + } + // now make the tkeyboard which decodes the data + espp::TKeyboard tkeyboard( + {.write = espp::make_i2c_addressed_write(tkeyboard_device), + .read = espp::make_i2c_addressed_read(tkeyboard_device), + .key_cb = [](uint8_t key) { fmt::print("'{}' Pressed!\n", (char)key); }, + .auto_start = false, // can't auto start since we need to provide power + .log_level = espp::Logger::Verbosity::WARN}); tkeyboard.start(); //! [tkeyboard example] while (true) { diff --git a/components/tla2528/example/main/tla2528_example.cpp b/components/tla2528/example/main/tla2528_example.cpp index 36fa96cc1..3b8301abb 100644 --- a/components/tla2528/example/main/tla2528_example.cpp +++ b/components/tla2528/example/main/tla2528_example.cpp @@ -60,6 +60,17 @@ extern "C" void app_main(void) { } logger.info("Found TLA2528 at address {:#02x}", tla_address); + std::error_code ec; + auto tla_device = + i2c.add_device({.device_address = tla_address, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!tla_device) { + logger.error("Failed to initialize TLA2528 I2C device: {}", ec.message()); + return; + } static std::vector channels = { espp::Tla2528::Channel::CH0, espp::Tla2528::Channel::CH1, espp::Tla2528::Channel::CH2, @@ -75,10 +86,8 @@ extern "C" void app_main(void) { .digital_outputs = {}, // enable oversampling / averaging .oversampling_ratio = espp::Tla2528::OversamplingRatio::NONE, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .write = espp::make_i2c_addressed_write(tla_device), + .read = espp::make_i2c_addressed_read(tla_device), .log_level = espp::Logger::Verbosity::WARN, }); @@ -87,7 +96,6 @@ extern "C" void app_main(void) { // NOTE: this is not strictly necessary, but improves the accuracy of the // ADC. auto cal_start_us = esp_timer_get_time(); - std::error_code ec; tla.calibrate(ec); auto cal_end_us = esp_timer_get_time(); if (ec) { diff --git a/components/tt21100/example/main/tt21100_example.cpp b/components/tt21100/example/main/tt21100_example.cpp index 3a00f9013..644416402 100644 --- a/components/tt21100/example/main/tt21100_example.cpp +++ b/components/tt21100/example/main/tt21100_example.cpp @@ -20,10 +20,20 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, }); + std::error_code ec; + auto tt21100_device = + i2c.add_device({.device_address = espp::Tt21100::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!tt21100_device) { + fmt::print("TT21100 I2C device initialization failed: {}\n", ec.message()); + return; + } // now make the tt21100 auto tt21100 = espp::Tt21100({ - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + .read = espp::make_i2c_addressed_read(tt21100_device), }); // and finally, make the task to periodically poll the tt21100 and print diff --git a/components/vl53l/example/main/vl53l_example.cpp b/components/vl53l/example/main/vl53l_example.cpp index 9db76b3a7..036fbf7a3 100644 --- a/components/vl53l/example/main/vl53l_example.cpp +++ b/components/vl53l/example/main/vl53l_example.cpp @@ -28,17 +28,24 @@ extern "C" void app_main(void) { .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, .clk_speed = i2c_clock_speed}); + std::error_code ec; + auto vl53l_device = + i2c.add_device({.device_address = espp::Vl53l::DEFAULT_ADDRESS, + .timeout_ms = static_cast(i2c.config().timeout_ms), + .scl_speed_hz = i2c.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN}, + ec); + if (!vl53l_device) { + logger.error("Failed to initialize VL53L I2C device: {}", ec.message()); + return; + } // make the actual test object - espp::Vl53l vl53l( - espp::Vl53l::Config{.device_address = espp::Vl53l::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); + espp::Vl53l vl53l(espp::Vl53l::Config{.device_address = espp::Vl53l::DEFAULT_ADDRESS, + .write = espp::make_i2c_addressed_write(vl53l_device), + .read = espp::make_i2c_addressed_read(vl53l_device), + .log_level = espp::Logger::Verbosity::WARN}); - std::error_code ec; // set the timing budget to 10ms, which must be shorter than the // inter-measurement period. We'll log every 20ms so this guarantees we get // new data every time diff --git a/components/ws-s3-touch/src/imu.cpp b/components/ws-s3-touch/src/imu.cpp index 5d5fa5c2c..c94d0395c 100644 --- a/components/ws-s3-touch/src/imu.cpp +++ b/components/ws-s3-touch/src/imu.cpp @@ -9,12 +9,24 @@ bool WsS3Touch::initialize_imu(const WsS3Touch::Imu::filter_fn &orientation_filt return false; } + std::error_code ec; + auto imu_device = internal_i2c_.add_device( + { + .device_address = Imu::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!imu_device) { + logger_.error("Could not initialize IMU I2C device: {}", ec.message()); + return false; + } + Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), + .write = espp::make_i2c_addressed_write(imu_device), + .read = espp::make_i2c_addressed_read(imu_device), .imu_config = imu_config, .orientation_filter = orientation_filter, .auto_init = true, diff --git a/components/ws-s3-touch/src/rtc.cpp b/components/ws-s3-touch/src/rtc.cpp index 0117e8881..4d81a9a7c 100644 --- a/components/ws-s3-touch/src/rtc.cpp +++ b/components/ws-s3-touch/src/rtc.cpp @@ -8,10 +8,23 @@ bool WsS3Touch::initialize_rtc() { return true; } logger_.info("Initializing RTC"); + std::error_code ec; + auto rtc_device = internal_i2c_.add_device( + { + .device_address = Rtc::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!rtc_device) { + logger_.error("Could not initialize RTC I2C device: {}", ec.message()); + return false; + } rtc_ = std::make_shared(Rtc::Config{ .device_address = Rtc::DEFAULT_ADDRESS, - .write = std::bind_front(&espp::I2c::write, &internal_i2c_), - .read = std::bind_front(&espp::I2c::read, &internal_i2c_), + .write = espp::make_i2c_addressed_write(rtc_device), + .read = espp::make_i2c_addressed_read(rtc_device), .log_level = espp::Logger::Verbosity::WARN, }); return true; diff --git a/components/ws-s3-touch/src/touchpad.cpp b/components/ws-s3-touch/src/touchpad.cpp index daab0572a..48de54934 100644 --- a/components/ws-s3-touch/src/touchpad.cpp +++ b/components/ws-s3-touch/src/touchpad.cpp @@ -13,12 +13,23 @@ bool WsS3Touch::initialize_touch(const WsS3Touch::touch_callback_t &callback) { } logger_.info("Initializing TouchDriver"); - touch_driver_ = std::make_unique(TouchDriver::Config{ - .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, - std::placeholders::_2, std::placeholders::_3), - .log_level = espp::Logger::Verbosity::WARN}); + std::error_code ec; + auto touch_device = internal_i2c_.add_device( + { + .device_address = TouchDriver::DEFAULT_ADDRESS, + .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .scl_speed_hz = internal_i2c_.config().clk_speed, + .log_level = espp::Logger::Verbosity::WARN, + }, + ec); + if (!touch_device) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + return false; + } + touch_driver_ = std::make_unique( + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), + .read = espp::make_i2c_addressed_read(touch_device), + .log_level = espp::Logger::Verbosity::WARN}); // store the callback touch_callback_ = callback; diff --git a/doc/en/i2c.rst b/doc/en/i2c.rst index 26d0a845c..baa058620 100644 --- a/doc/en/i2c.rst +++ b/doc/en/i2c.rst @@ -1,26 +1,31 @@ I2C *** -The `i2c` component provides c++ wrappers / implementation build on ESP-IDF's -i2c drivers. +The `i2c` component provides C++ wrappers built on ESP-IDF's I2C drivers. -There are two versions of these drivers, supporting the different driver -versions supported in ESP-IDF v5.x. +There are two driver families available: -The two drivers cannot coexist at the same time, so you must select (via -kconfig) which driver you want to use. +- the deprecated legacy driver +- the new master/slave bus API used by ESP-IDF v6.x -Legacy API -========== +Only one family can be selected at a time via Kconfig. + +Primary API +=========== -This driver is deprecated and will no longer be available for ESP-IDF >= v6.0 - -so make sure you upgrade. +When the new driver family is selected, ``espp::I2c`` remains the main public +API. It preserves the familiar address-based helper methods for backwards +compatibility while internally using ESP-IDF's new master bus/device model. -The `I2C` class provides a simple interface to the I2C bus. It is a wrapper -around the esp-idf I2C driver. +That means existing code using methods such as ``probe_device()``, ``read()``, +``write()``, ``write_read()``, and ``read_at_register()`` can keep working on +ESP-IDF v6.x without being rewritten around the raw ESP-IDF handles. -A helper `I2cMenu` is also provided which can be used to interactively test -I2C buses - scanning the bus, probing devices, reading and writing to devices. +For code that wants explicit per-device ownership, ``espp::I2c`` also exposes +``add_device()`` which returns an ``I2cMasterDevice``. + +``I2cMenu`` is available for the bus-compatible ``espp::I2c`` API and can be +used to scan the bus, probe devices, and read/write registers interactively. .. ------------------------------- Example ------------------------------------- @@ -36,25 +41,28 @@ API Reference .. include-build-file:: inc/i2c.inc .. include-build-file:: inc/i2c_menu.inc -New API -======= +Explicit master/slave APIs +========================== + +The component also exposes the explicit new-driver wrappers directly: -This driver was introduced and has been refined in ESP-IDF v5. As of v6.0, it -will be the only driver available. +- ``I2cMasterBus`` +- ``I2cMasterDevice`` +- ``I2cSlaveDevice`` -The `I2cMasterBus` and `I2cMasterDevice` classes provide simple interfaces to -manage and communicate with I2C peripherals on an I2C bus. +These classes mirror the ESP-IDF master/slave bus model more closely. The master +helpers are synchronous/blocking wrappers around ESP-IDF's new API. -It should be noted that while the new ESP-IDF APIs can support asynchronous -mode, these classes do not. +``I2cMasterMenu`` and ``I2cMasterDeviceMenu`` are also provided for interactive +testing of explicit master buses and devices. -Helper `I2cMasterMenu` and `I2cMasterDeviceMenu` are also provided which can be -used to interactively test I2C buses - scanning the bus, probing devices, and -performing read/write operations with devices. +``I2cSlaveDevice`` and ``I2cSlaveMenu`` are available for slave-side work. +The slave wrapper follows ESP-IDF's callback-driven model: ``read()`` returns +the next complete master-write transaction buffered by the receive callback +path, ``write()`` stages bytes for the next master read, and optional request / +receive callbacks run in task context instead of the ISR callback itself. -There are also `I2cSlaveDevice` and `I2cSlaveMenu` classes for developing your -own I2C Slave device using ESP, though they are not currently tested / used as -the upstream ESP-IDF is only recently stabilizing. +There is not yet a dedicated slave example. .. ------------------------------- Example ------------------------------------- @@ -72,3 +80,10 @@ API Reference .. include-build-file:: inc/i2c_master_device_menu.inc .. include-build-file:: inc/i2c_slave.inc .. include-build-file:: inc/i2c_slave_menu.inc + +Legacy API +========== + +The legacy ESP-IDF I2C driver is still available behind +``CONFIG_ESPP_I2C_USE_LEGACY_API`` for older environments, but it is deprecated +and should not be used for new development. From ba0e9b78c50f6226467d00d98c4335804e9ba45b Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 17:04:01 -0500 Subject: [PATCH 02/16] fix issues --- components/i2c/src/i2c_slave.cpp | 36 +++++++++++++++++-- .../st25dv/example/main/st25dv_example.cpp | 16 +++++++-- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index 495ff3e26..bbb4c96d6 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -191,7 +191,10 @@ bool I2cSlaveDevice::deinit(std::error_code &ec) { if (event_queue_) { Event stop_event{EventType::STOP}; - xQueueSend(event_queue_, &stop_event, 0); + xQueueReset(event_queue_); + if (xQueueSend(event_queue_, &stop_event, timeout_ticks(config_.timeout_ms)) != pdPASS) { + logger_.warn("failed to queue I2C slave stop event during deinit"); + } } if (event_task_) { event_task_->stop(); @@ -348,9 +351,36 @@ void I2cSlaveDevice::log_pending_overflows() { } } -bool I2cSlaveDevice::event_task_callback(std::mutex &, std::condition_variable &, bool &) { +bool I2cSlaveDevice::event_task_callback(std::mutex &m, std::condition_variable &, bool ¬ified) { + { + std::lock_guard lock(m); + if (notified) { + notified = false; + return true; + } + } + Event event{}; - if (!event_queue_ || !xQueueReceive(event_queue_, &event, portMAX_DELAY)) { + static constexpr TickType_t stop_poll_ticks = pdMS_TO_TICKS(50); + if (!event_queue_) { + return true; + } + if (!xQueueReceive(event_queue_, &event, stop_poll_ticks)) { + std::lock_guard lock(m); + bool stop_requested = notified; + notified = false; + return stop_requested; + } + + { + std::lock_guard lock(m); + if (notified) { + notified = false; + return true; + } + } + + if (!initialized_ && event.type != EventType::STOP) { return false; } diff --git a/components/st25dv/example/main/st25dv_example.cpp b/components/st25dv/example/main/st25dv_example.cpp index a993744f1..6749d0a7b 100644 --- a/components/st25dv/example/main/st25dv_example.cpp +++ b/components/st25dv/example/main/st25dv_example.cpp @@ -99,13 +99,25 @@ extern "C" void app_main(void) { size_t len) { auto device = addr == espp::St25dv::SYST_ADDRESS ? st25dv_syst_device : st25dv_data_device; - return device->write(data, len); + std::error_code ec; + bool success = device->write(data, len, ec); + if (!success) { + fmt::print("Failed to write ST25DV {} I2C device: {}\n", + addr == espp::St25dv::SYST_ADDRESS ? "system" : "data", ec.message()); + } + return success; }, .read = [st25dv_data_device, st25dv_syst_device](uint8_t addr, uint8_t *data, size_t len) { auto device = addr == espp::St25dv::SYST_ADDRESS ? st25dv_syst_device : st25dv_data_device; - return device->read(data, len); + std::error_code ec; + bool success = device->read(data, len, ec); + if (!success) { + fmt::print("Failed to read ST25DV {} I2C device: {}\n", + addr == espp::St25dv::SYST_ADDRESS ? "system" : "data", ec.message()); + } + return success; }, .log_level = espp::Logger::Verbosity::INFO}); From 834a1e0f709e8df270162912cef8740caf6ea3db Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 21:43:27 -0500 Subject: [PATCH 03/16] address comments --- components/i2c/README.md | 5 ++ components/i2c/include/i2c_slave.hpp | 17 ++++++ components/i2c/src/i2c_slave.cpp | 79 +++++++++++++++++++++++++--- doc/en/i2c.rst | 5 ++ 4 files changed, 99 insertions(+), 7 deletions(-) diff --git a/components/i2c/README.md b/components/i2c/README.md index c1bd8767a..ede2d91e7 100644 --- a/components/i2c/README.md +++ b/components/i2c/README.md @@ -26,6 +26,11 @@ the driver callback path, `write()` stages bytes for the next master read, and optional request / receive callbacks are dispatched in task context rather than directly from the ISR callback. +On ESP-IDF v5.5's default slave driver, the underlying receive callback does +not report the exact transaction length. In that configuration ESPP still +supports buffered slave reads and receive callbacks, but trailing unread bytes +are zero-filled and the reported receive length matches the requested read size. + ## Example The [example](./example) shows both styles: diff --git a/components/i2c/include/i2c_slave.hpp b/components/i2c/include/i2c_slave.hpp index a3c6884ba..1e6899c16 100644 --- a/components/i2c/include/i2c_slave.hpp +++ b/components/i2c/include/i2c_slave.hpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,12 @@ extern "C" { namespace espp { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) || CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 +#define ESPP_I2C_SLAVE_V2_API 1 +#else +#define ESPP_I2C_SLAVE_V2_API 0 +#endif + /// @brief I2C slave device wrapper for ESP-IDF's callback-driven slave API. /// @details /// ESP-IDF's slave driver is event/callback based: master writes arrive through @@ -38,6 +45,11 @@ namespace espp { /// - task-context request / receive callbacks so user code does not have to run /// inside the ISR callback context /// +/// When built against ESP-IDF v5.5's default slave driver, the receive callback +/// API does not expose the actual transaction length. In that configuration this +/// wrapper buffers the requested receive length and zero-fills any trailing bytes +/// the master did not write so `read()` and `on_receive` still behave consistently. +/// /// @note No dedicated example exists yet. /// /// Usage: @@ -123,9 +135,11 @@ class I2cSlaveDevice : public BaseComponent { EventType type; }; +#if ESPP_I2C_SLAVE_V2_API static bool IRAM_ATTR request_callback_trampoline(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_request_event_data_t *evt_data, void *user_data); +#endif static bool IRAM_ATTR receive_callback_trampoline(i2c_slave_dev_handle_t i2c_slave, const i2c_slave_rx_done_event_data_t *evt_data, void *user_data); @@ -145,6 +159,9 @@ class I2cSlaveDevice : public BaseComponent { MessageBufferHandle_t read_buffer_ = nullptr; MessageBufferHandle_t callback_buffer_ = nullptr; std::unique_ptr event_task_; + std::vector legacy_receive_buffer_; + std::atomic legacy_receive_length_{0}; + std::atomic legacy_receive_armed_{false}; std::atomic read_buffer_overflowed_{false}; std::atomic callback_buffer_overflowed_{false}; std::atomic event_queue_overflowed_{false}; diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index bbb4c96d6..52d46e260 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -101,12 +101,16 @@ bool I2cSlaveDevice::init(std::error_code &ec) { slave_cfg.scl_io_num = static_cast(config_.scl_io_num); slave_cfg.clk_source = I2C_CLK_SRC_DEFAULT; slave_cfg.send_buf_depth = config_.send_buffer_depth; +#if ESPP_I2C_SLAVE_V2_API slave_cfg.receive_buf_depth = config_.receive_buffer_depth; +#endif slave_cfg.slave_addr = config_.slave_address; slave_cfg.addr_bit_len = config_.addr_bit_len; slave_cfg.intr_priority = config_.intr_priority; slave_cfg.flags.allow_pd = 0; +#if ESPP_I2C_SLAVE_V2_API slave_cfg.flags.enable_internal_pullup = config_.enable_internal_pullup; +#endif esp_err_t err = i2c_new_slave_device(&slave_cfg, &dev_handle_); if (err != ESP_OK) { @@ -122,8 +126,12 @@ bool I2cSlaveDevice::init(std::error_code &ec) { } i2c_slave_event_callbacks_t cbs = {}; +#if ESPP_I2C_SLAVE_V2_API cbs.on_request = &I2cSlaveDevice::request_callback_trampoline; cbs.on_receive = &I2cSlaveDevice::receive_callback_trampoline; +#else + cbs.on_recv_done = &I2cSlaveDevice::receive_callback_trampoline; +#endif err = i2c_slave_register_event_callbacks(dev_handle_, &cbs, this); if (err != ESP_OK) { logger_.error("could not register I2C slave callbacks: {}", esp_err_to_name(err)); @@ -162,6 +170,9 @@ bool I2cSlaveDevice::init(std::error_code &ec) { read_buffer_overflowed_ = false; callback_buffer_overflowed_ = false; event_queue_overflowed_ = false; + legacy_receive_buffer_.assign(config_.receive_buffer_depth, 0); + legacy_receive_length_ = 0; + legacy_receive_armed_ = false; initialized_ = true; ec.clear(); return true; @@ -215,6 +226,9 @@ bool I2cSlaveDevice::deinit(std::error_code &ec) { callback_buffer_ = nullptr; } callbacks_ = {}; + legacy_receive_buffer_.clear(); + legacy_receive_length_ = 0; + legacy_receive_armed_ = false; ec.clear(); return true; } @@ -234,6 +248,7 @@ bool I2cSlaveDevice::write(const uint8_t *data, size_t len, std::error_code &ec) return false; } +#if ESPP_I2C_SLAVE_V2_API uint32_t write_len = 0; esp_err_t err = i2c_slave_write(dev_handle_, data, len, &write_len, config_.timeout_ms); if (err != ESP_OK) { @@ -249,6 +264,17 @@ bool I2cSlaveDevice::write(const uint8_t *data, size_t len, std::error_code &ec) ec.clear(); return true; +#else + esp_err_t err = i2c_slave_transmit(dev_handle_, data, len, config_.timeout_ms); + if (err != ESP_OK) { + logger_.error("I2C slave write failed: {}", esp_err_to_name(err)); + ec = make_error_code(err); + return false; + } + + ec.clear(); + return true; +#endif } bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { @@ -268,6 +294,30 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { log_pending_overflows(); +#if !ESPP_I2C_SLAVE_V2_API + size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); + if (next_length == 0 && !legacy_receive_armed_) { + if (len > config_.receive_buffer_depth) { + logger_.error( + "I2C slave read buffer too small for configured receive depth (need {}, have {})", + config_.receive_buffer_depth, len); + ec = std::make_error_code(std::errc::message_size); + return false; + } + std::fill(legacy_receive_buffer_.begin(), legacy_receive_buffer_.end(), 0); + legacy_receive_length_ = len; + legacy_receive_armed_ = true; + esp_err_t err = i2c_slave_receive(dev_handle_, legacy_receive_buffer_.data(), len); + if (err != ESP_OK) { + legacy_receive_armed_ = false; + legacy_receive_length_ = 0; + logger_.error("I2C slave read failed to arm receive: {}", esp_err_to_name(err)); + ec = make_error_code(err); + return false; + } + } +#endif + size_t read_len = xMessageBufferReceive(read_buffer_, data, len, timeout_ticks(config_.timeout_ms)); if (read_len == 0) { @@ -276,6 +326,10 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { logger_.error("I2C slave read buffer too small for next transaction (need {}, have {})", next_length, len); ec = std::make_error_code(std::errc::message_size); +#if !ESPP_I2C_SLAVE_V2_API + } else if (legacy_receive_armed_) { + ec = std::make_error_code(std::errc::timed_out); +#endif } else if (read_buffer_overflowed_.exchange(false)) { logger_.error("I2C slave dropped received data because the read queue overflowed"); ec = std::make_error_code(std::errc::no_buffer_space); @@ -296,6 +350,7 @@ bool I2cSlaveDevice::register_callbacks(const Callbacks &cb, std::error_code &ec return true; } +#if ESPP_I2C_SLAVE_V2_API bool IRAM_ATTR I2cSlaveDevice::request_callback_trampoline(i2c_slave_dev_handle_t, const i2c_slave_request_event_data_t *, void *user_data) { @@ -311,24 +366,34 @@ bool IRAM_ATTR I2cSlaveDevice::request_callback_trampoline(i2c_slave_dev_handle_ } return task_woken == pdTRUE; } +#endif bool IRAM_ATTR I2cSlaveDevice::receive_callback_trampoline( i2c_slave_dev_handle_t, const i2c_slave_rx_done_event_data_t *evt_data, void *user_data) { auto *device = static_cast(user_data); - if (!device || !device->event_queue_ || !evt_data || !evt_data->buffer || evt_data->length == 0) { + if (!device || !device->event_queue_ || !evt_data || !evt_data->buffer) { return false; } BaseType_t task_woken = pdFALSE; - size_t sent = xMessageBufferSendFromISR(device->read_buffer_, evt_data->buffer, evt_data->length, - &task_woken); - if (sent != evt_data->length) { +#if ESPP_I2C_SLAVE_V2_API + size_t length = evt_data->length; +#else + size_t length = device->legacy_receive_length_.exchange(0); + device->legacy_receive_armed_ = false; +#endif + if (length == 0) { + return false; + } + + size_t sent = + xMessageBufferSendFromISR(device->read_buffer_, evt_data->buffer, length, &task_woken); + if (sent != length) { device->read_buffer_overflowed_ = true; } - sent = xMessageBufferSendFromISR(device->callback_buffer_, evt_data->buffer, evt_data->length, - &task_woken); - if (sent != evt_data->length) { + sent = xMessageBufferSendFromISR(device->callback_buffer_, evt_data->buffer, length, &task_woken); + if (sent != length) { device->callback_buffer_overflowed_ = true; } diff --git a/doc/en/i2c.rst b/doc/en/i2c.rst index baa058620..da68b1624 100644 --- a/doc/en/i2c.rst +++ b/doc/en/i2c.rst @@ -62,6 +62,11 @@ the next complete master-write transaction buffered by the receive callback path, ``write()`` stages bytes for the next master read, and optional request / receive callbacks run in task context instead of the ISR callback itself. +On ESP-IDF v5.5's default slave driver, the receive callback does not expose the +exact byte count. ESPP still supports buffered reads and receive callbacks in +that configuration, but trailing unread bytes are zero-filled and the reported +receive length matches the requested read size. + There is not yet a dedicated slave example. .. ------------------------------- Example ------------------------------------- From ac5de5932005d4f7207e102085b46099e7916d34 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 21:45:33 -0500 Subject: [PATCH 04/16] suppress invalid cppcheck result --- components/i2c/include/i2c_master_menu.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/components/i2c/include/i2c_master_menu.hpp b/components/i2c/include/i2c_master_menu.hpp index bb9c6f264..4347ec08a 100644 --- a/components/i2c/include/i2c_master_menu.hpp +++ b/components/i2c/include/i2c_master_menu.hpp @@ -153,8 +153,9 @@ class I2cMasterMenu { /// @param address The address to read from. /// @param reg The register to read from. /// @param len The number of bytes to read. - static void read_device(std::ostream &out, espp::I2cMasterBus &bus, uint16_t address, uint8_t reg, - uint8_t len) { + static void read_device(std::ostream &out, + espp::I2cMasterBus &bus, // cppcheck-suppress constParameterReference + uint16_t address, uint8_t reg, uint8_t len) { std::error_code ec; auto dev = bus.add_device({.device_address = address, .timeout_ms = 50}, ec); if (!dev) { @@ -176,8 +177,9 @@ class I2cMasterMenu { /// @param out The output stream to write to. /// @param address The address to write to. /// @param data The data to write (first byte is register, rest is data). - static void write_device(std::ostream &out, espp::I2cMasterBus &bus, uint16_t address, - const std::vector &data) { + static void write_device(std::ostream &out, + espp::I2cMasterBus &bus, // cppcheck-suppress constParameterReference + uint16_t address, const std::vector &data) { if (data.empty()) { out << "No register/data provided.\n"; return; From cc975e14d122676c0f0f9685d056d36f2b0b7445 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 21:53:06 -0500 Subject: [PATCH 05/16] address comments --- components/i2c/README.md | 4 ++ components/i2c/include/i2c_master.hpp | 9 ++++ components/i2c/include/i2c_slave.hpp | 14 ++++++ components/i2c/include/i2c_slave_menu.hpp | 6 ++- components/i2c/src/i2c_slave.cpp | 59 ++++++++++++++++------- doc/en/i2c.rst | 4 ++ 6 files changed, 76 insertions(+), 20 deletions(-) diff --git a/components/i2c/README.md b/components/i2c/README.md index ede2d91e7..ef458dddd 100644 --- a/components/i2c/README.md +++ b/components/i2c/README.md @@ -26,6 +26,10 @@ the driver callback path, `write()` stages bytes for the next master read, and optional request / receive callbacks are dispatched in task context rather than directly from the ISR callback. +If you need the exact transaction size, use the `read()` overload that returns +the received length by reference. The original boolean `read()` API remains for +compatibility. + On ESP-IDF v5.5's default slave driver, the underlying receive callback does not report the exact transaction length. In that configuration ESPP still supports buffered slave reads and receive callbacks, but trailing unread bytes diff --git a/components/i2c/include/i2c_master.hpp b/components/i2c/include/i2c_master.hpp index 70fcacbf9..c11e99391 100644 --- a/components/i2c/include/i2c_master.hpp +++ b/components/i2c/include/i2c_master.hpp @@ -162,6 +162,9 @@ template class I2cMasterDevice : public BaseCo /// @brief Expose config for CLI menu /// @return Reference to config const Config &config() const { return config_; } + /// @brief Check whether the device is initialized + /// @return True if initialized + bool initialized() const { return initialized_; } protected: Config config_; @@ -239,8 +242,14 @@ class I2cMasterBus : public BaseComponent { return nullptr; } auto device = std::make_shared>(bus_handle_, dev_config); + if (dev_config.auto_init && device && !device->initialized()) { + logger_.error("I2C device auto-init failed at address 0x{:02x}", dev_config.device_address); + ec = std::make_error_code(std::errc::io_error); + return nullptr; + } logger_.info("I2C device added at address 0x{:02x} on bus {}", dev_config.device_address, config_.port); + ec.clear(); return device; } diff --git a/components/i2c/include/i2c_slave.hpp b/components/i2c/include/i2c_slave.hpp index 1e6899c16..7ca7766fc 100644 --- a/components/i2c/include/i2c_slave.hpp +++ b/components/i2c/include/i2c_slave.hpp @@ -13,6 +13,13 @@ #include #include +#ifndef ESP_IDF_VERSION_VAL +#define ESP_IDF_VERSION_VAL(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) +#endif +#ifndef ESP_IDF_VERSION +#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(0, 0, 0) +#endif + #include #include #include @@ -114,6 +121,13 @@ class I2cSlaveDevice : public BaseComponent { /// @brief Read data from the master /// @param data Pointer to buffer /// @param len Maximum transaction length to read + /// @param received_len Actual number of bytes received for the transaction + /// @param ec Error code output + /// @return True if a complete master-write transaction was received + bool read(uint8_t *data, size_t len, size_t &received_len, std::error_code &ec); + /// @brief Read data from the master + /// @param data Pointer to buffer + /// @param len Maximum transaction length to read /// @param ec Error code output /// @return True if a complete master-write transaction was received bool read(uint8_t *data, size_t len, std::error_code &ec); diff --git a/components/i2c/include/i2c_slave_menu.hpp b/components/i2c/include/i2c_slave_menu.hpp index 156eaaec6..21d62a41b 100644 --- a/components/i2c/include/i2c_slave_menu.hpp +++ b/components/i2c/include/i2c_slave_menu.hpp @@ -56,10 +56,12 @@ class I2cSlaveMenu { "read", {"length"}, [this](std::ostream &out, size_t len) -> void { std::vector data(len); + size_t received_len = 0; std::error_code ec; - bool success = device_->read(data.data(), data.size(), ec); + bool success = device_->read(data.data(), data.size(), received_len, ec); if (success) { - out << fmt::format("Read {} bytes from slave: {::#02x}\n", len, data); + data.resize(received_len); + out << fmt::format("Read {} bytes from slave: {::#02x}\n", received_len, data); } else { out << fmt::format("Error reading from slave: {}\n", ec.message()); } diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index 52d46e260..5aa297c40 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -200,13 +200,6 @@ bool I2cSlaveDevice::deinit(std::error_code &ec) { } } - if (event_queue_) { - Event stop_event{EventType::STOP}; - xQueueReset(event_queue_); - if (xQueueSend(event_queue_, &stop_event, timeout_ticks(config_.timeout_ms)) != pdPASS) { - logger_.warn("failed to queue I2C slave stop event during deinit"); - } - } if (event_task_) { event_task_->stop(); event_task_.reset(); @@ -278,7 +271,13 @@ bool I2cSlaveDevice::write(const uint8_t *data, size_t len, std::error_code &ec) } bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { + size_t received_len = 0; + return read(data, len, received_len, ec); +} + +bool I2cSlaveDevice::read(uint8_t *data, size_t len, size_t &received_len, std::error_code &ec) { std::lock_guard lock(mutex_); + received_len = 0; if (!initialized_ || !read_buffer_) { ec = std::make_error_code(std::errc::not_connected); return false; @@ -339,6 +338,7 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, std::error_code &ec) { return false; } + received_len = read_len; ec.clear(); return true; } @@ -355,13 +355,17 @@ bool IRAM_ATTR I2cSlaveDevice::request_callback_trampoline(i2c_slave_dev_handle_ const i2c_slave_request_event_data_t *, void *user_data) { auto *device = static_cast(user_data); - if (!device || !device->event_queue_) { + if (!device) { + return false; + } + auto event_queue = device->event_queue_; + if (!event_queue) { return false; } BaseType_t task_woken = pdFALSE; Event event{EventType::REQUEST}; - if (xQueueSendFromISR(device->event_queue_, &event, &task_woken) != pdPASS) { + if (xQueueSendFromISR(event_queue, &event, &task_woken) != pdPASS) { device->event_queue_overflowed_ = true; } return task_woken == pdTRUE; @@ -371,7 +375,15 @@ bool IRAM_ATTR I2cSlaveDevice::request_callback_trampoline(i2c_slave_dev_handle_ bool IRAM_ATTR I2cSlaveDevice::receive_callback_trampoline( i2c_slave_dev_handle_t, const i2c_slave_rx_done_event_data_t *evt_data, void *user_data) { auto *device = static_cast(user_data); - if (!device || !device->event_queue_ || !evt_data || !evt_data->buffer) { + if (!device || !evt_data || !evt_data->buffer) { + return false; + } + + auto event_queue = device->event_queue_; + auto read_buffer = device->read_buffer_; + auto callback_buffer = device->callback_buffer_; + if (!event_queue || !read_buffer || !callback_buffer) { + device->event_queue_overflowed_ = true; return false; } @@ -386,19 +398,18 @@ bool IRAM_ATTR I2cSlaveDevice::receive_callback_trampoline( return false; } - size_t sent = - xMessageBufferSendFromISR(device->read_buffer_, evt_data->buffer, length, &task_woken); + size_t sent = xMessageBufferSendFromISR(read_buffer, evt_data->buffer, length, &task_woken); if (sent != length) { device->read_buffer_overflowed_ = true; } - sent = xMessageBufferSendFromISR(device->callback_buffer_, evt_data->buffer, length, &task_woken); + sent = xMessageBufferSendFromISR(callback_buffer, evt_data->buffer, length, &task_woken); if (sent != length) { device->callback_buffer_overflowed_ = true; } Event event{EventType::RECEIVE}; - if (xQueueSendFromISR(device->event_queue_, &event, &task_woken) != pdPASS) { + if (xQueueSendFromISR(event_queue, &event, &task_woken) != pdPASS) { device->event_queue_overflowed_ = true; } return task_woken == pdTRUE; @@ -427,10 +438,15 @@ bool I2cSlaveDevice::event_task_callback(std::mutex &m, std::condition_variable Event event{}; static constexpr TickType_t stop_poll_ticks = pdMS_TO_TICKS(50); - if (!event_queue_) { + QueueHandle_t event_queue = nullptr; + { + std::lock_guard lock(mutex_); + event_queue = event_queue_; + } + if (!event_queue) { return true; } - if (!xQueueReceive(event_queue_, &event, stop_poll_ticks)) { + if (!xQueueReceive(event_queue, &event, stop_poll_ticks)) { std::lock_guard lock(m); bool stop_requested = notified; notified = false; @@ -445,7 +461,14 @@ bool I2cSlaveDevice::event_task_callback(std::mutex &m, std::condition_variable } } - if (!initialized_ && event.type != EventType::STOP) { + bool initialized = false; + MessageBufferHandle_t callback_buffer = nullptr; + { + std::lock_guard lock(mutex_); + initialized = initialized_; + callback_buffer = callback_buffer_; + } + if (!initialized && event.type != EventType::STOP) { return false; } @@ -468,7 +491,7 @@ bool I2cSlaveDevice::event_task_callback(std::mutex &m, std::condition_variable case EventType::RECEIVE: { std::vector data(config_.receive_buffer_depth); size_t length = - callback_buffer_ ? xMessageBufferReceive(callback_buffer_, data.data(), data.size(), 0) : 0; + callback_buffer ? xMessageBufferReceive(callback_buffer, data.data(), data.size(), 0) : 0; if (length == 0) { break; } diff --git a/doc/en/i2c.rst b/doc/en/i2c.rst index da68b1624..f7e051d12 100644 --- a/doc/en/i2c.rst +++ b/doc/en/i2c.rst @@ -62,6 +62,10 @@ the next complete master-write transaction buffered by the receive callback path, ``write()`` stages bytes for the next master read, and optional request / receive callbacks run in task context instead of the ISR callback itself. +If you need the exact transaction size, use the ``read()`` overload that +returns the received length by reference. The original boolean-only ``read()`` +API remains available for compatibility. + On ESP-IDF v5.5's default slave driver, the receive callback does not expose the exact byte count. ESPP still supports buffered reads and receive callbacks in that configuration, but trailing unread bytes are zero-filled and the reported From 2900c9cf20dd49044535516e912f46757fe930d6 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 21:58:03 -0500 Subject: [PATCH 06/16] fix sa --- components/i2c/include/i2c_master.hpp | 2 +- components/i2c/src/i2c_slave.cpp | 40 ++++++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/components/i2c/include/i2c_master.hpp b/components/i2c/include/i2c_master.hpp index c11e99391..b38311e19 100644 --- a/components/i2c/include/i2c_master.hpp +++ b/components/i2c/include/i2c_master.hpp @@ -242,7 +242,7 @@ class I2cMasterBus : public BaseComponent { return nullptr; } auto device = std::make_shared>(bus_handle_, dev_config); - if (dev_config.auto_init && device && !device->initialized()) { + if (dev_config.auto_init && !device->initialized()) { logger_.error("I2C device auto-init failed at address 0x{:02x}", dev_config.device_address); ec = std::make_error_code(std::errc::io_error); return nullptr; diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index 5aa297c40..7d9754023 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -294,25 +294,27 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, size_t &received_len, std:: log_pending_overflows(); #if !ESPP_I2C_SLAVE_V2_API - size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); - if (next_length == 0 && !legacy_receive_armed_) { - if (len > config_.receive_buffer_depth) { - logger_.error( - "I2C slave read buffer too small for configured receive depth (need {}, have {})", - config_.receive_buffer_depth, len); - ec = std::make_error_code(std::errc::message_size); - return false; - } - std::fill(legacy_receive_buffer_.begin(), legacy_receive_buffer_.end(), 0); - legacy_receive_length_ = len; - legacy_receive_armed_ = true; - esp_err_t err = i2c_slave_receive(dev_handle_, legacy_receive_buffer_.data(), len); - if (err != ESP_OK) { - legacy_receive_armed_ = false; - legacy_receive_length_ = 0; - logger_.error("I2C slave read failed to arm receive: {}", esp_err_to_name(err)); - ec = make_error_code(err); - return false; + { + size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); + if (next_length == 0 && !legacy_receive_armed_) { + if (len > config_.receive_buffer_depth) { + logger_.error( + "I2C slave read buffer too small for configured receive depth (need {}, have {})", + config_.receive_buffer_depth, len); + ec = std::make_error_code(std::errc::message_size); + return false; + } + std::fill(legacy_receive_buffer_.begin(), legacy_receive_buffer_.end(), 0); + legacy_receive_length_ = len; + legacy_receive_armed_ = true; + esp_err_t err = i2c_slave_receive(dev_handle_, legacy_receive_buffer_.data(), len); + if (err != ESP_OK) { + legacy_receive_armed_ = false; + legacy_receive_length_ = 0; + logger_.error("I2C slave read failed to arm receive: {}", esp_err_to_name(err)); + ec = make_error_code(err); + return false; + } } } #endif From 9be81d95d17279efafa45ad02a82562669e50d07 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 22:07:53 -0500 Subject: [PATCH 07/16] fix include ordering to not break unintentional transitive includes --- components/i2c/include/i2c.hpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/components/i2c/include/i2c.hpp b/components/i2c/include/i2c.hpp index c48f08e11..fd047ac9c 100644 --- a/components/i2c/include/i2c.hpp +++ b/components/i2c/include/i2c.hpp @@ -2,20 +2,20 @@ #include -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) - #include +#include #include #include #include #include -#include - #include "base_component.hpp" +#include "i2c_format_helpers.hpp" #include "run_on_core.hpp" -#include "i2c_format_helpers.hpp" +#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) + +#include #if CONFIG_ESPP_I2C_LEGACY_API_DISABLE_DEPRECATION_WARNINGS #define ESPP_I2C_LEGACY_API_DEPRECATED_ATTR @@ -503,14 +503,6 @@ class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { #elif defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) -#include -#include -#include -#include -#include - -#include "base_component.hpp" -#include "i2c_format_helpers.hpp" #include "i2c_master.hpp" namespace espp { From 48e325b5210b91be2166da6defb406b942c2f0a8 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 22:10:20 -0500 Subject: [PATCH 08/16] fix examples that had transitively included task via i2c improperly --- components/bmi270/example/CMakeLists.txt | 4 ++-- components/bmi270/example/main/bmi270_example.cpp | 1 + components/icm42607/example/CMakeLists.txt | 2 +- components/icm42607/example/main/icm42607_example.cpp | 1 + components/lsm6dso/example/CMakeLists.txt | 2 +- components/lsm6dso/example/main/lsm6dso_example.cpp | 1 + components/qmi8658/example/CMakeLists.txt | 2 +- components/qmi8658/example/main/qmi8658_example.cpp | 1 + 8 files changed, 9 insertions(+), 5 deletions(-) diff --git a/components/bmi270/example/CMakeLists.txt b/components/bmi270/example/CMakeLists.txt index 9d21d5904..68d3bd464 100644 --- a/components/bmi270/example/CMakeLists.txt +++ b/components/bmi270/example/CMakeLists.txt @@ -12,11 +12,11 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py i2c bmi270 filters" + "main esptool_py i2c bmi270 filters task" CACHE STRING "List of components to include" ) project(bmi270_example) -set(CMAKE_CXX_STANDARD 20) \ No newline at end of file +set(CMAKE_CXX_STANDARD 20) diff --git a/components/bmi270/example/main/bmi270_example.cpp b/components/bmi270/example/main/bmi270_example.cpp index f0b7e73b0..d773071c1 100644 --- a/components/bmi270/example/main/bmi270_example.cpp +++ b/components/bmi270/example/main/bmi270_example.cpp @@ -5,6 +5,7 @@ #include "i2c.hpp" #include "kalman_filter.hpp" #include "madgwick_filter.hpp" +#include "task.hpp" using namespace std::chrono_literals; diff --git a/components/icm42607/example/CMakeLists.txt b/components/icm42607/example/CMakeLists.txt index 7d0fd5547..75f37a91c 100644 --- a/components/icm42607/example/CMakeLists.txt +++ b/components/icm42607/example/CMakeLists.txt @@ -12,7 +12,7 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py i2c icm42607 filters" + "main esptool_py i2c icm42607 filters task" CACHE STRING "List of components to include" ) diff --git a/components/icm42607/example/main/icm42607_example.cpp b/components/icm42607/example/main/icm42607_example.cpp index 6da3da901..c8398e0dc 100644 --- a/components/icm42607/example/main/icm42607_example.cpp +++ b/components/icm42607/example/main/icm42607_example.cpp @@ -5,6 +5,7 @@ #include "icm42607.hpp" #include "kalman_filter.hpp" #include "madgwick_filter.hpp" +#include "task.hpp" using namespace std::chrono_literals; diff --git a/components/lsm6dso/example/CMakeLists.txt b/components/lsm6dso/example/CMakeLists.txt index 6ee405dc6..65ddf16c1 100644 --- a/components/lsm6dso/example/CMakeLists.txt +++ b/components/lsm6dso/example/CMakeLists.txt @@ -12,7 +12,7 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py i2c lsm6dso filters" + "main esptool_py i2c lsm6dso filters task" CACHE STRING "List of components to include" ) diff --git a/components/lsm6dso/example/main/lsm6dso_example.cpp b/components/lsm6dso/example/main/lsm6dso_example.cpp index 227cccaa7..86db02ccc 100644 --- a/components/lsm6dso/example/main/lsm6dso_example.cpp +++ b/components/lsm6dso/example/main/lsm6dso_example.cpp @@ -7,6 +7,7 @@ #include "logger.hpp" #include "lsm6dso.hpp" #include "madgwick_filter.hpp" +#include "task.hpp" using namespace std::chrono_literals; diff --git a/components/qmi8658/example/CMakeLists.txt b/components/qmi8658/example/CMakeLists.txt index 80c0512c7..f0aee6e38 100644 --- a/components/qmi8658/example/CMakeLists.txt +++ b/components/qmi8658/example/CMakeLists.txt @@ -12,7 +12,7 @@ set(EXTRA_COMPONENT_DIRS set( COMPONENTS - "main esptool_py i2c qmi8658 filters" + "main esptool_py i2c qmi8658 filters task" CACHE STRING "List of components to include" ) diff --git a/components/qmi8658/example/main/qmi8658_example.cpp b/components/qmi8658/example/main/qmi8658_example.cpp index 3961fce7f..97f9b3157 100644 --- a/components/qmi8658/example/main/qmi8658_example.cpp +++ b/components/qmi8658/example/main/qmi8658_example.cpp @@ -5,6 +5,7 @@ #include "kalman_filter.hpp" #include "madgwick_filter.hpp" #include "qmi8658.hpp" +#include "task.hpp" using namespace std::chrono_literals; From bffffe09869a207947fd04d959717b0f6254abc7 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 22:22:57 -0500 Subject: [PATCH 09/16] address comments --- components/i2c/README.md | 11 ++++++----- doc/en/i2c.rst | 8 +++++--- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/components/i2c/README.md b/components/i2c/README.md index ef458dddd..fb6e2f9d3 100644 --- a/components/i2c/README.md +++ b/components/i2c/README.md @@ -4,11 +4,12 @@ The `i2c` component provides C++ wrappers around ESP-IDF's I2C drivers. -For ESP-IDF v6.x and newer ESPP defaults to the new master/slave bus API. The -primary `espp::I2c` class keeps the familiar address-based helper methods -(`probe_device`, `read`, `write`, `write_read`, `read_at_register`, etc.) for -backwards compatibility, while also allowing explicit per-device handles via -`add_device()`. +The new master/slave bus API is available on ESP-IDF >= 5.4.0 and is required +on ESP-IDF v6.x and newer. ESPP defaults to that driver family where it is +required, while the primary `espp::I2c` class keeps the familiar address-based +helper methods (`probe_device`, `read`, `write`, `write_read`, +`read_at_register`, etc.) for backwards compatibility and also allows explicit +per-device handles via `add_device()`. If you want direct access to the bus/device model, the component also exposes: diff --git a/doc/en/i2c.rst b/doc/en/i2c.rst index f7e051d12..7f5c19573 100644 --- a/doc/en/i2c.rst +++ b/doc/en/i2c.rst @@ -6,7 +6,8 @@ The `i2c` component provides C++ wrappers built on ESP-IDF's I2C drivers. There are two driver families available: - the deprecated legacy driver -- the new master/slave bus API used by ESP-IDF v6.x +- the new master/slave bus API available on ESP-IDF >= 5.4.0 and required on + ESP-IDF v6.x and newer Only one family can be selected at a time via Kconfig. @@ -18,8 +19,9 @@ API. It preserves the familiar address-based helper methods for backwards compatibility while internally using ESP-IDF's new master bus/device model. That means existing code using methods such as ``probe_device()``, ``read()``, -``write()``, ``write_read()``, and ``read_at_register()`` can keep working on -ESP-IDF v6.x without being rewritten around the raw ESP-IDF handles. +``write()``, ``write_read()``, and ``read_at_register()`` can keep working when +the new driver family is selected, without being rewritten around the raw +ESP-IDF handles. For code that wants explicit per-device ownership, ``espp::I2c`` also exposes ``add_device()`` which returns an ``I2cMasterDevice``. From 5cf66064946a0adcad93b33ea7728d9a8f23e0cf Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 22:42:03 -0500 Subject: [PATCH 10/16] revert commit to nimble submodule on this branch --- components/esp-nimble-cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp-nimble-cpp b/components/esp-nimble-cpp index 1eddc2851..3266951ae 160000 --- a/components/esp-nimble-cpp +++ b/components/esp-nimble-cpp @@ -1 +1 @@ -Subproject commit 1eddc28515bbb2ef29ccc2494d14584c40b400ad +Subproject commit 3266951aeea7249fd717f33182235e23db9acd8f From 57a2656ffa730cb48695aa3579aea18f06ac9b79 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 28 May 2026 23:17:05 -0500 Subject: [PATCH 11/16] update bsp to store the device handles --- components/byte90/include/byte90.hpp | 1 + components/byte90/src/accelerometer.cpp | 8 ++++---- components/esp-box/include/esp-box.hpp | 3 +++ components/esp-box/src/audio.cpp | 8 ++++---- components/esp-box/src/imu.cpp | 18 ++++++++++++++---- components/esp-box/src/touchpad.cpp | 14 +++++++------- .../include/esp32-timer-cam.hpp | 1 + .../esp32-timer-cam/src/esp32-timer-cam.cpp | 8 ++++---- .../m5stack-tab5/include/m5stack-tab5.hpp | 8 ++++++++ components/m5stack-tab5/src/audio.cpp | 16 ++++++++-------- components/m5stack-tab5/src/imu.cpp | 8 ++++---- components/m5stack-tab5/src/m5stack-tab5.cpp | 16 ++++++++-------- components/m5stack-tab5/src/power.cpp | 12 ++++++------ components/m5stack-tab5/src/rtc.cpp | 8 ++++---- components/m5stack-tab5/src/touchpad.cpp | 8 ++++---- .../include/matouch-rotary-display.hpp | 1 + .../src/matouch-rotary-display.cpp | 8 ++++---- .../include/seeed-studio-round-display.hpp | 1 + .../src/seeed-studio-round-display.cpp | 8 ++++---- .../include/smartpanlee-sc01-plus.hpp | 1 + .../src/smartpanlee-sc01-plus.cpp | 12 ++++++------ components/t-deck/include/t-deck.hpp | 2 ++ components/t-deck/src/audio.cpp | 1 + components/t-deck/src/t-deck.cpp | 16 ++++++++-------- components/ws-s3-touch/include/ws-s3-touch.hpp | 3 +++ components/ws-s3-touch/src/imu.cpp | 8 ++++---- components/ws-s3-touch/src/rtc.cpp | 8 ++++---- components/ws-s3-touch/src/touchpad.cpp | 8 ++++---- 28 files changed, 123 insertions(+), 91 deletions(-) diff --git a/components/byte90/include/byte90.hpp b/components/byte90/include/byte90.hpp index 44075307a..3587f556a 100644 --- a/components/byte90/include/byte90.hpp +++ b/components/byte90/include/byte90.hpp @@ -322,6 +322,7 @@ class Byte90 : public BaseComponent { button_callback_t button_callback_{nullptr}; // accelerometer + std::shared_ptr> accelerometer_i2c_device_{nullptr}; std::shared_ptr accelerometer_{nullptr}; accel_callback_t accel_callback_{nullptr}; std::recursive_mutex accel_data_mutex_; diff --git a/components/byte90/src/accelerometer.cpp b/components/byte90/src/accelerometer.cpp index 8afb9d1fc..9039680bb 100644 --- a/components/byte90/src/accelerometer.cpp +++ b/components/byte90/src/accelerometer.cpp @@ -19,7 +19,7 @@ bool Byte90::initialize_accelerometer(const Byte90::accel_callback_t &callback) accel_callback_ = callback; std::error_code ec; - auto accelerometer_device = internal_i2c_.add_device( + accelerometer_i2c_device_ = internal_i2c_.add_device( { .device_address = Accelerometer::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -27,7 +27,7 @@ bool Byte90::initialize_accelerometer(const Byte90::accel_callback_t &callback) .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!accelerometer_device) { + if (!accelerometer_i2c_device_) { logger_.error("Could not initialize accelerometer I2C device: {}", ec.message()); return false; } @@ -36,8 +36,8 @@ bool Byte90::initialize_accelerometer(const Byte90::accel_callback_t &callback) .device_address = Accelerometer::DEFAULT_ADDRESS, .range = Accelerometer::RANGE_2G, .data_rate = Accelerometer::RATE_100_HZ, - .write = espp::make_i2c_addressed_write(accelerometer_device), - .read = espp::make_i2c_addressed_read(accelerometer_device), + .write = espp::make_i2c_addressed_write(accelerometer_i2c_device_), + .read = espp::make_i2c_addressed_read(accelerometer_i2c_device_), .log_level = espp::Logger::Verbosity::WARN, }); diff --git a/components/esp-box/include/esp-box.hpp b/components/esp-box/include/esp-box.hpp index be7b1b87c..bacdf9774 100644 --- a/components/esp-box/include/esp-box.hpp +++ b/components/esp-box/include/esp-box.hpp @@ -490,6 +490,7 @@ class EspBox : public BaseComponent { button_callback_t mute_button_callback_{nullptr}; // touch + std::shared_ptr> touch_i2c_device_; std::shared_ptr gt911_; // only used on ESP32-S3-BOX-3 std::shared_ptr tt21100_; // only used on ESP32-S3-BOX std::shared_ptr touchpad_input_; @@ -508,6 +509,7 @@ class EspBox : public BaseComponent { uint8_t *frame_buffer1_{nullptr}; // sound + std::shared_ptr> codec_i2c_device_; std::atomic sound_initialized_{false}; std::atomic volume_{50.0f}; std::atomic mute_{false}; @@ -520,6 +522,7 @@ class EspBox : public BaseComponent { i2s_std_config_t audio_std_cfg; // IMU + std::shared_ptr> imu_i2c_device_; std::shared_ptr imu_; }; // class EspBox } // namespace espp diff --git a/components/esp-box/src/audio.cpp b/components/esp-box/src/audio.cpp index 209318a58..732f3874c 100644 --- a/components/esp-box/src/audio.cpp +++ b/components/esp-box/src/audio.cpp @@ -10,7 +10,7 @@ bool EspBox::initialize_codec() { logger_.info("initializing codec"); std::error_code ec; - auto codec_device = internal_i2c_.add_device( + codec_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x18, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -18,13 +18,13 @@ bool EspBox::initialize_codec() { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!codec_device) { + if (!codec_i2c_device_) { logger_.error("Could not initialize codec I2C device: {}", ec.message()); return false; } - set_es8311_write(espp::make_i2c_addressed_write(codec_device)); - set_es8311_read(espp::make_i2c_addressed_read_register(codec_device)); + set_es8311_write(espp::make_i2c_addressed_write(codec_i2c_device_)); + set_es8311_read(espp::make_i2c_addressed_read_register(codec_i2c_device_)); esp_err_t ret_val = ESP_OK; audio_hal_codec_config_t cfg; diff --git a/components/esp-box/src/imu.cpp b/components/esp-box/src/imu.cpp index 7990f2c6b..07f880256 100644 --- a/components/esp-box/src/imu.cpp +++ b/components/esp-box/src/imu.cpp @@ -10,7 +10,7 @@ bool EspBox::initialize_imu(const EspBox::Imu::filter_fn &orientation_filter, } std::error_code ec; - auto imu_device = internal_i2c_.add_device( + imu_i2c_device_ = internal_i2c_.add_device( { .device_address = Imu::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -18,15 +18,15 @@ bool EspBox::initialize_imu(const EspBox::Imu::filter_fn &orientation_filter, .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!imu_device) { + if (!imu_i2c_device_) { logger_.error("Could not initialize IMU I2C device: {}", ec.message()); return false; } Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = espp::make_i2c_addressed_write(imu_device), - .read = espp::make_i2c_addressed_read(imu_device), + .write = espp::make_i2c_addressed_write(imu_i2c_device_), + .read = espp::make_i2c_addressed_read(imu_i2c_device_), .imu_config = imu_config, .orientation_filter = orientation_filter, .auto_init = true, @@ -39,24 +39,34 @@ bool EspBox::initialize_imu(const EspBox::Imu::filter_fn &orientation_filter, // turn on DMP if (!imu_->set_dmp_power_save(false, ec)) { logger_.error("Failed to set DMP power save mode: {}", ec.message()); + imu_.reset(); + imu_i2c_device_.reset(); return false; } if (!imu_->dmp_initialize(ec)) { logger_.error("Failed to initialize DMP: {}", ec.message()); + imu_.reset(); + imu_i2c_device_.reset(); return false; } if (!imu_->set_dmp_odr(icm42607::DmpODR::ODR_25_HZ, ec)) { logger_.error("Failed to set DMP ODR: {}", ec.message()); + imu_.reset(); + imu_i2c_device_.reset(); return false; } // set filters for the accel / gyro static constexpr auto filter_bw = icm42607::SensorFilterBandwidth::BW_16_HZ; if (!imu_->set_accelerometer_filter(filter_bw, ec)) { logger_.error("Failed to set accel filter: {}", ec.message()); + imu_.reset(); + imu_i2c_device_.reset(); return false; } if (!imu_->set_gyroscope_filter(filter_bw, ec)) { logger_.error("Failed to set gyro filter: {}", ec.message()); + imu_.reset(); + imu_i2c_device_.reset(); return false; } diff --git a/components/esp-box/src/touchpad.cpp b/components/esp-box/src/touchpad.cpp index 6d9658429..b6d6ff597 100644 --- a/components/esp-box/src/touchpad.cpp +++ b/components/esp-box/src/touchpad.cpp @@ -15,7 +15,7 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { switch (box_type_) { case BoxType::BOX3: { std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = espp::Gt911::DEFAULT_ADDRESS_1, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -23,19 +23,19 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize GT911 I2C device: {}", ec.message()); return false; } logger_.info("Initializing GT911"); gt911_ = std::make_unique( - espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); } break; case BoxType::BOX: { std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = espp::Tt21100::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -43,13 +43,13 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize TT21100 I2C device: {}", ec.message()); return false; } logger_.info("Initializing TT21100"); tt21100_ = std::make_unique( - espp::Tt21100::Config{.read = espp::make_i2c_addressed_read(touch_device), + espp::Tt21100::Config{.read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); } break; default: diff --git a/components/esp32-timer-cam/include/esp32-timer-cam.hpp b/components/esp32-timer-cam/include/esp32-timer-cam.hpp index ec2ec8af3..e3d7a8646 100644 --- a/components/esp32-timer-cam/include/esp32-timer-cam.hpp +++ b/components/esp32-timer-cam/include/esp32-timer-cam.hpp @@ -255,6 +255,7 @@ class EspTimerCam : public BaseComponent { espp::Gaussian gaussian_{{.gamma = 0.1f, .alpha = 1.0f, .beta = 0.5f}}; // RTC + std::shared_ptr> rtc_i2c_device_; std::shared_ptr rtc_; // Battery ADC diff --git a/components/esp32-timer-cam/src/esp32-timer-cam.cpp b/components/esp32-timer-cam/src/esp32-timer-cam.cpp index e5b7c69b8..e5d94eae5 100644 --- a/components/esp32-timer-cam/src/esp32-timer-cam.cpp +++ b/components/esp32-timer-cam/src/esp32-timer-cam.cpp @@ -118,7 +118,7 @@ bool EspTimerCam::initialize_rtc() { return false; } std::error_code ec; - auto rtc_device = internal_i2c_.add_device( + rtc_i2c_device_ = internal_i2c_.add_device( { .device_address = EspTimerCam::Rtc::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -126,13 +126,13 @@ bool EspTimerCam::initialize_rtc() { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!rtc_device) { + if (!rtc_i2c_device_) { logger_.error("Could not initialize RTC I2C device: {}", ec.message()); return false; } rtc_ = std::make_shared(EspTimerCam::Rtc::Config{ - .write = espp::make_i2c_addressed_write(rtc_device), - .write_then_read = espp::make_i2c_addressed_write_then_read(rtc_device), + .write = espp::make_i2c_addressed_write(rtc_i2c_device_), + .write_then_read = espp::make_i2c_addressed_write_then_read(rtc_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); return true; } diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index 47daa1d4d..0102d3c5f 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -678,12 +678,14 @@ class M5StackTab5 : public BaseComponent { .stack_size_bytes = CONFIG_M5STACK_TAB5_INTERRUPT_STACK_SIZE}}}; // Component instances + std::shared_ptr> touch_i2c_device_; std::shared_ptr touch_driver_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; touch_callback_t touch_callback_{nullptr}; + std::shared_ptr> imu_i2c_device_; std::shared_ptr imu_; // Button callbacks @@ -693,6 +695,8 @@ class M5StackTab5 : public BaseComponent { std::atomic audio_initialized_{false}; std::atomic volume_{50.0f}; std::atomic mute_{false}; + std::shared_ptr> es8388_i2c_device_; + std::shared_ptr> es7210_i2c_device_; std::unique_ptr audio_task_{nullptr}; i2s_chan_handle_t audio_tx_handle{nullptr}; i2s_chan_handle_t audio_rx_handle{nullptr}; @@ -711,8 +715,11 @@ class M5StackTab5 : public BaseComponent { std::atomic battery_monitoring_initialized_{false}; BatteryStatus battery_status_; std::mutex battery_mutex_; + std::shared_ptr> battery_monitor_i2c_device_; std::shared_ptr battery_monitor_; // IO expanders on the internal I2C (addresses 0x43 and 0x44 per Tab5 design) + std::shared_ptr> ioexp_0x43_i2c_device_; + std::shared_ptr> ioexp_0x44_i2c_device_; std::shared_ptr ioexp_0x43_; std::shared_ptr ioexp_0x44_; @@ -724,6 +731,7 @@ class M5StackTab5 : public BaseComponent { // RTC std::atomic rtc_initialized_{false}; + std::shared_ptr> rtc_i2c_device_; std::shared_ptr rtc_; // Display state diff --git a/components/m5stack-tab5/src/audio.cpp b/components/m5stack-tab5/src/audio.cpp index 18db44e8f..60be9ecea 100644 --- a/components/m5stack-tab5/src/audio.cpp +++ b/components/m5stack-tab5/src/audio.cpp @@ -25,7 +25,7 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, } std::error_code ec; - auto es8388_device = internal_i2c_.add_device( + es8388_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x10, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -33,11 +33,11 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!es8388_device) { + if (!es8388_i2c_device_) { logger_.error("Could not initialize ES8388 I2C device: {}", ec.message()); return false; } - auto es7210_device = internal_i2c_.add_device( + es7210_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x40, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -45,16 +45,16 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!es7210_device) { + if (!es7210_i2c_device_) { logger_.error("Could not initialize ES7210 I2C device: {}", ec.message()); return false; } // Wire codec register access over internal I2C - set_es8388_write(espp::make_i2c_addressed_write(es8388_device)); - set_es8388_read(espp::make_i2c_addressed_read_register(es8388_device)); - set_es7210_write(espp::make_i2c_addressed_write(es7210_device)); - set_es7210_read(espp::make_i2c_addressed_read_register(es7210_device)); + set_es8388_write(espp::make_i2c_addressed_write(es8388_i2c_device_)); + set_es8388_read(espp::make_i2c_addressed_read_register(es8388_i2c_device_)); + set_es7210_write(espp::make_i2c_addressed_write(es7210_i2c_device_)); + set_es7210_read(espp::make_i2c_addressed_read_register(es7210_i2c_device_)); // I2S standard channel for TX (playback) logger_.info("Creating I2S channel for playback (TX)"); diff --git a/components/m5stack-tab5/src/imu.cpp b/components/m5stack-tab5/src/imu.cpp index 6e55fd204..9b38c8a5c 100644 --- a/components/m5stack-tab5/src/imu.cpp +++ b/components/m5stack-tab5/src/imu.cpp @@ -11,7 +11,7 @@ bool M5StackTab5::initialize_imu(const Imu::filter_fn &orientation_filter) { logger_.info("Initializing BMI270 6-axis IMU"); std::error_code ec; - auto imu_device = internal_i2c_.add_device( + imu_i2c_device_ = internal_i2c_.add_device( { .device_address = Imu::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -19,15 +19,15 @@ bool M5StackTab5::initialize_imu(const Imu::filter_fn &orientation_filter) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!imu_device) { + if (!imu_i2c_device_) { logger_.error("Could not initialize IMU I2C device: {}", ec.message()); return false; } // Create BMI270 instance imu_ = std::make_shared(Imu::Config{ - .write = espp::make_i2c_addressed_write(imu_device), - .read = espp::make_i2c_addressed_read(imu_device), + .write = espp::make_i2c_addressed_write(imu_i2c_device_), + .read = espp::make_i2c_addressed_read(imu_i2c_device_), .imu_config = { .accelerometer_range = Imu::AccelerometerRange::RANGE_4G, diff --git a/components/m5stack-tab5/src/m5stack-tab5.cpp b/components/m5stack-tab5/src/m5stack-tab5.cpp index a8ec9d518..feb62f1c4 100644 --- a/components/m5stack-tab5/src/m5stack-tab5.cpp +++ b/components/m5stack-tab5/src/m5stack-tab5.cpp @@ -16,7 +16,7 @@ M5StackTab5::M5StackTab5() bool M5StackTab5::initialize_io_expanders() { logger_.info("Initializing IO expanders (0x43, 0x44)"); std::error_code ec; - auto ioexp_0x43_device = internal_i2c_.add_device( + ioexp_0x43_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x43, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -24,11 +24,11 @@ bool M5StackTab5::initialize_io_expanders() { .log_level = Logger::Verbosity::INFO, }, ec); - if (!ioexp_0x43_device) { + if (!ioexp_0x43_i2c_device_) { logger_.error("Could not initialize IO expander 0x43 I2C device: {}", ec.message()); return false; } - auto ioexp_0x44_device = internal_i2c_.add_device( + ioexp_0x44_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x44, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -36,7 +36,7 @@ bool M5StackTab5::initialize_io_expanders() { .log_level = Logger::Verbosity::INFO, }, ec); - if (!ioexp_0x44_device) { + if (!ioexp_0x44_i2c_device_) { logger_.error("Could not initialize IO expander 0x44 I2C device: {}", ec.message()); return false; } @@ -47,8 +47,8 @@ bool M5StackTab5::initialize_io_expanders() { .high_z_mask = IOX_0x43_HIGH_Z_MASK, .pull_up_mask = IOX_0x43_PULL_UPS, .pull_down_mask = IOX_0x43_PULL_DOWNS, - .write = espp::make_i2c_addressed_write(ioexp_0x43_device), - .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x43_device), + .write = espp::make_i2c_addressed_write(ioexp_0x43_i2c_device_), + .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x43_i2c_device_), .log_level = Logger::Verbosity::INFO}); ioexp_0x44_ = std::make_shared(IoExpander::Config{ .device_address = 0x44, @@ -57,8 +57,8 @@ bool M5StackTab5::initialize_io_expanders() { .high_z_mask = IOX_0x44_HIGH_Z_MASK, .pull_up_mask = IOX_0x44_PULL_UPS, .pull_down_mask = IOX_0x44_PULL_DOWNS, - .write = espp::make_i2c_addressed_write(ioexp_0x44_device), - .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x44_device), + .write = espp::make_i2c_addressed_write(ioexp_0x44_i2c_device_), + .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x44_i2c_device_), .log_level = Logger::Verbosity::INFO}); logger_.info("IO expanders initialized"); diff --git a/components/m5stack-tab5/src/power.cpp b/components/m5stack-tab5/src/power.cpp index 642c83a48..e8d1dbf8a 100644 --- a/components/m5stack-tab5/src/power.cpp +++ b/components/m5stack-tab5/src/power.cpp @@ -10,7 +10,7 @@ bool M5StackTab5::initialize_battery_monitoring() { logger_.info("Initializing battery monitoring (INA226)"); std::error_code ec; - auto battery_device = internal_i2c_.add_device( + battery_monitor_i2c_device_ = internal_i2c_.add_device( { .device_address = 0x41, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -18,7 +18,7 @@ bool M5StackTab5::initialize_battery_monitoring() { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!battery_device) { + if (!battery_monitor_i2c_device_) { logger_.error("Could not initialize battery monitor I2C device: {}", ec.message()); return false; } @@ -32,10 +32,10 @@ bool M5StackTab5::initialize_battery_monitoring() { .mode = BatteryMonitor::Mode::SHUNT_BUS_CONT, .current_lsb = 0.0005f, // 0.5 mA / LSB .shunt_resistance_ohms = 0.005f, // 5 mΩ (adjust if different on board) - .probe = espp::make_i2c_addressed_probe(battery_device), - .write = espp::make_i2c_addressed_write(battery_device), - .read_register = espp::make_i2c_addressed_read_register(battery_device), - .write_then_read = espp::make_i2c_addressed_write_then_read(battery_device), + .probe = espp::make_i2c_addressed_probe(battery_monitor_i2c_device_), + .write = espp::make_i2c_addressed_write(battery_monitor_i2c_device_), + .read_register = espp::make_i2c_addressed_read_register(battery_monitor_i2c_device_), + .write_then_read = espp::make_i2c_addressed_write_then_read(battery_monitor_i2c_device_), .auto_init = true, .log_level = espp::Logger::Verbosity::WARN, }; diff --git a/components/m5stack-tab5/src/rtc.cpp b/components/m5stack-tab5/src/rtc.cpp index 71b2b3b0d..111d5b352 100644 --- a/components/m5stack-tab5/src/rtc.cpp +++ b/components/m5stack-tab5/src/rtc.cpp @@ -15,7 +15,7 @@ bool M5StackTab5::initialize_rtc() { } std::error_code ec; - auto rtc_device = internal_i2c_.add_device( + rtc_i2c_device_ = internal_i2c_.add_device( { .device_address = Rtc::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -23,14 +23,14 @@ bool M5StackTab5::initialize_rtc() { .log_level = Logger::Verbosity::WARN, }, ec); - if (!rtc_device) { + if (!rtc_i2c_device_) { logger_.error("Could not initialize RTC I2C device: {}", ec.message()); return false; } rtc_ = std::make_shared(Rtc::Config{ .device_address = Rtc::DEFAULT_ADDRESS, - .write = espp::make_i2c_addressed_write(rtc_device), - .read = espp::make_i2c_addressed_read(rtc_device), + .write = espp::make_i2c_addressed_write(rtc_i2c_device_), + .read = espp::make_i2c_addressed_read(rtc_i2c_device_), .auto_init = true, .log_level = Logger::Verbosity::WARN, }); diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp index 87710845f..71612df76 100644 --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -23,7 +23,7 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { std::this_thread::sleep_for(50ms); std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = TouchDriver::DEFAULT_ADDRESS_2, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -31,15 +31,15 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } // Create touch driver instance touch_driver_ = std::make_shared( - TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 address .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/matouch-rotary-display/include/matouch-rotary-display.hpp b/components/matouch-rotary-display/include/matouch-rotary-display.hpp index 683b93814..f560d2562 100644 --- a/components/matouch-rotary-display/include/matouch-rotary-display.hpp +++ b/components/matouch-rotary-display/include/matouch-rotary-display.hpp @@ -329,6 +329,7 @@ class MatouchRotaryDisplay : public BaseComponent { // touch touch_callback_t touch_callback_{nullptr}; + std::shared_ptr> touch_i2c_device_; std::shared_ptr cst816_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; diff --git a/components/matouch-rotary-display/src/matouch-rotary-display.cpp b/components/matouch-rotary-display/src/matouch-rotary-display.cpp index 98d62288e..648bcdcb8 100644 --- a/components/matouch-rotary-display/src/matouch-rotary-display.cpp +++ b/components/matouch-rotary-display/src/matouch-rotary-display.cpp @@ -86,7 +86,7 @@ bool MatouchRotaryDisplay::initialize_touch( logger_.info("Initializing touch input"); std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = espp::Cst816::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -94,13 +94,13 @@ bool MatouchRotaryDisplay::initialize_touch( .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } cst816_ = std::make_shared( - espp::Cst816::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + espp::Cst816::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); // save the callback diff --git a/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp b/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp index b1d8c87bf..3f9509de4 100644 --- a/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp +++ b/components/seeed-studio-round-display/include/seeed-studio-round-display.hpp @@ -292,6 +292,7 @@ class SsRoundDisplay : public espp::BaseComponent { CONFIG_SEEED_STUDIO_ROUND_DISPLAY_INTERRUPT_STACK_SIZE}}}; // touch + std::shared_ptr> touch_i2c_device_; std::shared_ptr touch_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; diff --git a/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp b/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp index 0678d3ac8..848b1556d 100644 --- a/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp +++ b/components/seeed-studio-round-display/src/seeed-studio-round-display.cpp @@ -40,7 +40,7 @@ bool SsRoundDisplay::initialize_touch(const SsRoundDisplay::touch_callback_t &ca logger_.info("Initializing Touch Driver"); std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = TouchDriver::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -48,13 +48,13 @@ bool SsRoundDisplay::initialize_touch(const SsRoundDisplay::touch_callback_t &ca .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } touch_ = std::make_unique( - TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); // store the callback diff --git a/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp b/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp index aa2582ad3..ab165edc4 100755 --- a/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp +++ b/components/smartpanlee-sc01-plus/include/smartpanlee-sc01-plus.hpp @@ -338,6 +338,7 @@ class SmartPanleeSc01Plus : public BaseComponent { .task_config = {.name = "sc01+ interrupts", .stack_size_bytes = CONFIG_SMARTPANLEE_SC01_PLUS_INTERRUPT_STACK_SIZE}}}; + std::shared_ptr> touch_i2c_device_; std::shared_ptr touch_driver_; std::shared_ptr touchpad_input_; mutable std::recursive_mutex touchpad_data_mutex_; diff --git a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp index 76922ff39..2ae268e42 100644 --- a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp +++ b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp @@ -33,7 +33,7 @@ bool SmartPanleeSc01Plus::initialize_touch(const SmartPanleeSc01Plus::touch_call } std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = TouchDriver::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -41,14 +41,14 @@ bool SmartPanleeSc01Plus::initialize_touch(const SmartPanleeSc01Plus::touch_call .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } - touch_driver_ = std::make_shared( - TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read_register = espp::make_i2c_addressed_read_register(touch_device), - .log_level = espp::Logger::Verbosity::WARN}); + touch_driver_ = std::make_shared(TouchDriver::Config{ + .write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read_register = espp::make_i2c_addressed_read_register(touch_i2c_device_), + .log_level = espp::Logger::Verbosity::WARN}); touch_callback_ = callback; interrupts_.add_interrupt(touch_interrupt_pin_); diff --git a/components/t-deck/include/t-deck.hpp b/components/t-deck/include/t-deck.hpp index f5e3374c1..a8dc3f6a9 100644 --- a/components/t-deck/include/t-deck.hpp +++ b/components/t-deck/include/t-deck.hpp @@ -626,6 +626,7 @@ class TDeck : public BaseComponent { .priority = 20}}}; // keyboard + std::shared_ptr> keyboard_i2c_device_{nullptr}; std::shared_ptr keyboard_{nullptr}; // trackball @@ -636,6 +637,7 @@ class TDeck : public BaseComponent { trackball_callback_t trackball_callback_{nullptr}; // touch + std::shared_ptr> touch_i2c_device_; std::shared_ptr gt911_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; diff --git a/components/t-deck/src/audio.cpp b/components/t-deck/src/audio.cpp index 46e97fd13..cea2507fa 100644 --- a/components/t-deck/src/audio.cpp +++ b/components/t-deck/src/audio.cpp @@ -29,6 +29,7 @@ bool TDeck::initialize_i2s(uint32_t default_audio_rate) { .clk_src = I2S_CLK_SRC_DEFAULT, .ext_clk_freq_hz = 0, .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, }, .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), diff --git a/components/t-deck/src/t-deck.cpp b/components/t-deck/src/t-deck.cpp index b789db6db..66de0c60f 100644 --- a/components/t-deck/src/t-deck.cpp +++ b/components/t-deck/src/t-deck.cpp @@ -58,7 +58,7 @@ bool TDeck::initialize_keyboard(bool start_task, const TDeck::keypress_callback_ } logger_.info("Initializing keyboard input"); std::error_code ec; - auto keyboard_device = internal_i2c_.add_device( + keyboard_i2c_device_ = internal_i2c_.add_device( { .device_address = espp::TKeyboard::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -66,13 +66,13 @@ bool TDeck::initialize_keyboard(bool start_task, const TDeck::keypress_callback_ .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!keyboard_device) { + if (!keyboard_i2c_device_) { logger_.error("Could not initialize keyboard I2C device: {}", ec.message()); return false; } keyboard_ = std::make_shared( - espp::TKeyboard::Config{.write = espp::make_i2c_addressed_write(keyboard_device), - .read = espp::make_i2c_addressed_read(keyboard_device), + espp::TKeyboard::Config{.write = espp::make_i2c_addressed_write(keyboard_i2c_device_), + .read = espp::make_i2c_addressed_read(keyboard_i2c_device_), .key_cb = key_cb, .polling_interval = poll_interval, .auto_start = start_task, @@ -160,7 +160,7 @@ bool TDeck::initialize_touch(const TDeck::touch_callback_t &touch_cb) { logger_.info("Initializing touch input"); std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = espp::Gt911::DEFAULT_ADDRESS_1, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -168,14 +168,14 @@ bool TDeck::initialize_touch(const TDeck::touch_callback_t &touch_cb) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } gt911_ = std::make_unique( - espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); // store the callback diff --git a/components/ws-s3-touch/include/ws-s3-touch.hpp b/components/ws-s3-touch/include/ws-s3-touch.hpp index e505fbbbf..bf7d1ae7e 100644 --- a/components/ws-s3-touch/include/ws-s3-touch.hpp +++ b/components/ws-s3-touch/include/ws-s3-touch.hpp @@ -392,6 +392,7 @@ class WsS3Touch : public BaseComponent { button_callback_t boot_button_callback_{nullptr}; // touch + std::shared_ptr> touch_i2c_device_; std::shared_ptr touch_driver_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; @@ -416,7 +417,9 @@ class WsS3Touch : public BaseComponent { }; // IMU + std::shared_ptr> imu_i2c_device_; std::shared_ptr imu_; + std::shared_ptr> rtc_i2c_device_; std::shared_ptr rtc_; }; // class WsS3Touch } // namespace espp diff --git a/components/ws-s3-touch/src/imu.cpp b/components/ws-s3-touch/src/imu.cpp index c94d0395c..017e35b1a 100644 --- a/components/ws-s3-touch/src/imu.cpp +++ b/components/ws-s3-touch/src/imu.cpp @@ -10,7 +10,7 @@ bool WsS3Touch::initialize_imu(const WsS3Touch::Imu::filter_fn &orientation_filt } std::error_code ec; - auto imu_device = internal_i2c_.add_device( + imu_i2c_device_ = internal_i2c_.add_device( { .device_address = Imu::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -18,15 +18,15 @@ bool WsS3Touch::initialize_imu(const WsS3Touch::Imu::filter_fn &orientation_filt .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!imu_device) { + if (!imu_i2c_device_) { logger_.error("Could not initialize IMU I2C device: {}", ec.message()); return false; } Imu::Config config{ .device_address = Imu::DEFAULT_ADDRESS, - .write = espp::make_i2c_addressed_write(imu_device), - .read = espp::make_i2c_addressed_read(imu_device), + .write = espp::make_i2c_addressed_write(imu_i2c_device_), + .read = espp::make_i2c_addressed_read(imu_i2c_device_), .imu_config = imu_config, .orientation_filter = orientation_filter, .auto_init = true, diff --git a/components/ws-s3-touch/src/rtc.cpp b/components/ws-s3-touch/src/rtc.cpp index 4d81a9a7c..0caa7746e 100644 --- a/components/ws-s3-touch/src/rtc.cpp +++ b/components/ws-s3-touch/src/rtc.cpp @@ -9,7 +9,7 @@ bool WsS3Touch::initialize_rtc() { } logger_.info("Initializing RTC"); std::error_code ec; - auto rtc_device = internal_i2c_.add_device( + rtc_i2c_device_ = internal_i2c_.add_device( { .device_address = Rtc::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -17,14 +17,14 @@ bool WsS3Touch::initialize_rtc() { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!rtc_device) { + if (!rtc_i2c_device_) { logger_.error("Could not initialize RTC I2C device: {}", ec.message()); return false; } rtc_ = std::make_shared(Rtc::Config{ .device_address = Rtc::DEFAULT_ADDRESS, - .write = espp::make_i2c_addressed_write(rtc_device), - .read = espp::make_i2c_addressed_read(rtc_device), + .write = espp::make_i2c_addressed_write(rtc_i2c_device_), + .read = espp::make_i2c_addressed_read(rtc_i2c_device_), .log_level = espp::Logger::Verbosity::WARN, }); return true; diff --git a/components/ws-s3-touch/src/touchpad.cpp b/components/ws-s3-touch/src/touchpad.cpp index 48de54934..52aabf7b2 100644 --- a/components/ws-s3-touch/src/touchpad.cpp +++ b/components/ws-s3-touch/src/touchpad.cpp @@ -14,7 +14,7 @@ bool WsS3Touch::initialize_touch(const WsS3Touch::touch_callback_t &callback) { logger_.info("Initializing TouchDriver"); std::error_code ec; - auto touch_device = internal_i2c_.add_device( + touch_i2c_device_ = internal_i2c_.add_device( { .device_address = TouchDriver::DEFAULT_ADDRESS, .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), @@ -22,13 +22,13 @@ bool WsS3Touch::initialize_touch(const WsS3Touch::touch_callback_t &callback) { .log_level = espp::Logger::Verbosity::WARN, }, ec); - if (!touch_device) { + if (!touch_i2c_device_) { logger_.error("Could not initialize touch I2C device: {}", ec.message()); return false; } touch_driver_ = std::make_unique( - TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_device), - .read = espp::make_i2c_addressed_read(touch_device), + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); // store the callback From bf5e8f08c0060ad82e8ec42041c4f615c1374d99 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 29 May 2026 11:42:06 -0500 Subject: [PATCH 12/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- components/i2c/src/i2c_slave.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index 7d9754023..038f91af9 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -298,9 +298,8 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, size_t &received_len, std:: size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); if (next_length == 0 && !legacy_receive_armed_) { if (len > config_.receive_buffer_depth) { - logger_.error( - "I2C slave read buffer too small for configured receive depth (need {}, have {})", - config_.receive_buffer_depth, len); + logger_.error("I2C slave read request ({}) exceeds configured receive_buffer_depth ({})", len, + config_.receive_buffer_depth); ec = std::make_error_code(std::errc::message_size); return false; } From bb6b037a3031302847b3a510afd2b814234b597b Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 29 May 2026 11:42:50 -0500 Subject: [PATCH 13/16] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- components/i2c/example/main/i2c_example.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/i2c/example/main/i2c_example.cpp b/components/i2c/example/main/i2c_example.cpp index b68dd3182..a7847e297 100644 --- a/components/i2c/example/main/i2c_example.cpp +++ b/components/i2c/example/main/i2c_example.cpp @@ -48,7 +48,7 @@ extern "C" void app_main(void) { ec); //! [i2c device creation example] if (!device) { - logger.error("Failed to add compatibility device: {}", ec.message()); + logger.error("Failed to add explicit I2C device: {}", ec.message()); } else { espp::I2cMasterDeviceMenu i2c_device_menu(device); root_menu->Insert(i2c_device_menu.get()); From 58ac92901b646375aeed822e895419895a1d1801 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 29 May 2026 11:51:14 -0500 Subject: [PATCH 14/16] address comments to clean up parsing of hex/decimal in menu --- .../i2c/include/i2c_master_device_menu.hpp | 2 +- components/i2c/include/i2c_master_menu.hpp | 12 +++++---- components/i2c/include/i2c_menu.hpp | 26 ++++++++++--------- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/components/i2c/include/i2c_master_device_menu.hpp b/components/i2c/include/i2c_master_device_menu.hpp index ff8741e85..4877647c1 100644 --- a/components/i2c/include/i2c_master_device_menu.hpp +++ b/components/i2c/include/i2c_master_device_menu.hpp @@ -66,7 +66,7 @@ template class I2cMasterDeviceMenu { // Write to a register menu->Insert( - "write", {"register", "data byte (hex)", "data byte (hex)", "..."}, + "write", {"register", "data byte (hex/dec)", "data byte (hex/dec)", "..."}, [device](std::ostream &out, const std::vector &args) -> void { if (args.size() < 2) { out << "Not enough arguments.\n"; diff --git a/components/i2c/include/i2c_master_menu.hpp b/components/i2c/include/i2c_master_menu.hpp index 4347ec08a..414894d98 100644 --- a/components/i2c/include/i2c_master_menu.hpp +++ b/components/i2c/include/i2c_master_menu.hpp @@ -52,18 +52,18 @@ class I2cMasterMenu { "scan", [bus](std::ostream &out) -> void { scan_bus(out, *bus); }, "Scan the I2c master bus for devices."); - // Probe for a device (hexadecimal address string) + // Probe for a device (hex or decimal address string) menu->Insert( - "probe", {"address (hex)"}, + "probe", {"address (hex/dec)"}, [bus](std::ostream &out, const std::string &address_string) -> void { uint16_t address = std::stoi(address_string, nullptr, 0); probe_device(out, *bus, address); }, - "Probe for a device at a specific address, given as a hexadecimal string."); + "Probe for a device at a specific address, given as a hex or decimal string."); // Read from a device menu->Insert( - "read", {"address (hex)", "register", "length (number of bytes to read)"}, + "read", {"address (hex/dec)", "register", "length (number of bytes to read)"}, [bus](std::ostream &out, const std::string &address_string, uint8_t reg, uint8_t len) -> void { uint16_t address = std::stoi(address_string, nullptr, 0); @@ -73,7 +73,9 @@ class I2cMasterMenu { // Write to a device menu->Insert( - "write", {"address (hex)", "register (hex)", "data byte (hex)", "data byte (hex)", "..."}, + "write", + {"address (hex/dec)", "register (hex/dec)", "data byte (hex/dec)", "data byte (hex/dec)", + "..."}, [bus](std::ostream &out, const std::vector &args) -> void { if (args.size() < 3) { out << "Not enough arguments.\n"; diff --git a/components/i2c/include/i2c_menu.hpp b/components/i2c/include/i2c_menu.hpp index 36547e6b7..35709a8b2 100644 --- a/components/i2c/include/i2c_menu.hpp +++ b/components/i2c/include/i2c_menu.hpp @@ -52,44 +52,44 @@ class I2cMenu { "scan", [this](std::ostream &out) -> void { scan_bus(out); }, "Scan the I2c bus for devices."); - // probe for a device (hexadecimal address string) + // probe for a device (hex or decimal address string) i2c_menu->Insert( - "probe", {"address (hex)"}, + "probe", {"address (hex/dec)"}, [this](std::ostream &out, const std::string &address_string) -> void { // convert address_string to a uint8_t - uint8_t address = std::stoi(address_string, nullptr, 16); + uint8_t address = std::stoi(address_string, nullptr, 0); probe_device(out, address); }, - "Probe for a device at a specific address, given as a hexadecimal string."); + "Probe for a device at a specific address, given as a hex or decimal string."); // read from a device i2c_menu->Insert( - "read", {"address (hex)", "register"}, + "read", {"address (hex/dec)", "register"}, [this](std::ostream &out, const std::string &address_string, uint8_t reg) -> void { // convert address_string to a uint8_t - uint8_t address = std::stoi(address_string, nullptr, 16); + uint8_t address = std::stoi(address_string, nullptr, 0); read_device(out, address, reg, 1); }, "Read a byte from a device at a specific address and register."); // read from a device i2c_menu->Insert( - "read", {"address (hex)", "register", "length (number of bytes to read)"}, + "read", {"address (hex/dec)", "register", "length (number of bytes to read)"}, [this](std::ostream &out, const std::string &address_string, uint8_t reg, uint8_t len) -> void { // convert address_string to a uint8_t - uint8_t address = std::stoi(address_string, nullptr, 16); + uint8_t address = std::stoi(address_string, nullptr, 0); read_device(out, address, reg, len); }, "Read len bytes from a device at a specific address and register."); // write to a device i2c_menu->Insert( - "write", {"address (hex)", "register", "data byte"}, + "write", {"address (hex/dec)", "register", "data byte"}, [this](std::ostream &out, const std::string &address_string, uint8_t reg, uint8_t data) -> void { // convert address_string to a uint8_t - uint8_t address = std::stoi(address_string, nullptr, 16); + uint8_t address = std::stoi(address_string, nullptr, 0); std::vector data_vector = {reg, data}; write_device(out, address, data_vector); }, @@ -97,7 +97,9 @@ class I2cMenu { // write to a device i2c_menu->Insert( - "write", {"address (hex)", "register (hex)", "data byte (hex)", "data byte (hex)", "..."}, + "write", + {"address (hex/dec)", "register (hex/dec)", "data byte (hex/dec)", "data byte (hex/dec)", + "..."}, [this](std::ostream &out, const std::vector &args) -> void { // parse the args into address, reg, and data if (args.size() < 3) { @@ -105,7 +107,7 @@ class I2cMenu { return; } // convert address_string to a uint8_t - uint8_t address = std::stoi(args[0], nullptr, 16); + uint8_t address = std::stoi(args[0], nullptr, 0); // remove the address byte (first element) and convert the rest of the // vector of strings into a vector of bytes std::vector data; From afb76208eb6e7ff74ec06a5f3f72b2d606bbfbf5 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 29 May 2026 12:46:23 -0500 Subject: [PATCH 15/16] address comments --- components/i2c/include/i2c.hpp | 10 +++++++--- components/i2c/include/i2c_menu.hpp | 1 + components/i2c/include/i2c_slave.hpp | 1 + components/i2c/src/i2c_slave.cpp | 25 ++++++++++++++++--------- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/components/i2c/include/i2c.hpp b/components/i2c/include/i2c.hpp index fd047ac9c..d34edf695 100644 --- a/components/i2c/include/i2c.hpp +++ b/components/i2c/include/i2c.hpp @@ -615,6 +615,7 @@ class I2c : public BaseComponent { ec.clear(); return; } + initialized_ = false; compatibility_devices.swap(compatibility_devices_); } @@ -634,9 +635,6 @@ class I2c : public BaseComponent { std::error_code bus_ec; master_bus_.deinit(bus_ec); - - std::lock_guard lock(mutex_); - initialized_ = bus_ec ? true : false; if (bus_ec) { ec = bus_ec; return; @@ -663,6 +661,12 @@ class I2c : public BaseComponent { template std::shared_ptr> add_device(const DeviceConfig &dev_config, std::error_code &ec) { + std::lock_guard lock(mutex_); + if (!initialized_) { + logger_.error("not initialized"); + ec = std::make_error_code(std::errc::not_connected); + return nullptr; + } return master_bus_.add_device(dev_config, ec); } diff --git a/components/i2c/include/i2c_menu.hpp b/components/i2c/include/i2c_menu.hpp index 35709a8b2..ec8a2eaf8 100644 --- a/components/i2c/include/i2c_menu.hpp +++ b/components/i2c/include/i2c_menu.hpp @@ -2,6 +2,7 @@ #include +#include #include #include #include diff --git a/components/i2c/include/i2c_slave.hpp b/components/i2c/include/i2c_slave.hpp index 7ca7766fc..f1fe4468d 100644 --- a/components/i2c/include/i2c_slave.hpp +++ b/components/i2c/include/i2c_slave.hpp @@ -174,6 +174,7 @@ class I2cSlaveDevice : public BaseComponent { MessageBufferHandle_t callback_buffer_ = nullptr; std::unique_ptr event_task_; std::vector legacy_receive_buffer_; + std::vector callback_dispatch_buffer_; std::atomic legacy_receive_length_{0}; std::atomic legacy_receive_armed_{false}; std::atomic read_buffer_overflowed_{false}; diff --git a/components/i2c/src/i2c_slave.cpp b/components/i2c/src/i2c_slave.cpp index 038f91af9..2bffcbcbb 100644 --- a/components/i2c/src/i2c_slave.cpp +++ b/components/i2c/src/i2c_slave.cpp @@ -171,6 +171,7 @@ bool I2cSlaveDevice::init(std::error_code &ec) { callback_buffer_overflowed_ = false; event_queue_overflowed_ = false; legacy_receive_buffer_.assign(config_.receive_buffer_depth, 0); + callback_dispatch_buffer_.assign(config_.receive_buffer_depth, 0); legacy_receive_length_ = 0; legacy_receive_armed_ = false; initialized_ = true; @@ -220,6 +221,7 @@ bool I2cSlaveDevice::deinit(std::error_code &ec) { } callbacks_ = {}; legacy_receive_buffer_.clear(); + callback_dispatch_buffer_.clear(); legacy_receive_length_ = 0; legacy_receive_armed_ = false; ec.clear(); @@ -298,8 +300,8 @@ bool I2cSlaveDevice::read(uint8_t *data, size_t len, size_t &received_len, std:: size_t next_length = xMessageBufferNextLengthBytes(read_buffer_); if (next_length == 0 && !legacy_receive_armed_) { if (len > config_.receive_buffer_depth) { - logger_.error("I2C slave read request ({}) exceeds configured receive_buffer_depth ({})", len, - config_.receive_buffer_depth); + logger_.error("I2C slave read request ({}) exceeds configured receive_buffer_depth ({})", + len, config_.receive_buffer_depth); ec = std::make_error_code(std::errc::message_size); return false; } @@ -490,19 +492,24 @@ bool I2cSlaveDevice::event_task_callback(std::mutex &m, std::condition_variable break; } case EventType::RECEIVE: { - std::vector data(config_.receive_buffer_depth); - size_t length = - callback_buffer ? xMessageBufferReceive(callback_buffer, data.data(), data.size(), 0) : 0; - if (length == 0) { - break; - } + uint8_t *data = nullptr; + size_t capacity = 0; ReceiveCallback callback; { std::lock_guard lock(mutex_); callback = callbacks_.on_receive; + if (!callback_buffer_ || callback_dispatch_buffer_.empty()) { + break; + } + data = callback_dispatch_buffer_.data(); + capacity = callback_dispatch_buffer_.size(); + } + size_t length = callback_buffer ? xMessageBufferReceive(callback_buffer, data, capacity, 0) : 0; + if (length == 0) { + break; } if (callback) { - callback(data.data(), length); + callback(data, length); } break; } From 5237e7b74e276eb3617956a0a92e8f0993e5925c Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 29 May 2026 12:53:30 -0500 Subject: [PATCH 16/16] add partitions to t-deck to work in esp idf v5.5 with latest changes --- components/t-deck/example/partitions.csv | 5 +++++ components/t-deck/example/sdkconfig.defaults | 7 +++++++ 2 files changed, 12 insertions(+) create mode 100644 components/t-deck/example/partitions.csv diff --git a/components/t-deck/example/partitions.csv b/components/t-deck/example/partitions.csv new file mode 100644 index 000000000..19d66ab8f --- /dev/null +++ b/components/t-deck/example/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size +nvs, data, nvs, 0xa000, 0x6000 +phy_init, data, phy, , 0x1000 +factory, app, factory, , 6M +littlefs, data, littlefs, , 6M diff --git a/components/t-deck/example/sdkconfig.defaults b/components/t-deck/example/sdkconfig.defaults index 8e8c066ec..100486ab5 100644 --- a/components/t-deck/example/sdkconfig.defaults +++ b/components/t-deck/example/sdkconfig.defaults @@ -10,6 +10,13 @@ CONFIG_ESPTOOLPY_FLASHSIZE="16MB" # over twice as fast as DIO CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +# +# Partition Table +# +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_OFFSET=0x9000 +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" + # ESP32-specific # CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y