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;
/**