I am currently working on a project where I want to connect 3 environment sensors to the same board.
The peripheral is the AHT21 + ENS160 which consists of two sensors, the AHT21 temperature and relative humidity sensor and the ENS160 "air quality" sensor. Both sensors works via I2C.
I have been able to successfully initialize and read from an individual sensor, now I want to connect more of them on the same board.
The problem of course is that they share the same I2C address and you cannot change them.
I am aware of the I2C multiplexer or I2C address translator, but I want to avoid adding new hardware and keep everything in software.
I have found a post where they accomplished a "sort of multiplexing" using the old i2c.h driver.
After initializing the driver and adding the device they used "i2c_set_pin" to change the SDA and SCL pins before reading from the device.
The problem is that it uses the old driver, and the library I have to read the ENS160 uses the new driver, thus I get an error saying that I can't use both drivers at the same time.
The question is, is there a way of accomplishing a similar thing using the new driver defined in "i2c_master.h"?
I also tried a very rudimentary way of reading the devices by creating and removing the master bus handle in between reads, however I get a weird behavior under the monitor where it prints weird characters and issues a CPU software reset. I thought that it might be due the fast creating/removing master buses, but adding a delay of up to 500ms in between didn't solved.
Here's the result I get from the monitor:

This is my code (apologies if it's the wrong format, first post here) (in the test I only read from the AHT21 sensor because the ENS160 takes an hour to start up):
Code: Select all
// Test I2C mux.
#define I2C_MASTER_PORT (static_cast<i2c_port_t>(0))
#define I2C_SDA1 (static_cast<gpio_num_t>(21))
#define I2C_SCL1 (static_cast<gpio_num_t>(22))
#define I2C_SDA2 (static_cast<gpio_num_t>(16))
#define I2C_SCL2 (static_cast<gpio_num_t>(17))
#define I2C_SDA3 (static_cast<gpio_num_t>(3))
#define I2C_SCL3 (static_cast<gpio_num_t>(1))
struct test_aht2_result_t {
sens_aht21_result_t result1;
sens_aht21_result_t result2;
sens_aht21_result_t result3;
};
static bool first_read = true;
/**
* @brief Initializes I2C by creating a new master bus.
*
* @param[in] sda The SDA GPIO number.
* @param[in] slc The SCL GPIO number.
* @param[out] bus_handle The output I2C Master Bus Handle.
*
* @returns Nothing.
*/
void test_init_i2c_driver(gpio_num_t sda, gpio_num_t scl, i2c_master_bus_handle_t* bus_handle) {
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_MASTER_PORT,
.sda_io_num = sda,
.scl_io_num = scl,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
};
bus_config.flags.enable_internal_pullup = true;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, bus_handle));
}
/**
* @brief Switches I2C master buses by deleting the current bus and creating a new one.
*
* @param[inout] bus_handle On input the current bus handle. On output the new bus handle.
* @param[in] new_sda The new SDA GPIO number.
* @param[in] new_scl The new SCL GPIO number.
*
* @returns Nothing.
*/
void test_i2c_bus_switcharoo(i2c_master_bus_handle_t* bus_handle, gpio_num_t new_sda, gpio_num_t new_scl) {
ESP_ERROR_CHECK(i2c_del_master_bus(*bus_handle));
// Attempt to solve the CPU SW reset behavior with a delay between I2C buses creation/deletion.
vTaskDelay(pdMS_TO_TICKS(500));
i2c_master_bus_config_t bus_config = {
.i2c_port = I2C_MASTER_PORT,
.sda_io_num = new_sda,
.scl_io_num = new_scl,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
};
bus_config.flags.enable_internal_pullup = true;
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, bus_handle));
}
/**
* @brief Initializes the AHT21 sensor.
*
* @param[in] bus_handle The I2C Master Bus Handle.
* @param[out] device_handle The I2C Device Handle.
*
* @returns Nothing.
*/
void test_init_aht21(i2c_master_bus_handle_t bus_handle, i2c_master_dev_handle_t* device_handle) {
i2c_device_config_t aht21_device_config = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = SENS_AHT21_I2C_ADDRESS,
.scl_speed_hz = SENS_I2C_DEFAULT_SCL_CLK_SPEED,
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &aht21_device_config, device_handle));
uint8_t aht21_init_attempt_count = 1;
uint8_t aht21_init_cmd[] = { SENS_AHT21_CMD_INIT, 0x08, 0x00 };
ESP_ERROR_CHECK(i2c_master_transmit(*device_handle, aht21_init_cmd, sizeof(aht21_init_cmd), SENS_AHT21_TRANS_TIMEOUT_MS));
vTaskDelay(pdMS_TO_TICKS(40));
}
/**
* @brief Switches sensors by creating a new I2C master bus and initializing the new sensor.
*
* @param[inout] bus_handle On input the old I2C bus handle. On output the new bus handle.
* @param[inout] device_handle On input the old I2C device handle. On output the new device handle.
* @param[in] new_sda The new SDA GPIO number.
* @param[in] new_scl The new SCL GPIO number.
*
* @returns Nothing.
*/
void test_sensor_switch(i2c_master_bus_handle_t* bus_handle, i2c_master_dev_handle_t* device_handle, gpio_num_t new_sda, gpio_num_t new_scl) {
ESP_ERROR_CHECK(i2c_master_bus_rm_device(*device_handle));
test_i2c_bus_switcharoo(bus_handle, new_sda, new_scl);
test_init_aht21(*bus_handle, device_handle);
}
/**
* @brief Reads all sensors by switching between active I2C buses.
*
* @param[out] result The reading results.
*
* @returns Nothing.
*/
void test_read_all(test_aht2_result_t& result) {
i2c_master_bus_handle_t bus_handle;
i2c_master_dev_handle_t device_handle;
if (first_read) {
vTaskDelay(pdMS_TO_TICKS(20));
test_init_i2c_driver(I2C_SDA1, I2C_SCL1, &bus_handle);
test_init_aht21(bus_handle, &device_handle);
// The function 'sens_aht21_read' is defined in another header file, but it reads from the sensor using I2C.
ESP_ERROR_CHECK(sens_aht21_read(device_handle, result.result1));
first_read = false;
}
else {
test_sensor_switch(&bus_handle, &device_handle, I2C_SDA1, I2C_SCL1);
ESP_ERROR_CHECK(sens_aht21_read(device_handle, result.result1));
}
test_sensor_switch(&bus_handle, &device_handle, I2C_SDA2, I2C_SCL2);
ESP_ERROR_CHECK(sens_aht21_read(device_handle, result.result2));
test_sensor_switch(&bus_handle, &device_handle, I2C_SDA3, I2C_SCL3);
ESP_ERROR_CHECK(sens_aht21_read(device_handle, result.result2));
}
/**
* @brief Main test function to be called on 'app_main'. It reads from all 3 sensors and writes the result to the console.
*/
void test_main_task() {
for (;;) {
test_aht2_result_t result{ };
test_read_all(result);
ESP_LOGI(tag_main, "SENSOR 1: Temperature: %.2f°C; RH: %.2f%%", result.result1.temperature, result.result1.relative_humidity);
ESP_LOGI(tag_main, "SENSOR 2: Temperature: %.2f°C; RH: %.2f%%", result.result2.temperature, result.result2.relative_humidity);
ESP_LOGI(tag_main, "SENSOR 3: Temperature: %.2f°C; RH: %.2f%%", result.result3.temperature, result.result3.relative_humidity);
vTaskDelay(pdMS_TO_TICKS(500));
}
}