-
Notifications
You must be signed in to change notification settings - Fork 179
Add support for CH422G chip (BSP-760) #697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| set(req) | ||
| if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.2.6") | ||
| list(APPEND req "esp_driver_i2c") | ||
| else() | ||
| list(APPEND req "driver") | ||
| endif() | ||
|
|
||
| idf_component_register(SRCS "esp_io_expander_ch422g.c" INCLUDE_DIRS "include" REQUIRES ${req}) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| # ESP IO Expander Chip CH422G | ||
|
|
||
| Implementation of the CH422G io expander chip with esp_io_expander component. | ||
|
|
||
| | Chip | Communication interface | Component name | Link to datasheet | | ||
| | :--------------: | :---------------------: | :------------: | :---------------: | | ||
| | CH422G | I2C | esp_io_expander_ch422g | [datasheet](https://files.waveshare.com/wiki/common/CH422DS1_EN.pdf) | | ||
|
|
||
| Currently only support bidirectional I/O pins. General purposes pins OC3-OC0 are not supported. | ||
| Chip limitations dictates that all bidirectional I/O pins are either input or output, they cannot be controlled individually. | ||
|
|
||
| ## Add to project | ||
|
|
||
| Packages from this repository are uploaded to [Espressif's component service](https://components.espressif.com/). | ||
| You can add them to your project via `idf.py add-dependency`, e.g. | ||
| ``` | ||
| idf.py add-dependency esp_io_expander_ch422g==1.0.0 | ||
| ``` | ||
|
|
||
| Alternatively, you can create `idf_component.yml`. More is in [Espressif's documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). | ||
|
|
||
| ## Example use | ||
|
|
||
| Creation of the i2c bus. | ||
|
|
||
| ```c | ||
| i2c_master_bus_handle_t i2c_handle = NULL; | ||
| const i2c_master_bus_config_t bus_config = { | ||
| .i2c_port = I2C_NUM_0, | ||
| .sda_io_num = 47, | ||
| .scl_io_num = 48, | ||
| .clk_source = I2C_CLK_SRC_DEFAULT, | ||
| }; | ||
| i2c_new_master_bus(&bus_config, &i2c_handle); | ||
| ``` | ||
|
|
||
| Creation of the component. | ||
|
|
||
| ```c | ||
| esp_io_expander_handle_t io_expander = NULL; | ||
| esp_io_expander_new_i2c_ch422g(i2c_handle, &io_expander); | ||
| ``` | ||
|
|
||
| Print all pins's status to the log: | ||
|
|
||
| ```c | ||
| esp_io_expander_print_state(io_expander); | ||
| ``` | ||
|
|
||
| Set pin 0 and pin 1 with output direction and low level: | ||
|
|
||
| ```c | ||
| esp_io_expander_set_dir(io_expander, IO_EXPANDER_ALL_PINS, IO_EXPANDER_OUTPUT); | ||
| esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); | ||
| ``` | ||
|
|
||
| Set pin 2 and pin 3 with input direction: | ||
|
|
||
| ```c | ||
| uint32_t pin_levels = 0; | ||
| esp_io_expander_set_dir(io_expander, IO_EXPANDER_ALL_PINS, IO_EXPANDER_INPUT); | ||
| esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, &pin_levels); | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD | ||
| * SPDX-FileCopyrightText: 2025 Frédéric Nadeau | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #include <inttypes.h> | ||
| #include <string.h> | ||
| #include <stdlib.h> | ||
| #include "esp_bit_defs.h" | ||
| #include "esp_check.h" | ||
| #include "esp_log.h" | ||
| #include "esp_io_expander.h" | ||
| #include "esp_io_expander_ch422g.h" | ||
|
|
||
| /* I2C communication related */ | ||
| #define I2C_TIMEOUT_MS (1000) | ||
| #define I2C_CLK_SPEED (400000) | ||
|
|
||
| #define IO_COUNT (8) | ||
|
|
||
| /* Register address */ | ||
| #define GENERAL_PURPOSE_OUTPUT_ADDR (0x24) | ||
| #define SET_IO_ADDR (0x38) | ||
| #define READ_IO_ADDR (0x26) | ||
|
|
||
| /* Default register value on power-up */ | ||
| #define DIR_REG_DEFAULT_VAL (0xFF) | ||
| #define OUT_REG_DEFAULT_VAL (0x00) | ||
|
|
||
| /** | ||
| * @brief Device Structure Type | ||
| * | ||
| */ | ||
| typedef struct { | ||
| esp_io_expander_t base; | ||
| i2c_master_dev_handle_t i2c_gpo_handle; | ||
| i2c_master_dev_handle_t i2c_set_io_handle; | ||
| i2c_master_dev_handle_t i2c_read_io_handle; | ||
| struct { | ||
| uint8_t direction; | ||
| uint8_t output; | ||
| } regs; | ||
| } esp_io_expander_ch422g_t; | ||
|
|
||
| static const char *TAG = "ch422g"; | ||
|
|
||
| static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value); | ||
| static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value); | ||
| static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value); | ||
| static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value); | ||
| static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value); | ||
| static esp_err_t reset(esp_io_expander_t *handle); | ||
| static esp_err_t del(esp_io_expander_t *handle); | ||
|
|
||
| esp_err_t esp_io_expander_new_i2c_ch422g(i2c_master_bus_handle_t i2c_bus, esp_io_expander_handle_t *handle_ret) | ||
| { | ||
| ESP_RETURN_ON_FALSE(handle_ret != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle_ret"); | ||
|
|
||
| // Allocate memory for driver object | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)calloc(1, sizeof(esp_io_expander_ch422g_t)); | ||
| ESP_RETURN_ON_FALSE(ch422g != NULL, ESP_ERR_NO_MEM, TAG, "Malloc failed"); | ||
|
|
||
| // Add new I2C device | ||
| esp_err_t ret = ESP_OK; | ||
| const i2c_device_config_t i2c_dev_cfg1 = { | ||
| .device_address = GENERAL_PURPOSE_OUTPUT_ADDR, | ||
| .scl_speed_hz = I2C_CLK_SPEED, | ||
| }; | ||
| ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg1, &ch422g->i2c_gpo_handle), err, TAG, "Add new I2C device (GENERAL_PURPOSE_OUTPUT_ADDR) failed"); | ||
|
|
||
| const i2c_device_config_t i2c_dev_cfg2 = { | ||
| .device_address = SET_IO_ADDR, | ||
| .scl_speed_hz = I2C_CLK_SPEED, | ||
| }; | ||
| ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg2, &ch422g->i2c_set_io_handle), err, TAG, "Add new I2C device (SET_IO_ADDR) failed"); | ||
|
|
||
| const i2c_device_config_t i2c_dev_cfg3 = { | ||
| .device_address = READ_IO_ADDR, | ||
| .scl_speed_hz = I2C_CLK_SPEED, | ||
| }; | ||
| ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg3, &ch422g->i2c_read_io_handle), err, TAG, "Add new I2C device (READ_IO_ADDR) failed"); | ||
|
|
||
| ch422g->base.config.io_count = IO_COUNT; | ||
| ch422g->base.config.flags.dir_out_bit_zero = 0; | ||
| ch422g->base.read_input_reg = read_input_reg; | ||
| ch422g->base.write_output_reg = write_output_reg; | ||
| ch422g->base.read_output_reg = read_output_reg; | ||
| ch422g->base.write_direction_reg = write_direction_reg; | ||
| ch422g->base.read_direction_reg = read_direction_reg; | ||
| ch422g->base.del = del; | ||
| ch422g->base.reset = reset; | ||
|
|
||
| /* Reset configuration and register status */ | ||
| ESP_GOTO_ON_ERROR(reset(&ch422g->base), err, TAG, "Reset failed"); | ||
|
|
||
| *handle_ret = &ch422g->base; | ||
| return ESP_OK; | ||
| err: | ||
| if (ch422g->i2c_gpo_handle) | ||
| i2c_master_bus_rm_device(ch422g->i2c_gpo_handle); | ||
| if (ch422g->i2c_set_io_handle) | ||
| i2c_master_bus_rm_device(ch422g->i2c_set_io_handle); | ||
| if (ch422g->i2c_read_io_handle) | ||
| i2c_master_bus_rm_device(ch422g->i2c_read_io_handle); | ||
|
|
||
| free(ch422g); | ||
| return ret; | ||
| } | ||
|
|
||
| static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
|
|
||
| uint8_t temp = 0; | ||
| ESP_RETURN_ON_ERROR(i2c_master_receive(ch422g->i2c_read_io_handle, &temp, sizeof(temp), I2C_TIMEOUT_MS), TAG, "Read input reg failed"); | ||
| *value = temp; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
| value &= 0xff; | ||
|
|
||
| uint8_t data[] = {value}; | ||
| ESP_RETURN_ON_ERROR(i2c_master_transmit(ch422g->i2c_set_io_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write output reg failed"); | ||
| ch422g->regs.output = value; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
|
|
||
| *value = ch422g->regs.output; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
| value &= 0xff; | ||
|
|
||
| /* CH422G limitation: all bidirectional I/Os must be inputs or outputs together. | ||
| * Accept only 0x00 (all inputs) or 0xFF (all outputs) and reject mixed masks. | ||
| */ | ||
| if (value != 0x00 && value != 0xFF) { | ||
| ESP_LOGE(TAG, "Invalid direction mask 0x%02" PRIx32 ", CH422G only supports all-input (0x00) or all-output (0xFF)", value); | ||
| return ESP_ERR_INVALID_ARG; | ||
| } | ||
| uint8_t data[] = {0x00}; | ||
| if (value == 0xFF) { | ||
| /* Any non-zero config means 'all outputs' for the device; use 0x01 as per datasheet/README. */ | ||
| data[0] = 0x01; | ||
| } | ||
| ESP_RETURN_ON_ERROR(i2c_master_transmit(ch422g->i2c_gpo_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write direction reg failed"); | ||
| /* Cache the logical direction as a full mask for higher layers: 0x00 = all in, 0xFF = all out. */ | ||
| ch422g->regs.direction = (value == 0x00) ? 0x00 : 0xFF; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
|
|
||
| *value = ch422g->regs.direction; | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t reset(esp_io_expander_t *handle) | ||
| { | ||
| ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed"); | ||
| ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed"); | ||
| return ESP_OK; | ||
| } | ||
|
|
||
| static esp_err_t del(esp_io_expander_t *handle) | ||
| { | ||
| esp_io_expander_ch422g_t *ch422g = (esp_io_expander_ch422g_t *)__containerof(handle, esp_io_expander_ch422g_t, base); | ||
|
|
||
| ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(ch422g->i2c_gpo_handle), TAG, "Remove I2C device (GENERAL_PURPOSE_OUTPUT_ADDR) failed"); | ||
| ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(ch422g->i2c_set_io_handle), TAG, "Remove I2C device (SET_IO_ADDR) failed"); | ||
| ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(ch422g->i2c_read_io_handle), TAG, "Remove I2C device (READ_IO_ADDR) failed"); | ||
| free(ch422g); | ||
| return ESP_OK; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resource leak in del function on partial failureThe |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| version: "1.0.1" | ||
| description: ESP IO Expander - CH422G | ||
| url: https://github.com/espressif/esp-bsp/tree/master/components/io_expander/esp_io_expander_ch422g | ||
| dependencies: | ||
| idf: ">=5.2" | ||
| esp_io_expander: | ||
| version: "^1.0.1" | ||
| public: true |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD | ||
| * SPDX-FileCopyrightText: 2025 Frédéric Nadeau | ||
| * | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| /** | ||
| * @file | ||
| * @brief ESP IO expander: CH422G | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <stdint.h> | ||
| #include "esp_err.h" | ||
| #include "driver/i2c_master.h" | ||
| #include "esp_io_expander.h" | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| #define IO_EXPANDER_ALL_PINS (IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3 | \ | ||
| IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_5 | IO_EXPANDER_PIN_NUM_6 | IO_EXPANDER_PIN_NUM_7) | ||
|
|
||
| /** | ||
| * @brief Create a CH422G IO expander object | ||
| * | ||
| * @param[in] i2c_bus I2C bus handle. Obtained from `i2c_new_master_bus()` | ||
| * @param[out] handle_ret Handle to created IO expander object | ||
| * | ||
| * @return | ||
| * - ESP_OK: Success, otherwise returns ESP_ERR_xxx | ||
| */ | ||
| esp_err_t esp_io_expander_new_i2c_ch422g(i2c_master_bus_handle_t i2c_bus, esp_io_expander_handle_t *handle_ret); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The version comparison string "5.2.6" appears unusual. The idf_component.yml specifies a minimum IDF version of ">=5.2", but this CMakeLists checks for version ">=5.2.6" which is a very specific patch version. This seems inconsistent. Typically, API changes occur in minor versions (e.g., 5.2 vs 5.3), not patch versions. Consider aligning this with the version requirement in idf_component.yml or documenting why version 5.2.6 specifically is required for the esp_driver_i2c component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All other io_expander have it written like this, I propose keeping it like this for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree