diff --git a/I2C/components/scd30_driver/scd30.c b/I2C/components/scd30_driver/scd30.c index c02909b..0c046e9 100644 --- a/I2C/components/scd30_driver/scd30.c +++ b/I2C/components/scd30_driver/scd30.c @@ -1,12 +1,18 @@ #include "scd30.h" #include #include +#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]); diff --git a/I2C/components/scd30_driver/scd30.h b/I2C/components/scd30_driver/scd30.h index 7614a89..3cf3e54 100644 --- a/I2C/components/scd30_driver/scd30.h +++ b/I2C/components/scd30_driver/scd30.h @@ -2,7 +2,7 @@ #define SCD30_H #include -#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; /** diff --git a/I2C_emulator/main/main.c b/I2C_emulator/main/main.c index 624830a..5825bd0 100644 --- a/I2C_emulator/main/main.c +++ b/I2C_emulator/main/main.c @@ -2,23 +2,25 @@ #include #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); } \ No newline at end of file diff --git a/Master/.devcontainer/Dockerfile b/Master/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/Master/.devcontainer/Dockerfile @@ -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"] \ No newline at end of file diff --git a/Master/.devcontainer/devcontainer.json b/Master/.devcontainer/devcontainer.json new file mode 100644 index 0000000..246b79f --- /dev/null +++ b/Master/.devcontainer/devcontainer.json @@ -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"] +} \ No newline at end of file diff --git a/Master/.gitignore b/Master/.gitignore new file mode 100644 index 0000000..7805557 --- /dev/null +++ b/Master/.gitignore @@ -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 diff --git a/Master/.vscode/c_cpp_properties.json b/Master/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..2e04660 --- /dev/null +++ b/Master/.vscode/c_cpp_properties.json @@ -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 +} diff --git a/Master/.vscode/launch.json b/Master/.vscode/launch.json new file mode 100644 index 0000000..3694ae4 --- /dev/null +++ b/Master/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] +} \ No newline at end of file diff --git a/Master/.vscode/settings.json b/Master/.vscode/settings.json new file mode 100644 index 0000000..2b2b86a --- /dev/null +++ b/Master/.vscode/settings.json @@ -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" + } +} diff --git a/Master/CMakeLists.txt b/Master/CMakeLists.txt new file mode 100644 index 0000000..661a590 --- /dev/null +++ b/Master/CMakeLists.txt @@ -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) diff --git a/Master/main/CMakeLists.txt b/Master/main/CMakeLists.txt new file mode 100644 index 0000000..cf2c455 --- /dev/null +++ b/Master/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/Master/main/main.c b/Master/main/main.c new file mode 100644 index 0000000..db0a47e --- /dev/null +++ b/Master/main/main.c @@ -0,0 +1,74 @@ +#include +#include +#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); + } + +} \ No newline at end of file diff --git a/Slave/.devcontainer/Dockerfile b/Slave/.devcontainer/Dockerfile new file mode 100644 index 0000000..dafb8ad --- /dev/null +++ b/Slave/.devcontainer/Dockerfile @@ -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"] \ No newline at end of file diff --git a/Slave/.devcontainer/devcontainer.json b/Slave/.devcontainer/devcontainer.json new file mode 100644 index 0000000..246b79f --- /dev/null +++ b/Slave/.devcontainer/devcontainer.json @@ -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"] +} \ No newline at end of file diff --git a/Slave/.gitignore b/Slave/.gitignore new file mode 100644 index 0000000..7805557 --- /dev/null +++ b/Slave/.gitignore @@ -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 diff --git a/Slave/.vscode/c_cpp_properties.json b/Slave/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..2e04660 --- /dev/null +++ b/Slave/.vscode/c_cpp_properties.json @@ -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 +} diff --git a/Slave/.vscode/launch.json b/Slave/.vscode/launch.json new file mode 100644 index 0000000..3694ae4 --- /dev/null +++ b/Slave/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "gdbtarget", + "request": "attach", + "name": "Eclipse CDT GDB Adapter" + } + ] +} \ No newline at end of file diff --git a/Slave/.vscode/settings.json b/Slave/.vscode/settings.json new file mode 100644 index 0000000..2b2b86a --- /dev/null +++ b/Slave/.vscode/settings.json @@ -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" + } +} diff --git a/Slave/CMakeLists.txt b/Slave/CMakeLists.txt new file mode 100644 index 0000000..abaccc0 --- /dev/null +++ b/Slave/CMakeLists.txt @@ -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) diff --git a/Slave/main/CMakeLists.txt b/Slave/main/CMakeLists.txt new file mode 100644 index 0000000..cf2c455 --- /dev/null +++ b/Slave/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "main.c" + INCLUDE_DIRS ".") diff --git a/Slave/main/main.c b/Slave/main/main.c new file mode 100644 index 0000000..fb7873d --- /dev/null +++ b/Slave/main/main.c @@ -0,0 +1,76 @@ +#include +#include +#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); + } + +} diff --git a/__pycache__/hil_lib.cpython-313.pyc b/__pycache__/hil_lib.cpython-313.pyc new file mode 100644 index 0000000..cd67c96 Binary files /dev/null and b/__pycache__/hil_lib.cpython-313.pyc differ diff --git a/hil_validation_results.csv b/hil_validation_results.csv new file mode 100644 index 0000000..60276a3 --- /dev/null +++ b/hil_validation_results.csv @@ -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 diff --git a/run_test.py b/run_test.py index 63518aa..cc28374 100644 --- a/run_test.py +++ b/run_test.py @@ -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