222 lines
7.0 KiB
C
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(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)
|
|
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
|
|
}
|