This commit is contained in:
2026-02-11 19:46:46 +01:00
parent 86bc6505c8
commit 4ec6e645ba
24 changed files with 710 additions and 146 deletions

View File

@@ -1,12 +1,18 @@
#include "scd30.h"
#include <string.h>
#include <stdio.h>
#include "driver/i2c.h"
#include "esp_log.h"
#define SCD30_I2C_ADDRESS 0x61
#define SCD30_CMD_START_MEASUREMENT 0x0010
#define SCD30_CMD_READ_DATA 0x0300
#define SCD30_CMD_READY_STATUS 0x0202
#define I2C_MASTER_TIMEOUT_MS 1000
static const char *TAG = "SCD30";
// Private function to calculate CRC-8
static uint8_t calculate_crc8(const uint8_t *data, size_t len) {
const uint8_t polynomial = 0x31;
@@ -27,23 +33,32 @@ static uint8_t calculate_crc8(const uint8_t *data, size_t len) {
scd30_handle_t *scd30_init(int sda_gpio, int scl_gpio) {
static scd30_handle_t handle;
i2c_master_bus_config_t bus_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_0,
.scl_io_num = scl_gpio,
.sda_io_num = sda_gpio,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = true
};
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &handle.bus_handle));
int i2c_master_port = I2C_NUM_0;
i2c_device_config_t dev_cfg = {
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
.device_address = SCD30_I2C_ADDRESS,
.scl_speed_hz = 100000,
.scl_wait_us = 150000 // Clock stretching
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = sda_gpio,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = scl_gpio,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = 100000,
.clk_flags = 0,
};
ESP_ERROR_CHECK(i2c_master_bus_add_device(handle.bus_handle, &dev_cfg, &handle.dev_handle));
esp_err_t err = i2c_param_config(i2c_master_port, &conf);
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2C param config failed");
return NULL;
}
err = i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2C driver install failed");
return NULL;
}
handle.i2c_port = i2c_master_port; // Store port in handle
return &handle;
}
@@ -55,7 +70,18 @@ void scd30_start_measurement(scd30_handle_t *handle) {
0x00 // Placeholder for CRC
};
command[4] = calculate_crc8(&command[2], 2);
i2c_master_transmit(handle->dev_handle, command, sizeof(command), 1000);
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (SCD30_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write(cmd, command, sizeof(command), true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Start measurement failed: %s", esp_err_to_name(ret));
}
i2c_cmd_link_delete(cmd);
}
// Helper to convert Big Endian IEEE754 bytes to float
@@ -67,38 +93,126 @@ static float bytes_to_float(uint8_t mmsb, uint8_t mlsb, uint8_t lmsb, uint8_t ll
}
int scd30_read_data(scd30_handle_t *handle, scd30_data_t *data) {
uint8_t ready_command[2] = {
uint8_t ready_cmd_bytes[2] = {
(SCD30_CMD_READY_STATUS >> 8) & 0xFF,
SCD30_CMD_READY_STATUS & 0xFF
};
uint8_t ready_status[3];
uint8_t ready_status[3]; // [MSB, LSB, CRC]
// Poll until data is ready
do {
i2c_master_transmit_receive(handle->dev_handle, ready_command, sizeof(ready_command), ready_status, sizeof(ready_status), 1000);
} while (ready_status[1] != 0x01);
int retry_count = 0;
while (retry_count < 200) { // Increased retries, 200 * 20ms = 4 seconds
// 1. Send Ready Command
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (SCD30_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write(cmd, ready_cmd_bytes, sizeof(ready_cmd_bytes), true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
vTaskDelay(pdMS_TO_TICKS(20));
retry_count++;
continue;
}
uint8_t read_command[2] = {
// 2. Wait for Slave to consult Python
vTaskDelay(pdMS_TO_TICKS(20));
// 3. Read Ready Status
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (SCD30_I2C_ADDRESS << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, ready_status, sizeof(ready_status), I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(handle->i2c_port, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
// Check if ready (0x0001)
if (ready_status[1] == 1) {
break;
}
}
vTaskDelay(pdMS_TO_TICKS(20)); // Wait before retry
retry_count++;
}
if (retry_count >= 200) {
ESP_LOGW(TAG, "Timeout waiting for data ready");
return -1;
}
uint8_t read_cmd_bytes[2] = {
(SCD30_CMD_READ_DATA >> 8) & 0xFF,
SCD30_CMD_READ_DATA & 0xFF
};
uint8_t buffer[18];
// Read 18 bytes of data
// Format: CO2_MSB, CO2_LSB, CRC, CO2_LMSB, CO2_LLSB, CRC, ...
i2c_master_transmit_receive(handle->dev_handle, read_command, sizeof(read_command), buffer, sizeof(buffer), 1000);
// Validate CRC for each pair of data
for (int i = 0; i < 18; i += 3) {
if (calculate_crc8(buffer + i, 2) != buffer[i + 2]) {
return -1; // CRC error
// Read 18 bytes of data with retry
int data_retry = 0;
bool data_valid = false;
while (data_retry < 5) {
// 1. Send Read Command
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (SCD30_I2C_ADDRESS << 1) | I2C_MASTER_WRITE, true);
i2c_master_write(cmd, read_cmd_bytes, sizeof(read_cmd_bytes), true);
i2c_master_stop(cmd);
esp_err_t ret = i2c_master_cmd_begin(handle->i2c_port, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "Command Write Failed, retrying... (%d)", data_retry);
vTaskDelay(pdMS_TO_TICKS(50));
data_retry++;
continue;
}
// 2. Wait for Slave to consult Python
vTaskDelay(pdMS_TO_TICKS(50)); // Give ample time for UART bridge
// 3. Read Data
cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, (SCD30_I2C_ADDRESS << 1) | I2C_MASTER_READ, true);
i2c_master_read(cmd, buffer, sizeof(buffer), I2C_MASTER_LAST_NACK);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(handle->i2c_port, cmd, I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
if (ret == ESP_OK) {
// Validate CRC
bool crc_ok = true;
for (int i = 0; i < 18; i += 3) {
if (calculate_crc8(buffer + i, 2) != buffer[i + 2]) {
crc_ok = false;
break;
}
}
if (crc_ok) {
data_valid = true;
break;
}
}
ESP_LOGW(TAG, "CRC failed or Read error, retrying... (%d)", data_retry);
vTaskDelay(pdMS_TO_TICKS(50));
data_retry++;
}
if (!data_valid) {
ESP_LOGE(TAG, "Read data failed after retries: CRC Mismatch or I2C Error");
return -1;
}
// Parse IEEE754 floats (Big Endian)
// Structure: [Byte1, Byte2, CRC, Byte3, Byte4, CRC]
// 0 1 2 3 4 5
data->co2 = bytes_to_float(buffer[0], buffer[1], buffer[3], buffer[4]);
data->temp = bytes_to_float(buffer[6], buffer[7], buffer[9], buffer[10]);
data->humidity = bytes_to_float(buffer[12], buffer[13], buffer[15], buffer[16]);

View File

@@ -2,7 +2,7 @@
#define SCD30_H
#include <stdint.h>
#include "driver/i2c_master.h"
#include "driver/i2c.h"
// Data structure to hold SCD30 sensor data
typedef struct {
@@ -12,8 +12,7 @@ typedef struct {
} scd30_data_t;
typedef struct {
i2c_master_bus_handle_t bus_handle;
i2c_master_dev_handle_t dev_handle;
i2c_port_t i2c_port;
} scd30_handle_t;
/**

View File

@@ -2,23 +2,25 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_slave.h"
#include "driver/i2c.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "esp_log.h"
// I2C Slave Configuration
#define I2C_SLAVE_SDA_IO 21
#define I2C_SLAVE_SCL_IO 22
#define I2C_SLAVE_FREQ_HZ 100000
#define I2C_SLAVE_ADDR 0x61
#define I2C_SLAVE_RX_BUF_LEN 128
#define I2C_SLAVE_TX_BUF_LEN 128
#define I2C_SLAVE_RX_BUF_LEN 256
#define I2C_SLAVE_TX_BUF_LEN 256
// UART Configuration
#define UART_NUM UART_NUM_0
#define UART_BAUD_RATE 921600
#define UART_BUF_SIZE 256
#define TAG "I2C2SERIAL"
#define UART_BUF_SIZE 512
static i2c_slave_dev_handle_t i2c_slave = NULL;
static TaskHandle_t s_i2c_task_handle = NULL;
static const char *TAG = "I2C_SLAVE";
static void uart_init(void) {
uart_config_t uart_config = {
@@ -33,117 +35,94 @@ 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 = {
static esp_err_t i2c_slave_init(void) {
int i2c_slave_port = I2C_NUM_0;
i2c_config_t conf_slave = {
.sda_io_num = I2C_SLAVE_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.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,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.mode = I2C_MODE_SLAVE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = I2C_SLAVE_ADDR,
.clk_flags = 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));
esp_err_t err = i2c_param_config(i2c_slave_port, &conf_slave);
if (err != ESP_OK) return err;
return i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0);
}
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];
size_t out_res = 0; // Added for ESP-IDF v5.4 compatibility
void app_main(void) {
uint8_t i2c_rx_buffer[I2C_SLAVE_RX_BUF_LEN];
uint8_t uart_rx_buffer[UART_BUF_SIZE];
uint8_t i2c_tx_buffer[I2C_SLAVE_TX_BUF_LEN];
s_i2c_task_handle = xTaskGetCurrentTaskHandle();
uart_init();
ESP_ERROR_CHECK(i2c_slave_init());
ESP_LOGI(TAG, "I2C Slave initialized successfully (Legacy Driver)");
while (1) {
// 1. Prepare to Receive (Non-blocking queue)
memset(i2c_rx, 0, sizeof(i2c_rx));
// 1. Wait for data from Master (Blocking with timeout)
int len = i2c_slave_read_buffer(I2C_NUM_0, i2c_rx_buffer, I2C_SLAVE_RX_BUF_LEN, 50 / portTICK_PERIOD_MS);
// This queues the receive request
ESP_ERROR_CHECK(i2c_slave_receive(i2c_slave, i2c_rx, sizeof(i2c_rx)));
if (len > 0) {
ESP_LOGI(TAG, "Received %d bytes from Master", len);
// 2. Send CMD to Python
uart_write_bytes(UART_NUM, "CMD:", 4);
for (int i = 0; i < len; i++) {
char hex[3];
sprintf(hex, "%02X", i2c_rx_buffer[i]);
uart_write_bytes(UART_NUM, hex, 2);
}
uart_write_bytes(UART_NUM, "\n", 1);
// 3. Wait for DATA from Python (Blocking with timeout)
int total_rx = 0;
bool data_received = false;
// Simple timeout for Python response
for (int i=0; i<100; i++) { // ~1 second timeout
int ulen = uart_read_bytes(UART_NUM, uart_rx_buffer + total_rx, UART_BUF_SIZE - total_rx, 10 / portTICK_PERIOD_MS);
if (ulen > 0) {
total_rx += ulen;
// Check for newline
char *newline = memchr(uart_rx_buffer, '\n', total_rx);
if (newline) {
// Parse DATA:
if (strncmp((char*)uart_rx_buffer, "DATA:", 5) == 0) {
int tx_len = 0;
char *p = (char*)uart_rx_buffer + 5;
while (p < newline) {
unsigned int val;
if (sscanf(p, "%2x", &val) == 1) {
i2c_tx_buffer[tx_len++] = (uint8_t)val;
p += 2;
} else {
break;
}
}
if (tx_len > 0) {
i2c_slave_write_buffer(I2C_NUM_0, i2c_tx_buffer, tx_len, 100 / portTICK_PERIOD_MS);
ESP_LOGI(TAG, "Loaded %d bytes to TX buffer", tx_len);
} else {
// Even if empty, we might need to clear or write 0 if Master expects read?
// If Python sends empty DATA:, it means just ack/nothing to send.
}
data_received = true;
}
break;
}
}
}
// 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;
}
}
// 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 (!data_received) {
ESP_LOGW(TAG, "Timeout waiting for Python response");
}
}
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));
}
// Clear RX buffer not needed as read_buffer consumes it, but good practice if using ring buffer?
// i2c_reset_rx_fifo(I2C_NUM_0); // This might be too aggressive if data came in during processing?
// Legacy driver handles fifo logic.
}
}
void app_main(void) {
uart_init();
i2c_slave_init();
xTaskCreatePinnedToCore(i2c2serial_task, "i2c2serial_task", 4096, NULL, 10, NULL, 0);
}

View File

@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -0,0 +1,19 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

78
Master/.gitignore vendored Normal file
View File

@@ -0,0 +1,78 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Directory metadata
.directory
# Temporary files
*~
*.swp
*.swo
*.bak
*.tmp
# Log files
*.log
# Build artifacts and directories
**/build/
build/
*.o
*.a
*.out
*.exe # For any host-side utilities compiled on Windows
# ESP-IDF specific build outputs
*.bin
*.elf
*.map
flasher_args.json # Generated in build directory
sdkconfig.old
sdkconfig
# ESP-IDF dependencies
# For older versions or manual component management
/components/.idf/
**/components/.idf/
# For modern ESP-IDF component manager
managed_components/
# If ESP-IDF tools are installed/referenced locally to the project
.espressif/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
# Python environment files
*.pyc
*.pyo
*.pyd
__pycache__/
*.egg-info/
dist/
# Virtual environment folders
venv/
.venv/
env/
# Language Servers
.clangd/
.ccls-cache/
compile_commands.json
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# User-specific configuration files
*.user
*.workspace # General workspace files, can be from various tools
*.suo # Visual Studio Solution User Options
*.sln.docstates # Visual Studio

19
Master/.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "c:\\Espressif\\tools\\tools\\xtensa-esp-elf\\esp-14.2.0_20241119\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

10
Master/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
}
]
}

11
Master/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.openOcdConfigs": [
"board/esp32-wrover-kit-3.3v.cfg"
],
"idf.portWin": "detect",
"idf.currentSetup": "C:\\Users\\mahmo\\esp\\v5.4\\esp-idf",
"idf.customExtraVars": {
"IDF_TARGET": "esp32"
}
}

6
Master/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(Master)

View File

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

74
Master/main/main.c Normal file
View File

@@ -0,0 +1,74 @@
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "driver/i2c.h"
static const char *TAG = "i2c-master";
#define I2C_MASTER_SCL_IO 22 /*!< gpio number for I2C master clock */
#define I2C_MASTER_SDA_IO 21 /*!< gpio number for I2C master data */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
#define SLAVE_ADDRESS 0x0A
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
int i2c_master_port = 0;
static esp_err_t i2c_master_init(void)
{
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_MASTER_SCL_IO,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
// .clk_flags = 0, /*!< Optional, you can use I2C_SCLK_SRC_FLAG_* flags to choose i2c source clock here. */
};
esp_err_t err = i2c_param_config(i2c_master_port, &conf);
if (err != ESP_OK) {
return err;
}
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
}
static esp_err_t i2c_master_send(uint8_t message[], int len)
{
ESP_LOGI(TAG, "Sending Message = %s", message);
esp_err_t ret;
i2c_cmd_handle_t cmd = i2c_cmd_link_create();
i2c_master_start(cmd);
i2c_master_write_byte(cmd, SLAVE_ADDRESS << 1 | WRITE_BIT, ACK_CHECK_EN);
i2c_master_write(cmd, message, len, ACK_CHECK_EN);
i2c_master_stop(cmd);
ret = i2c_master_cmd_begin(i2c_master_port, cmd, 1000 / portTICK_PERIOD_MS);
i2c_cmd_link_delete(cmd);
return ret;
}
void app_main(void)
{
const uint8_t on_command[] = "LED_ON";
const uint8_t off_command[] = "LED_OFF";
ESP_ERROR_CHECK(i2c_master_init());
ESP_LOGI(TAG, "I2C initialized successfully");
while(1)
{
i2c_master_send(on_command, sizeof(on_command));
vTaskDelay(1000/ portTICK_PERIOD_MS);
i2c_master_send(off_command, sizeof(off_command));
vTaskDelay(1000/ portTICK_PERIOD_MS);
}
}

