This commit is contained in:
2026-02-09 16:36:29 +01:00
parent af9e0c11e0
commit 0f50ddbae3
18 changed files with 610 additions and 293 deletions

View File

@@ -1,2 +1,2 @@
idf_component_register(SRCS
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@@ -1,49 +1,9 @@
menu "Example Configuration"
menu "I2C Emulator Configuration"
orsource "$IDF_PATH/examples/common_components/env_caps/$IDF_TARGET/Kconfig.env_caps"
choice BLINK_LED
prompt "Blink LED type"
default BLINK_LED_GPIO
config I2C_SLAVE_ADDR
int "I2C Slave Address (Decimal)"
default 97
help
Select the LED type. A normal level controlled LED or an addressable LED strip.
The default selection is based on the Espressif DevKit boards.
You can change the default selection according to your board.
config BLINK_LED_GPIO
bool "GPIO"
config BLINK_LED_STRIP
bool "LED strip"
endchoice
choice BLINK_LED_STRIP_BACKEND
depends on BLINK_LED_STRIP
prompt "LED strip backend peripheral"
default BLINK_LED_STRIP_BACKEND_RMT if SOC_RMT_SUPPORTED
default BLINK_LED_STRIP_BACKEND_SPI
help
Select the backend peripheral to drive the LED strip.
config BLINK_LED_STRIP_BACKEND_RMT
depends on SOC_RMT_SUPPORTED
bool "RMT"
config BLINK_LED_STRIP_BACKEND_SPI
bool "SPI"
endchoice
config BLINK_GPIO
int "Blink GPIO number"
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
default 8
help
GPIO number (IOxx) to blink on and off the LED.
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
config BLINK_PERIOD
int "Blink period in ms"
range 10 3600000
default 1000
help
Define the blinking period in milliseconds.
The 7-bit I2C Slave Address (0x61 = 97)
endmenu

View File

@@ -1,3 +1,2 @@
dependencies:
espressif/led_strip: "^2.4.1"
idf: "*"

View File

@@ -1,40 +1,24 @@
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_slave.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"
#define I2C_SLAVE_SDA_IO 21
#define I2C_SLAVE_SCL_IO 22
#define I2C_SLAVE_NUM I2C_NUM_0
#define I2C_SLAVE_ADDR 0x61
#define I2C_SLAVE_RX_BUF_LEN 128
#define I2C_SLAVE_TX_BUF_LEN 128
#define UART_NUM UART_NUM_0
#define UART_BAUD_RATE 921600
#define UART_BUF_SIZE 256
#define TAG "I2C2SERIAL"
static void i2c_slave_init(void) {
i2c_config_t conf_slave = {
.mode = I2C_MODE_SLAVE,
.sda_io_num = I2C_SLAVE_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_SLAVE_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.slave = {
.slave_addr = I2C_SLAVE_ADDR,
.maximum_speed = 400000,
},
};
ESP_ERROR_CHECK(i2c_param_config(I2C_SLAVE_NUM, &conf_slave));
ESP_ERROR_CHECK(i2c_driver_install(I2C_SLAVE_NUM, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0));
}
static i2c_slave_dev_handle_t i2c_slave = NULL;
static TaskHandle_t s_i2c_task_handle = NULL;
static void uart_init(void) {
uart_config_t uart_config = {
@@ -49,70 +33,117 @@ static void uart_init(void) {
ESP_ERROR_CHECK(uart_param_config(UART_NUM, &uart_config));
}
// Callback for I2C Receive Done
static bool i2c_rx_done_callback(i2c_slave_dev_handle_t channel, const i2c_slave_rx_done_event_data_t *edata, void *user_data) {
BaseType_t high_task_wakeup = pdFALSE;
if (s_i2c_task_handle) {
vTaskNotifyGiveFromISR(s_i2c_task_handle, &high_task_wakeup);
}
return high_task_wakeup == pdTRUE;
}
static void i2c_slave_init(void) {
i2c_slave_config_t conf = {
.sda_io_num = I2C_SLAVE_SDA_IO,
.scl_io_num = I2C_SLAVE_SCL_IO,
.clk_source = I2C_CLK_SRC_DEFAULT,
.send_buf_depth = 256,
.slave_addr = I2C_SLAVE_ADDR,
.addr_bit_len = I2C_ADDR_BIT_LEN_7,
.intr_priority = 0,
};
#if SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE
conf.flags.stretch_en = true;
#endif
ESP_ERROR_CHECK(i2c_new_slave_device(&conf, &i2c_slave));
// Enable Internal Pullups manually
gpio_set_pull_mode(I2C_SLAVE_SDA_IO, GPIO_PULLUP_ONLY);
gpio_set_pull_mode(I2C_SLAVE_SCL_IO, GPIO_PULLUP_ONLY);
i2c_slave_event_callbacks_t cbs = {
.on_recv_done = i2c_rx_done_callback,
};
ESP_ERROR_CHECK(i2c_slave_register_event_callbacks(i2c_slave, &cbs, NULL));
}
static void i2c2serial_task(void *arg) {
uint8_t i2c_rx[I2C_SLAVE_RX_BUF_LEN];
uint8_t uart_rx[UART_BUF_SIZE];
uint8_t i2c_tx[I2C_SLAVE_TX_BUF_LEN];
while (1) {
// Wait for I2C master write
int rx_len = i2c_slave_read_buffer(I2C_SLAVE_NUM, i2c_rx, sizeof(i2c_rx), pdMS_TO_TICKS(100));
if (rx_len > 0) {
// Print CMD to serial in hex
printf("CMD:");
for (int i = 0; i < rx_len; ++i) {
printf("%02X", i2c_rx[i]);
}
printf("\n");
size_t out_res = 0; // Added for ESP-IDF v5.4 compatibility
// Wait for DATA:[hex]\n from serial, with 200ms timeout
int uart_len = 0;
int total_len = 0;
TickType_t start_tick = xTaskGetTickCount();
bool got_data = false;
while ((xTaskGetTickCount() - start_tick) < pdMS_TO_TICKS(200)) {
uart_len = uart_read_bytes(UART_NUM, uart_rx + total_len, UART_BUF_SIZE - total_len, pdMS_TO_TICKS(10));
if (uart_len > 0) {
total_len += uart_len;
// Look for a full line
char *newline = memchr(uart_rx, '\n', total_len);
if (newline) {
int line_len = newline - (char *)uart_rx + 1;
uart_rx[line_len-1] = 0; // Null-terminate
if (strncmp((char *)uart_rx, "DATA:", 5) == 0) {
// Parse hex after DATA:
char *hexstr = (char *)uart_rx + 5;
int tx_len = 0;
while (*hexstr && tx_len < I2C_SLAVE_TX_BUF_LEN) {
unsigned int val;
if (sscanf(hexstr, "%2x", &val) == 1) {
i2c_tx[tx_len++] = val;
hexstr += 2;
} else {
break;
}
s_i2c_task_handle = xTaskGetCurrentTaskHandle();
while (1) {
// 1. Prepare to Receive (Non-blocking queue)
memset(i2c_rx, 0, sizeof(i2c_rx));
// This queues the receive request
ESP_ERROR_CHECK(i2c_slave_receive(i2c_slave, i2c_rx, sizeof(i2c_rx)));
// 2. Wait for Receive Complete (Callback triggers notification)
// This is where we wait for the Master to finish writing the command
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Calculate actual length received (SCD30 commands are usually 2-5 bytes)
// We look for the first 0 if the master didn't send a full 128 bytes
size_t actual_rx_len = 2; // SCD30 commands are at least 2 bytes
// 3. Fast Send to PC
uart_write_bytes(UART_NUM, "CMD:", 4);
for (size_t i = 0; i < actual_rx_len; ++i) {
char hex[3];
sprintf(hex, "%02X", i2c_rx[i]);
uart_write_bytes(UART_NUM, hex, 2);
}
uart_write_bytes(UART_NUM, "\n", 1);
// 4. Wait for PC response
int total_len = 0;
TickType_t start_tick = xTaskGetTickCount();
bool got_data = false;
// Wait up to 200ms for "DATA:[hex]\n" from PC
while ((xTaskGetTickCount() - start_tick) < pdMS_TO_TICKS(200)) {
int len = uart_read_bytes(UART_NUM, uart_rx + total_len, UART_BUF_SIZE - total_len, pdMS_TO_TICKS(5));
if (len > 0) {
total_len += len;
char *newline = memchr(uart_rx, '\n', total_len);
if (newline) {
if (strncmp((char *)uart_rx, "DATA:", 5) == 0) {
// 5. Fast Hex Parse
int tx_len = 0;
for (int i = 5; i < (newline - (char*)uart_rx) - 1; i += 2) {
unsigned int val;
if (sscanf((char*)&uart_rx[i], "%2x", &val) == 1) {
i2c_tx[tx_len++] = (uint8_t)val;
}
// Write to I2C slave TX buffer
i2c_slave_write_buffer(I2C_SLAVE_NUM, i2c_tx, tx_len, 0);
got_data = true;
}
// Remove processed line
memmove(uart_rx, newline + 1, total_len - line_len);
total_len -= line_len;
// 6. Load Buffer & Release Clock Stretch
// Added &out_res parameter for v5.4 compatibility
if (tx_len > 0) {
i2c_slave_transmit(i2c_slave, i2c_tx, tx_len, &out_res, pdMS_TO_TICKS(50));
}
got_data = true;
break;
}
total_len = 0; // Clear buffer if line didn't match DATA:
}
}
if (!got_data) {
// Timeout: clear I2C TX buffer
i2c_slave_write_buffer(I2C_SLAVE_NUM, NULL, 0, 0);
}
}
vTaskDelay(pdMS_TO_TICKS(1));
if (!got_data) {
// If PC fails, send a dummy byte so the Master's read doesn't hang forever
uint8_t dummy = 0xFF;
i2c_slave_transmit(i2c_slave, &dummy, 1, &out_res, pdMS_TO_TICKS(10));
}
}
}
void app_main(void) {
i2c_slave_init();
uart_init();
xTaskCreate(i2c2serial_task, "i2c2serial_task", 4096, NULL, 10, NULL);
i2c_slave_init();
xTaskCreatePinnedToCore(i2c2serial_task, "i2c2serial_task", 4096, NULL, 10, NULL, 0);
}