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/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 6a4f545cc..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; @@ -82,14 +83,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 +129,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/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 f8b4bca6e..9039680bb 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; + accelerometer_i2c_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_i2c_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_i2c_device_), + .read = espp::make_i2c_addressed_read(accelerometer_i2c_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/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 28eecd731..732f3874c 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; + codec_i2c_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_i2c_device_) { + logger_.error("Could not initialize codec I2C device: {}", ec.message()); + return false; + } + + 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 e38464606..07f880256 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; + imu_i2c_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_i2c_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_i2c_device_), + .read = espp::make_i2c_addressed_read(imu_i2c_device_), .imu_config = imu_config, .orientation_filter = orientation_filter, .auto_init = true, @@ -24,28 +36,37 @@ 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()); + 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 5817b4394..b6d6ff597 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; + touch_i2c_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_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 = 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_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; + touch_i2c_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_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{ - .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_i2c_device_), + .log_level = espp::Logger::Verbosity::WARN}); + } break; default: return false; } 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 727e5394f..e5d94eae5 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; + rtc_i2c_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_i2c_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_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/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..fb6e2f9d3 100644 --- a/components/i2c/README.md +++ b/components/i2c/README.md @@ -2,16 +2,45 @@ [![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. +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()`. -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. + +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 +are zero-filled and the reported receive length matches the requested read size. ## 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..a7847e297 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 explicit I2C 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..d34edf695 100644 --- a/components/i2c/include/i2c.hpp +++ b/components/i2c/include/i2c.hpp @@ -2,18 +2,20 @@ #include -// Only include this header if the legacy API is selected -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) || defined(_DOXYGEN_) - +#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 @@ -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,325 @@ class ESPP_I2C_LEGACY_API_DEPRECATED_ATTR I2c : public espp::BaseComponent { }; } // namespace espp +#elif defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) + +#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; + } + initialized_ = false; + 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); + 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) { + 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); + } + + /// 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 +847,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..b38311e19 100644 --- a/components/i2c/include/i2c_master.hpp +++ b/components/i2c/include/i2c_master.hpp @@ -161,7 +161,10 @@ 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_; } + /// @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->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_master_device_menu.hpp b/components/i2c/include/i2c_master_device_menu.hpp index 544c67b4c..4877647c1 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 { + "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"; 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..414894d98 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,55 @@ 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) + // Probe for a device (hex or decimal 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); + "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)"}, - [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); + "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); + 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 { + "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"; 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 +96,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 +117,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 +141,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 +155,11 @@ 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, // cppcheck-suppress constParameterReference + 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 +179,15 @@ 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, // cppcheck-suppress constParameterReference + 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..ec8a2eaf8 100644 --- a/components/i2c/include/i2c_menu.hpp +++ b/components/i2c/include/i2c_menu.hpp @@ -2,9 +2,7 @@ #include -// Only include this menu if the legacy API is selected -#if defined(CONFIG_ESPP_I2C_USE_LEGACY_API) || defined(_DOXYGEN_) - +#include #include #include #include @@ -48,52 +46,51 @@ 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."); - // 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); }, @@ -101,7 +98,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) { @@ -109,7 +108,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; @@ -146,13 +145,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 +209,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..f1fe4468d 100644 --- a/components/i2c/include/i2c_slave.hpp +++ b/components/i2c/include/i2c_slave.hpp @@ -5,16 +5,27 @@ // Only include this header if the new API is selected #if defined(CONFIG_ESPP_I2C_USE_NEW_API) || defined(_DOXYGEN_) +#include #include #include #include #include #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 #include "base_component.hpp" +#include "task.hpp" extern "C" { #include @@ -24,25 +35,42 @@ extern "C" { namespace espp { -/// @brief I2C Slave Device (C++ wrapper for ESP-IDF new I2C slave API) +#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 -/// 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: +/// +/// - 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 +/// +/// 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 There is no example for this yet as this code is untested. +/// @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 +80,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 +120,16 @@ 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 received_len Actual number of bytes received for the transaction /// @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, 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); /// @brief Register callbacks for slave events @@ -101,18 +140,46 @@ 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; + }; + +#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); + + 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::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}; + 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..21d62a41b 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 { @@ -57,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()); } @@ -69,7 +70,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..2bffcbcbb 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,460 @@ 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; +#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 = I2C_ADDR_BIT_LEN_7; - slave_cfg.send_buf_depth = 128; -#if CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + 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 - slave_cfg.flags.allow_pd = 0; + 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 = {}; +#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)); + 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; + 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; 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_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_ = {}; + legacy_receive_buffer_.clear(); + callback_dispatch_buffer_.clear(); + legacy_receive_length_ = 0; + legacy_receive_armed_ = false; 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; + } + +#if ESPP_I2C_SLAVE_V2_API 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; + 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) { -#if !CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 + 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_); - if (!initialized_ || !dev_handle_) { + received_len = 0; + 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(); + +#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 request ({}) exceeds configured receive_buffer_depth ({})", + len, config_.receive_buffer_depth); + 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) { + 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); +#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); + } else { + ec = std::make_error_code(std::errc::timed_out); + } + return false; + } + + received_len = read_len; 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; } +#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) { + auto *device = static_cast(user_data); + 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(event_queue, &event, &task_woken) != pdPASS) { + device->event_queue_overflowed_ = true; + } + 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 || !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; + } + + BaseType_t task_woken = pdFALSE; +#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(read_buffer, evt_data->buffer, length, &task_woken); + if (sent != length) { + device->read_buffer_overflowed_ = true; + } + + sent = xMessageBufferSendFromISR(callback_buffer, evt_data->buffer, length, &task_woken); + if (sent != length) { + device->callback_buffer_overflowed_ = true; + } + + Event event{EventType::RECEIVE}; + if (xQueueSendFromISR(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 &m, std::condition_variable &, bool ¬ified) { + { + std::lock_guard lock(m); + if (notified) { + notified = false; + return true; + } + } + + Event event{}; + static constexpr TickType_t stop_poll_ticks = pdMS_TO_TICKS(50); + 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)) { + 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; + } + } + + 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; + } + + 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: { + 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, 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/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 136c1af95..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; @@ -26,6 +27,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 +82,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 +98,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/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 e71cb9f86..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; @@ -28,6 +29,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 +84,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 +101,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/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 85470eb1e..60be9ecea 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; + es8388_i2c_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_i2c_device_) { + logger_.error("Could not initialize ES8388 I2C device: {}", ec.message()); + return false; + } + es7210_i2c_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_i2c_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_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 a1e9180e3..9b38c8a5c 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; + imu_i2c_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_i2c_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_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 76a575a36..feb62f1c4 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; + ioexp_0x43_i2c_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_i2c_device_) { + logger_.error("Could not initialize IO expander 0x43 I2C device: {}", ec.message()); + return false; + } + ioexp_0x44_i2c_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_i2c_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_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, @@ -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_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 568750644..e8d1dbf8a 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; + battery_monitor_i2c_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_monitor_i2c_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_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, }; 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..111d5b352 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; + rtc_i2c_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_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 = std::bind_front(&espp::I2c::write, &internal_i2c_), - .read = std::bind_front(&espp::I2c::read, &internal_i2c_), + .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 3c51469b2..71612df76 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; + touch_i2c_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_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 = std::bind_front(&I2c::write, &internal_i2c_), - .read = std::bind_front(&I2c::read, &internal_i2c_), + 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 f9f1ce875..648bcdcb8 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; + touch_i2c_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_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_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_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/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 5227de69d..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; @@ -26,6 +27,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 +82,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/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 33b3d480a..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 @@ -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; + touch_i2c_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_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_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), + .log_level = espp::Logger::Verbosity::WARN}); // store the callback touch_callback_ = 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 92799e4e9..2ae268e42 100644 --- a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp +++ b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp @@ -32,12 +32,22 @@ bool SmartPanleeSc01Plus::initialize_touch(const SmartPanleeSc01Plus::touch_call return false; } + std::error_code ec; + touch_i2c_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_i2c_device_) { + logger_.error("Could not initialize touch I2C device: {}", ec.message()); + 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), + .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; diff --git a/components/st25dv/example/main/st25dv_example.cpp b/components/st25dv/example/main/st25dv_example.cpp index 51e4cecc6..6749d0a7b 100644 --- a/components/st25dv/example/main/st25dv_example.cpp +++ b/components/st25dv/example/main/st25dv_example.cpp @@ -70,16 +70,58 @@ 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; + 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; + 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}); 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/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 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 b74cbac5f..66de0c60f 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; + keyboard_i2c_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_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_i2c_device_), + .read = espp::make_i2c_addressed_read(keyboard_i2c_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; + touch_i2c_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_i2c_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_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_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/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 5d5fa5c2c..017e35b1a 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; + imu_i2c_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_i2c_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_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 0117e8881..0caa7746e 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; + rtc_i2c_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_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 = std::bind_front(&espp::I2c::write, &internal_i2c_), - .read = std::bind_front(&espp::I2c::read, &internal_i2c_), + .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 daab0572a..52aabf7b2 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; + touch_i2c_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_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_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_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..7f5c19573 100644 --- a/doc/en/i2c.rst +++ b/doc/en/i2c.rst @@ -1,26 +1,33 @@ 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 available on ESP-IDF >= 5.4.0 and required on + ESP-IDF v6.x and newer -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 when +the new driver family is selected, 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 +43,37 @@ 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: + +- ``I2cMasterBus`` +- ``I2cMasterDevice`` +- ``I2cSlaveDevice`` -This driver was introduced and has been refined in ESP-IDF v5. As of v6.0, it -will be the only driver available. +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. -The `I2cMasterBus` and `I2cMasterDevice` classes provide simple interfaces to -manage and communicate with I2C peripherals on an I2C bus. +``I2cMasterMenu`` and ``I2cMasterDeviceMenu`` are also provided for interactive +testing of explicit master buses and devices. -It should be noted that while the new ESP-IDF APIs can support asynchronous -mode, these classes do not. +``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. -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. +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. -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. +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 ------------------------------------- @@ -72,3 +91,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.