View File

@@ -0,0 +1,13 @@
ARG DOCKER_TAG=latest
FROM espressif/idf:${DOCKER_TAG}
ENV LC_ALL=C.UTF-8
ENV LANG=C.UTF-8
RUN apt-get update -y && apt-get install udev -y
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
CMD ["/bin/bash", "-c"]

View File

@@ -0,0 +1,19 @@
{
"name": "ESP-IDF QEMU",
"build": {
"dockerfile": "Dockerfile"
},
"customizations": {
"vscode": {
"settings": {
"terminal.integrated.defaultProfile.linux": "bash",
"idf.gitPath": "/usr/bin/git"
},
"extensions": [
"espressif.esp-idf-extension",
"espressif.esp-idf-web"
]
}
},
"runArgs": ["--privileged"]
}

78
Slave/.gitignore vendored Normal file
View File

@@ -0,0 +1,78 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
# Directory metadata
.directory
# Temporary files
*~
*.swp
*.swo
*.bak
*.tmp
# Log files
*.log
# Build artifacts and directories
**/build/
build/
*.o
*.a
*.out
*.exe # For any host-side utilities compiled on Windows
# ESP-IDF specific build outputs
*.bin
*.elf
*.map
flasher_args.json # Generated in build directory
sdkconfig.old
sdkconfig
# ESP-IDF dependencies
# For older versions or manual component management
/components/.idf/
**/components/.idf/
# For modern ESP-IDF component manager
managed_components/
# If ESP-IDF tools are installed/referenced locally to the project
.espressif/
# CMake generated files
CMakeCache.txt
CMakeFiles/
cmake_install.cmake
install_manifest.txt
CTestTestfile.cmake
# Python environment files
*.pyc
*.pyo
*.pyd
__pycache__/
*.egg-info/
dist/
# Virtual environment folders
venv/
.venv/
env/
# Language Servers
.clangd/
.ccls-cache/
compile_commands.json
# Windows specific
Thumbs.db
ehthumbs.db
Desktop.ini
# User-specific configuration files
*.user
*.workspace # General workspace files, can be from various tools
*.suo # Visual Studio Solution User Options
*.sln.docstates # Visual Studio

