Files
HIL_setup/I2C/components/scd30_driver/scd30.c
2026-02-11 23:03:59 +01:00

222 lines
7.0 KiB
C

#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;
uint8_t crc = 0xFF;
for (size_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t bit = 0; bit < 8; bit++) {
if (crc & 0x80) {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
return crc;
}
scd30_handle_t *scd30_init(int sda_gpio, int scl_gpio) {
static scd30_handle_t handle;
int i2c_master_port = I2C_NUM_0;
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_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;
}
void scd30_start_measurement(scd30_handle_t *handle) {
uint8_t command[5] = {
(SCD30_CMD_START_MEASUREMENT >> 8) & 0xFF,
SCD30_CMD_START_MEASUREMENT & 0xFF,
0x00, 0x10, // Argument for continuous measurement
0x00 // Placeholder for CRC
};
command[4] = calculate_crc8(&command[2], 2);
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
static float bytes_to_float(uint8_t mmsb, uint8_t mlsb, uint8_t lmsb, uint8_t llsb) {
uint32_t val = ((uint32_t)mmsb << 24) | ((uint32_t)mlsb << 16) | ((uint32_t)lmsb << 8) | (uint32_t)llsb;
float f;
memcpy(&f, &val, 4);
return f;
}
int scd30_read_data(scd30_handle_t *handle, scd30_data_t *data) {
uint8_t ready_cmd_bytes[2] = {
(SCD30_CMD_READY_STATUS >> 8) & 0xFF,
SCD30_CMD_READY_STATUS & 0xFF
};
uint8_t ready_status[3]; // [MSB, LSB, CRC]
// Poll until data is ready
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;
}
// 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 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(100)); // 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)
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]);
return 0; // Success
}