diff options
-rw-r--r-- | ports/esp32/machine_i2c.c | 245 | ||||
-rw-r--r-- | ports/esp32/mpconfigport.h | 3 |
2 files changed, 216 insertions, 32 deletions
diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index a9e5f0d3b..e8d913def 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -4,6 +4,7 @@ * The MIT License (MIT) * * Copyright (c) 2019 Damien P. George + * Copyright (c) 2025 Vincent1-python * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,11 +31,196 @@ #include "extmod/modmachine.h" #include "machine_i2c.h" +#if MICROPY_HW_ESP_NEW_I2C_DRIVER +#include "driver/i2c_master.h" +#else #include "driver/i2c.h" #include "hal/i2c_ll.h" +#endif #if MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C +#define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms + +// CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is set if the related sdkconfig +// option is set. + +#if MICROPY_HW_ESP_NEW_I2C_DRIVER + +typedef struct _machine_hw_i2c_obj_t { + mp_obj_base_t base; + i2c_master_bus_handle_t bus_handle; + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_master_dev_handle_t dev_handle; + #endif + uint8_t port : 8; + gpio_num_t scl : 8; + gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; +} machine_hw_i2c_obj_t; + +static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; + +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + if (!first_init && self->dev_handle) { + i2c_master_bus_rm_device(self->dev_handle); + self->dev_handle = NULL; + } + #endif + + if (!first_init && self->bus_handle) { + i2c_del_master_bus(self->bus_handle); + self->bus_handle = NULL; + } + + i2c_master_bus_config_t bus_cfg = { + .i2c_port = self->port, + .scl_io_num = self->scl, + .sda_io_num = self->sda, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_cfg, &self->bus_handle)); + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 0) + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = 0, // Will be replaced + .scl_speed_hz = self->freq, + }; + ESP_ERROR_CHECK(i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &self->dev_handle)); + #endif +} + +static uint8_t *create_transfer_buffer(size_t n, mp_machine_i2c_buf_t *bufs, size_t *len_ptr) { + size_t len = 0; + uint8_t *buf; + if (n == 1) { + // Use given single buffer + len = bufs[0].len; + buf = bufs[0].buf; + } else { + // Allocate a buffer that can hold the data from all buffers + len = 0; + for (size_t i = 0; i < n; ++i) { + len += bufs[i].len; + } + buf = m_new(uint8_t, len); + } + *len_ptr = len; + return buf; +} + +int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_machine_i2c_buf_t *bufs, unsigned int flags) { + machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); + + // Probe the address to see if any device responds. + // This test uses a fixed scl freq of 100_000. + esp_err_t err = i2c_master_probe(self->bus_handle, addr, self->timeout_us / 1000); + if (err != ESP_OK) { + return -MP_ENODEV; // No device at address, return immediately + } + + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Using ".device_address = I2C_DEVICE_ADDRESS_NOT_USED," below + // allows to write the address separately using the + // i2c_master_execute_defined_operations() API. + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = addr, + .scl_speed_hz = self->freq, + }; + i2c_master_dev_handle_t dev_handle; + err = i2c_master_bus_add_device(self->bus_handle, &dev_cfg, &dev_handle); + #else + #define dev_handle self->dev_handle + err = i2c_master_device_change_address(dev_handle, addr, self->timeout_us / 1000); + #endif + if (err != ESP_OK) { + return -MP_ENODEV; + } + + size_t len = 0; + uint8_t *buf; + + // Assume that with MP_MACHINE_I2C_FLAG_WRITE1 set the first + // buffer has to be written and all others have to be read. + // extmod/read_mem() uses only a single buffer for reading, but + // the other implementation(s) support multiple buffers. + if (flags & MP_MACHINE_I2C_FLAG_WRITE1) { + // create a large buffer if needed + buf = create_transfer_buffer(n - 1, bufs + 1, &len); + // Do a write then read + err = i2c_master_transmit_receive(dev_handle, bufs[0].buf, bufs[0].len, buf, len, self->timeout_us / 1000); + // Copy the data back if needed starting with the second buffer. + if (n > 2) { + len = 0; + for (size_t i = 1; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; + } + m_del(uint8_t, buf, len); + } + len += bufs[0].len; + } else if (bufs->len > 0) { + buf = create_transfer_buffer(n, bufs, &len); + // Transfer data and copy it from/to the buffers as needed. + if (flags & MP_MACHINE_I2C_FLAG_READ) { + err = i2c_master_receive(dev_handle, buf, len, self->timeout_us / 1000); + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(bufs[i].buf, buf + len, bufs[i].len); + len += bufs[i].len; + } + } + } else { + if (n > 1) { + len = 0; + for (size_t i = 0; i < n; ++i) { + memcpy(buf + len, bufs[i].buf, bufs[i].len); + len += bufs[i].len; + } + } + err = i2c_master_transmit(dev_handle, buf, len, self->timeout_us / 1000); + // Use i2c_master_execute_defined_operations() instead of + // i2c_master_transmit(), allowing for len == 0. + // That will be needed for scan() when dropping i2c_master_probe() is possible, + // after https://github.com/espressif/esp-idf/issues/17543 backported to supported versions + // i2c_operation_job_t i2c_ops[] = { + // { .command = I2C_MASTER_CMD_START }, + // { .command = I2C_MASTER_CMD_WRITE, .write = { .ack_check = true, .data = buf, .total_bytes = len } }, + // { .command = I2C_MASTER_CMD_STOP }, // Stop is still mandatory + // }; + // err = i2c_master_execute_defined_operations(dev_handle, i2c_ops, 3, self->timeout_us / 1000); + } + if (n > 1) { + m_del(uint8_t, buf, len); + } + } + #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 5, 0) + // Remove the temporary handle. + i2c_master_bus_rm_device(dev_handle); + #endif + + // Map errors + if (err == ESP_FAIL) { + return -MP_ENODEV; + } + if (err == ESP_ERR_TIMEOUT) { + return -MP_ETIMEDOUT; + } + if (err != ESP_OK) { + return -abs(err); + } + return len; +} + +#else + #if SOC_I2C_SUPPORT_XTAL #if CONFIG_XTAL_FREQ > 0 #define I2C_SCLK_FREQ (CONFIG_XTAL_FREQ * 1000000) @@ -47,18 +233,18 @@ #error "unsupported I2C for ESP32 SoC variant" #endif -#define I2C_DEFAULT_TIMEOUT_US (50000) // 50ms - typedef struct _machine_hw_i2c_obj_t { mp_obj_base_t base; i2c_port_t port : 8; gpio_num_t scl : 8; gpio_num_t sda : 8; + uint32_t freq; + uint32_t timeout_us; } machine_hw_i2c_obj_t; static machine_hw_i2c_obj_t machine_hw_i2c_obj[I2C_NUM_MAX]; -static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint32_t timeout_us, bool first_init) { +static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, bool first_init) { if (!first_init) { i2c_driver_delete(self->port); } @@ -68,10 +254,10 @@ static void machine_hw_i2c_init(machine_hw_i2c_obj_t *self, uint32_t freq, uint3 .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_io_num = self->scl, .scl_pullup_en = GPIO_PULLUP_ENABLE, - .master.clk_speed = freq, + .master.clk_speed = self->freq, }; i2c_param_config(self->port, &conf); - int timeout = I2C_SCLK_FREQ / 1000000 * timeout_us; + int timeout = I2C_SCLK_FREQ / 1000000 * self->timeout_us; i2c_set_timeout(self->port, (timeout > I2C_LL_MAX_TIMEOUT) ? I2C_LL_MAX_TIMEOUT : timeout); i2c_driver_install(self->port, I2C_MODE_MASTER, 0, 0, 0); } @@ -124,15 +310,16 @@ int machine_hw_i2c_transfer(mp_obj_base_t *self_in, uint16_t addr, size_t n, mp_ return data_len; } + +#endif // MICROPY_HW_ESP_NEW_I2C_DRIVER + /******************************************************************************/ // MicroPython bindings for machine API static void machine_hw_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_hw_i2c_obj_t *self = MP_OBJ_TO_PTR(self_in); - int h, l; - i2c_get_period(self->port, &h, &l); - mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u)", - self->port, self->scl, self->sda, I2C_SCLK_FREQ / (h + l)); + mp_printf(print, "I2C(%u, scl=%u, sda=%u, freq=%u, timeout=%u)", + self->port, self->scl, self->sda, self->freq, self->timeout_us); } mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { @@ -147,49 +334,43 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ { MP_QSTR_id, MP_ARG_INT, {.u_int = I2C_NUM_0} }, { MP_QSTR_scl, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, { MP_QSTR_sda, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, - { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 400000} }, - { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = I2C_DEFAULT_TIMEOUT_US} }, + { MP_QSTR_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, + { MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = -1} }, }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - // Get I2C bus mp_int_t i2c_id = args[ARG_id].u_int; - - // Check if the I2C bus is valid if (!(I2C_NUM_0 <= i2c_id && i2c_id < I2C_NUM_MAX)) { mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("I2C(%d) doesn't exist"), i2c_id); } - // Get static peripheral object - machine_hw_i2c_obj_t *self = (machine_hw_i2c_obj_t *)&machine_hw_i2c_obj[i2c_id]; + machine_hw_i2c_obj_t *self = &machine_hw_i2c_obj[i2c_id]; - bool first_init = false; - if (self->base.type == NULL) { - // Created for the first time, set default pins + bool first_init = (self->base.type == NULL); + if (first_init) { self->base.type = &machine_i2c_type; self->port = i2c_id; - if (self->port == I2C_NUM_0) { - self->scl = MICROPY_HW_I2C0_SCL; - self->sda = MICROPY_HW_I2C0_SDA; - } else { - self->scl = MICROPY_HW_I2C1_SCL; - self->sda = MICROPY_HW_I2C1_SDA; - } - first_init = true; + self->scl = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SCL : MICROPY_HW_I2C1_SCL; + self->sda = (i2c_id == I2C_NUM_0) ? MICROPY_HW_I2C0_SDA : MICROPY_HW_I2C1_SDA; + self->freq = 400000; + self->timeout_us = I2C_DEFAULT_TIMEOUT_US; } - // Set SCL/SDA pins if given if (args[ARG_scl].u_obj != MP_OBJ_NULL) { self->scl = machine_pin_get_id(args[ARG_scl].u_obj); } if (args[ARG_sda].u_obj != MP_OBJ_NULL) { self->sda = machine_pin_get_id(args[ARG_sda].u_obj); } + if (args[ARG_freq].u_int != -1) { + self->freq = args[ARG_freq].u_int; + } + if (args[ARG_timeout].u_int != -1) { + self->timeout_us = args[ARG_timeout].u_int; + } - // Initialise the I2C peripheral - machine_hw_i2c_init(self, args[ARG_freq].u_int, args[ARG_timeout].u_int, first_init); - + machine_hw_i2c_init(self, first_init); return MP_OBJ_FROM_PTR(self); } @@ -208,4 +389,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &mp_machine_i2c_locals_dict ); -#endif +#endif // MICROPY_PY_MACHINE_I2C || MICROPY_PY_MACHINE_SOFTI2C diff --git a/ports/esp32/mpconfigport.h b/ports/esp32/mpconfigport.h index 0e62cd748..4614abc3c 100644 --- a/ports/esp32/mpconfigport.h +++ b/ports/esp32/mpconfigport.h @@ -186,6 +186,9 @@ #endif #define MICROPY_HW_SOFTSPI_MIN_DELAY (0) #define MICROPY_HW_SOFTSPI_MAX_BAUDRATE (esp_rom_get_cpu_ticks_per_us() * 1000000 / 200) // roughly +#ifndef MICROPY_HW_ESP_NEW_I2C_DRIVER +#define MICROPY_HW_ESP_NEW_I2C_DRIVER (0) +#endif #define MICROPY_PY_SSL (1) #define MICROPY_SSL_MBEDTLS (1) #define MICROPY_PY_WEBSOCKET (1) |