19
Slave/.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"configurations": [
{
"name": "ESP-IDF",
"compilerPath": "c:\\Espressif\\tools\\tools\\xtensa-esp-elf\\esp-14.2.0_20241119\\xtensa-esp-elf\\bin\\xtensa-esp32-elf-gcc.exe",
"compileCommands": "${config:idf.buildPath}/compile_commands.json",
"includePath": [
"${workspaceFolder}/**"
],
"browse": {
"path": [
"${workspaceFolder}"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}

10
Slave/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "gdbtarget",
"request": "attach",
"name": "Eclipse CDT GDB Adapter"
}
]
}

11
Slave/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"C_Cpp.intelliSenseEngine": "default",
"idf.openOcdConfigs": [
"board/esp32-wrover-kit-3.3v.cfg"
],
"idf.portWin": "detect",
"idf.currentSetup": "C:\\Users\\mahmo\\esp\\v5.4\\esp-idf",
"idf.customExtraVars": {
"IDF_TARGET": "esp32"
}
}

6
Slave/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
# The following five lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(Slave)

View File

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

76
Slave/main/main.c Normal file
View File

@@ -0,0 +1,76 @@
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
static const char *TAG = "i2c-slave";
#define LED_PIN 2
#define I2C_SLAVE_SCL_IO 22 /*!< gpio number for I2C master clock */
#define I2C_SLAVE_SDA_IO 21 /*!< gpio number for I2C master data */
#define I2C_SLAVE_FREQ_HZ 100000 /*!< I2C master clock frequency */
#define I2C_SLAVE_TX_BUF_LEN 255 /*!< I2C master doesn't need buffer */
#define I2C_SLAVE_RX_BUF_LEN 255 /*!< I2C master doesn't need buffer */
#define ESP_SLAVE_ADDR 0x0A
#define WRITE_BIT I2C_MASTER_WRITE /*!< I2C master write */
#define READ_BIT I2C_MASTER_READ /*!< I2C master read */
#define ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/
#define ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */
#define ACK_VAL 0x0 /*!< I2C ack value */
#define NACK_VAL 0x1 /*!< I2C nack value */
int i2c_slave_port = 0;
static esp_err_t i2c_slave_init(void)
{
i2c_config_t conf_slave = {
.sda_io_num = I2C_SLAVE_SDA_IO, // select GPIO specific to your project
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_io_num = I2C_SLAVE_SCL_IO, // select GPIO specific to your project
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.mode = I2C_MODE_SLAVE,
.slave.addr_10bit_en = 0,
.slave.slave_addr = ESP_SLAVE_ADDR, // address of your project
.clk_flags = 0,
};
esp_err_t err = i2c_param_config(i2c_slave_port, &conf_slave);
if (err != ESP_OK) {
return err;
}
return i2c_driver_install(i2c_slave_port, conf_slave.mode, I2C_SLAVE_RX_BUF_LEN, I2C_SLAVE_TX_BUF_LEN, 0);
}
void app_main(void)
{
uint8_t received_data[I2C_SLAVE_RX_BUF_LEN] = {0};
uint8_t on_command[] = "LED_ON";
esp_rom_gpio_pad_select_gpio(LED_PIN);
gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);
ESP_ERROR_CHECK(i2c_slave_init());
ESP_LOGI(TAG, "I2C Slave initialized successfully");
while(1)
{
i2c_slave_read_buffer(i2c_slave_port, received_data, I2C_SLAVE_RX_BUF_LEN, 100 / portTICK_PERIOD_MS);
i2c_reset_rx_fifo(i2c_slave_port);
if(strncmp((const char*)received_data, "LED_ON", 6) == 0)
{
ESP_LOGI(TAG, "Data Recived = %s", received_data);
gpio_set_level(LED_PIN, 1);
}
else if(strncmp((const char*)received_data, "LED_OFF", 7) == 0)
{
ESP_LOGI(TAG, "Data Recived = %s", received_data);
gpio_set_level(LED_PIN, 0);
}
memset(received_data, 0, I2C_SLAVE_RX_BUF_LEN);
}
}

Binary file not shown.

View File

@@ -0,0 +1,6 @@
Timestamp,Simulated_CO2,Target_Received_Msg,Status
19:43:59,400.0,"W (4783) SCD30: Command Write Failed, retrying... (0)",CHECK
19:43:59,850.5,"W (4883) SCD30: CRC failed or Read error, retrying... (1)",CHECK
19:44:02,1200.0,"CO2: 400.00 ppm, Temp: 24.00 °C, Humidity: 50.00 %",CHECK
19:44:03,2500.0,"W (8393) SCD30: CRC failed or Read error, retrying... (0)",CHECK
19:44:06,5000.0,"CO2: 1200.00 ppm, Temp: 24.00 °C, Humidity: 50.00 %",CHECK
1 Timestamp Simulated_CO2 Target_Received_Msg Status
2 19:43:59 400.0 W (4783) SCD30: Command Write Failed, retrying... (0) CHECK
3 19:43:59 850.5 W (4883) SCD30: CRC failed or Read error, retrying... (1) CHECK
4 19:44:02 1200.0 CO2: 400.00 ppm, Temp: 24.00 °C, Humidity: 50.00 % CHECK
5 19:44:03 2500.0 W (8393) SCD30: CRC failed or Read error, retrying... (0) CHECK
6 19:44:06 5000.0 CO2: 1200.00 ppm, Temp: 24.00 °C, Humidity: 50.00 % CHECK

View File

@@ -3,8 +3,8 @@ import time
from hil_lib import SCD30_HIL_Lib
# Device paths from your environment
EMULATOR = '/dev/i2c_emulator'
TARGET = '/dev/esp_sensor_test'
EMULATOR = 'COM11'#'/dev/i2c_emulator'
TARGET = 'COM10'#'/dev/esp_sensor_test'
LOG_FILE = 'hil_validation_results.csv'
# Initialize HIL
@@ -32,7 +32,7 @@ with open(LOG_FILE, mode='w', newline='') as f:
if hil.process_bridge(co2, 24.0, 50.0):
data_served = True
break
#time.sleep(4)
if not data_served:
print("Error: Target didn't request data within 10s.")
continue