diff --git a/drivers/sensor/sensirion/CMakeLists.txt b/drivers/sensor/sensirion/CMakeLists.txt index 08d20a61ed87a..fae315f896a23 100644 --- a/drivers/sensor/sensirion/CMakeLists.txt +++ b/drivers/sensor/sensirion/CMakeLists.txt @@ -2,10 +2,12 @@ # SPDX-License-Identifier: Apache-2.0 # zephyr-keep-sorted-start +add_subdirectory(sensirion_core) add_subdirectory_ifdef(CONFIG_SCD4X scd4x) add_subdirectory_ifdef(CONFIG_SGP40 sgp40) add_subdirectory_ifdef(CONFIG_SHT3XD sht3xd) add_subdirectory_ifdef(CONFIG_SHT4X sht4x) add_subdirectory_ifdef(CONFIG_SHTCX shtcx) +add_subdirectory_ifdef(CONFIG_STCC4 stcc4) add_subdirectory_ifdef(CONFIG_STS4X sts4x) # zephyr-keep-sorted-stop diff --git a/drivers/sensor/sensirion/Kconfig b/drivers/sensor/sensirion/Kconfig index 2b70b2e731d8a..dc45aba94403c 100644 --- a/drivers/sensor/sensirion/Kconfig +++ b/drivers/sensor/sensirion/Kconfig @@ -7,5 +7,6 @@ source "drivers/sensor/sensirion/sgp40/Kconfig" source "drivers/sensor/sensirion/sht3xd/Kconfig" source "drivers/sensor/sensirion/sht4x/Kconfig" source "drivers/sensor/sensirion/shtcx/Kconfig" +source "drivers/sensor/sensirion/stcc4/Kconfig" source "drivers/sensor/sensirion/sts4x/Kconfig" # zephyr-keep-sorted-stop diff --git a/drivers/sensor/sensirion/sensirion_core/CMakeLists.txt b/drivers/sensor/sensirion/sensirion_core/CMakeLists.txt new file mode 100644 index 0000000000000..3f6911425ac0d --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/CMakeLists.txt @@ -0,0 +1,11 @@ +# ST Microelectronics stmemsc hal i/f +# +# Copyright (c) 2021 STMicroelectronics +# +# SPDX-License-Identifier: Apache-2.0 +# +zephyr_library() + +zephyr_library_sources(sensirion_common.c) +zephyr_library_sources(sensirion_i2c.c) +zephyr_library_sources(crc_tables.c) diff --git a/drivers/sensor/sensirion/sensirion_core/crc_tables.c b/drivers/sensor/sensirion/sensirion_core/crc_tables.c new file mode 100644 index 0000000000000..7394bf9204ddb --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/crc_tables.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +uint8_t crc8_poly31_table[256] = { + 0x00, 0x31, 0x62, 0x53, 0xC4, 0xF5, 0xA6, 0x97, 0xB9, 0x88, 0xDB, 0xEA, 0x7D, 0x4C, 0x1F, + 0x2E, 0x43, 0x72, 0x21, 0x10, 0x87, 0xB6, 0xE5, 0xD4, 0xFA, 0xCB, 0x98, 0xA9, 0x3E, 0x0F, + 0x5C, 0x6D, 0x86, 0xB7, 0xE4, 0xD5, 0x42, 0x73, 0x20, 0x11, 0x3F, 0x0E, 0x5D, 0x6C, 0xFB, + 0xCA, 0x99, 0xA8, 0xC5, 0xF4, 0xA7, 0x96, 0x01, 0x30, 0x63, 0x52, 0x7C, 0x4D, 0x1E, 0x2F, + 0xB8, 0x89, 0xDA, 0xEB, 0x3D, 0x0C, 0x5F, 0x6E, 0xF9, 0xC8, 0x9B, 0xAA, 0x84, 0xB5, 0xE6, + 0xD7, 0x40, 0x71, 0x22, 0x13, 0x7E, 0x4F, 0x1C, 0x2D, 0xBA, 0x8B, 0xD8, 0xE9, 0xC7, 0xF6, + 0xA5, 0x94, 0x03, 0x32, 0x61, 0x50, 0xBB, 0x8A, 0xD9, 0xE8, 0x7F, 0x4E, 0x1D, 0x2C, 0x02, + 0x33, 0x60, 0x51, 0xC6, 0xF7, 0xA4, 0x95, 0xF8, 0xC9, 0x9A, 0xAB, 0x3C, 0x0D, 0x5E, 0x6F, + 0x41, 0x70, 0x23, 0x12, 0x85, 0xB4, 0xE7, 0xD6, 0x7A, 0x4B, 0x18, 0x29, 0xBE, 0x8F, 0xDC, + 0xED, 0xC3, 0xF2, 0xA1, 0x90, 0x07, 0x36, 0x65, 0x54, 0x39, 0x08, 0x5B, 0x6A, 0xFD, 0xCC, + 0x9F, 0xAE, 0x80, 0xB1, 0xE2, 0xD3, 0x44, 0x75, 0x26, 0x17, 0xFC, 0xCD, 0x9E, 0xAF, 0x38, + 0x09, 0x5A, 0x6B, 0x45, 0x74, 0x27, 0x16, 0x81, 0xB0, 0xE3, 0xD2, 0xBF, 0x8E, 0xDD, 0xEC, + 0x7B, 0x4A, 0x19, 0x28, 0x06, 0x37, 0x64, 0x55, 0xC2, 0xF3, 0xA0, 0x91, 0x47, 0x76, 0x25, + 0x14, 0x83, 0xB2, 0xE1, 0xD0, 0xFE, 0xCF, 0x9C, 0xAD, 0x3A, 0x0B, 0x58, 0x69, 0x04, 0x35, + 0x66, 0x57, 0xC0, 0xF1, 0xA2, 0x93, 0xBD, 0x8C, 0xDF, 0xEE, 0x79, 0x48, 0x1B, 0x2A, 0xC1, + 0xF0, 0xA3, 0x92, 0x05, 0x34, 0x67, 0x56, 0x78, 0x49, 0x1A, 0x2B, 0xBC, 0x8D, 0xDE, 0xEF, + 0x82, 0xB3, 0xE0, 0xD1, 0x46, 0x77, 0x24, 0x15, 0x3B, 0x0A, 0x59, 0x68, 0xFF, 0xCE, 0x9D, + 0xAC}; diff --git a/drivers/sensor/sensirion/sensirion_core/sensirion_common.c b/drivers/sensor/sensirion/sensirion_core/sensirion_common.c new file mode 100644 index 0000000000000..3c21230d4e39c --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/sensirion_common.c @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sensirion_common.h" + +uint16_t sensirion_common_bytes_to_uint16_t(const uint8_t *bytes) +{ + return (uint16_t)bytes[0] << 8 | (uint16_t)bytes[1]; +} + +uint32_t sensirion_common_bytes_to_uint32_t(const uint8_t *bytes) +{ + return (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | (uint32_t)bytes[2] << 8 | + (uint32_t)bytes[3]; +} + +int16_t sensirion_common_bytes_to_int16_t(const uint8_t *bytes) +{ + return (int16_t)sensirion_common_bytes_to_uint16_t(bytes); +} + +int32_t sensirion_common_bytes_to_int32_t(const uint8_t *bytes) +{ + return (int32_t)sensirion_common_bytes_to_uint32_t(bytes); +} + +float sensirion_common_bytes_to_float(const uint8_t *bytes) +{ + union { + uint32_t u32_value; + float float32; + } tmp; + + tmp.u32_value = sensirion_common_bytes_to_uint32_t(bytes); + return tmp.float32; +} + +void sensirion_common_uint32_t_to_bytes(const uint32_t value, uint8_t *bytes) +{ + bytes[0] = (uint8_t)(value >> 24); + bytes[1] = (uint8_t)(value >> 16); + bytes[2] = (uint8_t)(value >> 8); + bytes[3] = (uint8_t)(value); +} + +void sensirion_common_uint16_t_to_bytes(const uint16_t value, uint8_t *bytes) +{ + bytes[0] = (uint8_t)(value >> 8); + bytes[1] = (uint8_t)value; +} + +void sensirion_common_int32_t_to_bytes(const int32_t value, uint8_t *bytes) +{ + bytes[0] = (uint8_t)(value >> 24); + bytes[1] = (uint8_t)(value >> 16); + bytes[2] = (uint8_t)(value >> 8); + bytes[3] = (uint8_t)value; +} + +void sensirion_common_int16_t_to_bytes(const int16_t value, uint8_t *bytes) +{ + bytes[0] = (uint8_t)(value >> 8); + bytes[1] = (uint8_t)value; +} + +void sensirion_common_float_to_bytes(const float value, uint8_t *bytes) +{ + union { + uint32_t u32_value; + float float32; + } tmp; + tmp.float32 = value; + sensirion_common_uint32_t_to_bytes(tmp.u32_value, bytes); +} + +void sensirion_common_copy_bytes(const uint8_t *source, uint8_t *destination, uint16_t data_length) +{ + uint16_t i; + + for (i = 0; i < data_length; i++) { + destination[i] = source[i]; + } +} + +void sensirion_common_to_integer(const uint8_t *source, uint8_t *destination, INT_TYPE int_type, + uint8_t data_length) +{ + if (data_length > int_type) { + data_length = 0; + } + + uint8_t offset = int_type - data_length; + + for (uint8_t i = 0; i < offset; i++) { + destination[int_type - i - 1] = 0; + } + + for (uint8_t i = 1; i <= data_length; i++) { + destination[int_type - offset - i] = source[i - 1]; + } +} diff --git a/drivers/sensor/sensirion/sensirion_core/sensirion_common.h b/drivers/sensor/sensirion/sensirion_core/sensirion_common.h new file mode 100644 index 0000000000000..5b79218425b3d --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/sensirion_common.h @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SENSIRION_COMMON_H +#define SENSIRION_COMMON_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#define NO_ERROR 0 +#define NOT_IMPLEMENTED_ERROR ENOSYS + +#define SENSIRION_COMMAND_SIZE 2 +#define SENSIRION_WORD_SIZE 2 +#define SENSIRION_NUM_WORDS(x) (sizeof(x) / SENSIRION_WORD_SIZE) +#define SENSIRION_MAX_BUFFER_WORDS 32 + +/** + * Enum to describe the type of an integer + */ +typedef enum { + BYTE = 1, + SHORT = 2, + INTEGER = 4, + LONG_INTEGER = 8 +} INT_TYPE; + +/** + * sensirion_common_bytes_to_int16_t() - Convert an array of bytes to an int16_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an int16_t value in the correct system-endianness. + * + * @param bytes An array of at least two bytes (MSB first) + * @return The byte array represented as int16_t + */ +int16_t sensirion_common_bytes_to_int16_t(const uint8_t *bytes); + +/** + * sensirion_common_bytes_to_int32_t() - Convert an array of bytes to an int32_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an int32_t value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as int32_t + */ +int32_t sensirion_common_bytes_to_int32_t(const uint8_t *bytes); + +/** + * sensirion_common_bytes_to_uint16_t() - Convert an array of bytes to an + * uint16_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an uint16_t value in the correct system-endianness. + * + * @param bytes An array of at least two bytes (MSB first) + * @return The byte array represented as uint16_t + */ +uint16_t sensirion_common_bytes_to_uint16_t(const uint8_t *bytes); + +/** + * sensirion_common_bytes_to_uint32_t() - Convert an array of bytes to an + * uint32_t + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an uint32_t value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as uint32_t + */ +uint32_t sensirion_common_bytes_to_uint32_t(const uint8_t *bytes); + +/** + * sensirion_common_bytes_to_float() - Convert an array of bytes to a float + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an float value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as float + */ +float sensirion_common_bytes_to_float(const uint8_t *bytes); + +/** + * sensirion_common_uint32_t_to_bytes() - Convert an uint32_t to an array of + * bytes + * + * Convert an uint32_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_uint32_t_to_bytes(const uint32_t value, uint8_t *bytes); + +/** + * sensirion_common_uint16_t_to_bytes() - Convert an uint16_t to an array of + * bytes + * + * Convert an uint16_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least two bytes + */ +void sensirion_common_uint16_t_to_bytes(const uint16_t value, uint8_t *bytes); + +/** + * sensirion_common_int32_t_to_bytes() - Convert an int32_t to an array of bytes + * + * Convert an int32_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_int32_t_to_bytes(const int32_t value, uint8_t *bytes); + +/** + * sensirion_common_int16_t_to_bytes() - Convert an int16_t to an array of bytes + * + * Convert an int16_t value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least two bytes + */ +void sensirion_common_int16_t_to_bytes(const int16_t value, uint8_t *bytes); + +/** + * sensirion_common_float_to_bytes() - Convert an float to an array of bytes + * + * Convert an float value in system-endianness to big-endian/MBS-first + * format to send to the sensor. + * + * @param value Value to convert + * @param bytes An array of at least four bytes + */ +void sensirion_common_float_to_bytes(const float value, uint8_t *bytes); + +/** + * sensirion_common_copy_bytes() - Copy bytes from one array to the other. + * + * @param source Array of bytes to be copied. + * @param destination Array of bytes to be copied to. + * @param data_length Number of bytes to copy. + */ +void sensirion_common_copy_bytes(const uint8_t *source, uint8_t *destination, uint16_t data_length); + +/** + * sensirion_common_to_integer() - Copy bytes from byte array to integer. + * + * @param source Array of bytes to be copied. + * @param int_value Pointer to integer of bytes to be copied to. + * @param int_type Type (size) of the integer to be copied. + * @param data_length Number of bytes to copy. + */ +void sensirion_common_to_integer(const uint8_t *source, uint8_t *destination, INT_TYPE int_type, + uint8_t data_length); + +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_COMMON_H */ diff --git a/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.c b/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.c new file mode 100644 index 0000000000000..c6c3a3b50b80e --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.c @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sensirion_i2c.h" +#include "sensirion_common.h" + +#include +#include + +extern uint8_t crc8_poly31_table[256]; + +uint8_t sensirion_i2c_generate_crc(const uint8_t *data, uint16_t count) +{ + uint8_t crc = CRC8_INIT; + + for (size_t i = 0; i < count; i++) { + crc = crc8_poly31_table[crc ^ data[i]]; + } + return crc; +} + +int8_t sensirion_i2c_check_crc(const uint8_t *data, uint16_t count, uint8_t checksum) +{ + if (sensirion_i2c_generate_crc(data, count) != checksum) { + return CRC_ERROR; + } + return NO_ERROR; +} + +int sensirion_i2c_general_call_reset(const struct i2c_dt_spec *i2c_spec) +{ + const uint8_t data = 0x06; + const uint8_t i2c_address = 0x00; + + return i2c_write(i2c_spec->bus, &data, sizeof(data), i2c_address); +} + +uint16_t sensirion_i2c_fill_cmd_send_buf(uint8_t *buf, uint16_t cmd, const uint16_t *args, + uint8_t num_args) +{ + uint8_t i; + uint16_t idx = 0; + + buf[idx++] = (uint8_t)((cmd & 0xFF00) >> 8); + buf[idx++] = (uint8_t)((cmd & 0x00FF) >> 0); + + for (i = 0; i < num_args; ++i) { + buf[idx++] = (uint8_t)((args[i] & 0xFF00) >> 8); + buf[idx++] = (uint8_t)((args[i] & 0x00FF) >> 0); + + uint8_t crc = + sensirion_i2c_generate_crc((uint8_t *)&buf[idx - 2], SENSIRION_WORD_SIZE); + buf[idx++] = crc; + } + return idx; +} + +int sensirion_i2c_read_words_as_bytes(const struct i2c_dt_spec *i2c_spec, uint8_t *data, + uint16_t num_words) +{ + int error; + uint16_t i, j; + uint16_t size = num_words * (SENSIRION_WORD_SIZE + CRC8_LEN); + uint16_t word_buf[SENSIRION_MAX_BUFFER_WORDS]; + uint8_t *const buf8 = (uint8_t *)word_buf; + + error = i2c_read_dt(i2c_spec, buf8, size); + if (error != NO_ERROR) { + return error; + } + + /* check the CRC for each word */ + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) { + + error = sensirion_i2c_check_crc(&buf8[i], SENSIRION_WORD_SIZE, + buf8[i + SENSIRION_WORD_SIZE]); + if (error != NO_ERROR) { + return error; + } + + data[j++] = buf8[i]; + data[j++] = buf8[i + 1]; + } + + return NO_ERROR; +} + +int sensirion_i2c_read_words(const struct i2c_dt_spec *i2c_spec, uint16_t *data_words, + uint16_t num_words) +{ + int error; + uint8_t i; + + error = sensirion_i2c_read_words_as_bytes(i2c_spec, (uint8_t *)data_words, num_words); + if (error != NO_ERROR) { + return error; + } + + for (i = 0; i < num_words; ++i) { + const uint8_t *word_bytes = (uint8_t *)&data_words[i]; + + data_words[i] = ((uint16_t)word_bytes[0] << 8) | word_bytes[1]; + } + + return NO_ERROR; +} + +int sensirion_i2c_write_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t command) +{ + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, command, NULL, 0); + return i2c_write_dt(i2c_spec, buf, SENSIRION_COMMAND_SIZE); +} + +int sensirion_i2c_write_cmd_with_args(const struct i2c_dt_spec *i2c_spec, uint16_t command, + const uint16_t *data_words, uint16_t num_words) +{ + uint8_t buf[SENSIRION_MAX_BUFFER_WORDS]; + uint16_t buf_size; + + buf_size = sensirion_i2c_fill_cmd_send_buf(buf, command, data_words, num_words); + return i2c_write_dt(i2c_spec, buf, buf_size); +} + +int sensirion_i2c_delayed_read_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t cmd, + uint32_t delay_us, uint16_t *data_words, uint16_t num_words) +{ + int error; + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, cmd, NULL, 0); + error = i2c_write_dt(i2c_spec, buf, SENSIRION_COMMAND_SIZE); + if (error != NO_ERROR) { + return error; + } + + if (delay_us) { + k_usleep(delay_us); + } + + return sensirion_i2c_read_words(i2c_spec, data_words, num_words); +} + +int sensirion_i2c_read_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t cmd, uint16_t *data_words, + uint16_t num_words) +{ + return sensirion_i2c_delayed_read_cmd(i2c_spec, cmd, 0, data_words, num_words); +} + +uint16_t sensirion_i2c_add_command_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t command) +{ + buffer[offset++] = (uint8_t)((command & 0xFF00) >> 8); + buffer[offset++] = (uint8_t)((command & 0x00FF) >> 0); + return offset; +} + +uint16_t sensirion_i2c_add_command16_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t command) +{ + buffer[offset++] = (uint8_t)((command & 0xFF00) >> 8); + buffer[offset++] = (uint8_t)((command & 0x00FF) >> 0); + return offset; +} + +uint16_t sensirion_i2c_add_command8_to_buffer(uint8_t *buffer, uint16_t offset, uint8_t command) +{ + buffer[offset++] = command; + return offset; +} + +uint16_t sensirion_i2c_add_uint32_t_to_buffer(uint8_t *buffer, uint16_t offset, uint32_t data) +{ + buffer[offset++] = (uint8_t)((data & 0xFF000000) >> 24); + buffer[offset++] = (uint8_t)((data & 0x00FF0000) >> 16); + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + buffer[offset++] = (uint8_t)((data & 0x0000FF00) >> 8); + buffer[offset++] = (uint8_t)((data & 0x000000FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_int32_t_to_buffer(uint8_t *buffer, uint16_t offset, int32_t data) +{ + return sensirion_i2c_add_uint32_t_to_buffer(buffer, offset, (uint32_t)data); +} + +uint16_t sensirion_i2c_add_uint16_t_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t data) +{ + buffer[offset++] = (uint8_t)((data & 0xFF00) >> 8); + buffer[offset++] = (uint8_t)((data & 0x00FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_int16_t_to_buffer(uint8_t *buffer, uint16_t offset, int16_t data) +{ + return sensirion_i2c_add_uint16_t_to_buffer(buffer, offset, (uint16_t)data); +} + +uint16_t sensirion_i2c_add_float_to_buffer(uint8_t *buffer, uint16_t offset, float data) +{ + union { + uint32_t uint32_data; + float float_data; + } convert; + + convert.float_data = data; + + buffer[offset++] = (uint8_t)((convert.uint32_data & 0xFF000000) >> 24); + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x00FF0000) >> 16); + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x0000FF00) >> 8); + buffer[offset++] = (uint8_t)((convert.uint32_data & 0x000000FF) >> 0); + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + + return offset; +} + +uint16_t sensirion_i2c_add_bytes_to_buffer(uint8_t *buffer, uint16_t offset, const uint8_t *data, + uint16_t data_length) +{ + uint16_t i; + + if (data_length % SENSIRION_WORD_SIZE != 0) { + return BYTE_NUM_ERROR; + } + + for (i = 0; i < data_length; i += 2) { + buffer[offset++] = data[i]; + buffer[offset++] = data[i + 1]; + + buffer[offset] = sensirion_i2c_generate_crc(&buffer[offset - SENSIRION_WORD_SIZE], + SENSIRION_WORD_SIZE); + offset++; + } + + return offset; +} + +int sensirion_i2c_write_data(const struct i2c_dt_spec *i2c_spec, const uint8_t *data, + uint16_t data_length) +{ + return i2c_write_dt(i2c_spec, data, data_length); +} + +int sensirion_i2c_read_data_inplace(const struct i2c_dt_spec *i2c_spec, uint8_t *buffer, + uint16_t expected_data_length) +{ + int error; + uint16_t i, j; + uint16_t size = + (expected_data_length / SENSIRION_WORD_SIZE) * (SENSIRION_WORD_SIZE + CRC8_LEN); + + if (expected_data_length % SENSIRION_WORD_SIZE != 0) { + return BYTE_NUM_ERROR; + } + + error = i2c_read_dt(i2c_spec, buffer, size); + if (error) { + return error; + } + + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) { + + error = sensirion_i2c_check_crc(&buffer[i], SENSIRION_WORD_SIZE, + buffer[i + SENSIRION_WORD_SIZE]); + if (error) { + return error; + } + buffer[j++] = buffer[i]; + buffer[j++] = buffer[i + 1]; + } + + return NO_ERROR; +} diff --git a/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.h b/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.h new file mode 100644 index 0000000000000..aadd1e91cf06d --- /dev/null +++ b/drivers/sensor/sensirion/sensirion_core/sensirion_i2c.h @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SENSIRION_I2C_H +#define SENSIRION_I2C_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#define CRC_ERROR 1 +#define I2C_BUS_ERROR 2 +#define I2C_NACK_ERROR 3 +#define BYTE_NUM_ERROR 4 + +#define CRC8_INIT 0xFF +#define CRC8_LEN 1 + +#define SENSIRION_COMMAND_SIZE 2 +#define SENSIRION_WORD_SIZE 2 +#define SENSIRION_NUM_WORDS(x) (sizeof(x) / SENSIRION_WORD_SIZE) +#define SENSIRION_MAX_BUFFER_WORDS 32 + +uint8_t sensirion_i2c_generate_crc(const uint8_t *data, uint16_t count); + +int8_t sensirion_i2c_check_crc(const uint8_t *data, uint16_t count, uint8_t checksum); + +/** + * sensirion_i2c_general_call_reset() - Send a general call reset. + * + * @warning This will reset all attached I2C devices on the bus which support + * general call reset. + * + * @param i2c_spec Sensor I2C specification + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_general_call_reset(const struct i2c_dt_spec *i2c_spec); + +/** + * sensirion_i2c_fill_cmd_send_buf() - create the i2c send buffer for a command + * and a set of argument words. The output buffer interleaves argument words + * with their checksums. + * @param buf: The generated buffer to send over i2c. Then buffer length + * must be at least SENSIRION_COMMAND_LEN + num_args * (SENSIRION_WORD_SIZE + + * CRC8_LEN). + * @param cmd: The i2c command to send. It already includes a checksum. + * @param args: The arguments to the command. Can be NULL if none. + * @param num_args: The number of word arguments in args. + * + * @return The number of bytes written to buf + */ +uint16_t sensirion_i2c_fill_cmd_send_buf(uint8_t *buf, uint16_t cmd, const uint16_t *args, + uint8_t num_args); + +/** + * sensirion_i2c_read_words() - read data words from sensor + * + * @param i2c_spec: Sensor I2C specification + * @param data_words: Allocated buffer to store the read words. + * The buffer may also have been modified in case of an error. + * @param num_words: Number of data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_read_words(const struct i2c_dt_spec *i2c_spec, uint16_t *data_words, + uint16_t num_words); + +/** + * sensirion_i2c_read_words_as_bytes() - read data words as byte-stream from + * sensor + * + * Read bytes without adjusting values to the uP's word-order. + * + * @param i2c_spec: Sensor I2C specification + * @param data: Allocated buffer to store the read bytes. + * The buffer may also have been modified in case of an error. + * @param num_words: Number of data words(!) to read (without CRC bytes) + * Since only word-chunks can be read from the sensor the size + * is still specified in sensor-words (num_words = num_bytes * + * SENSIRION_WORD_SIZE) + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_read_words_as_bytes(const struct i2c_dt_spec *i2c_spec, uint8_t *data, + uint16_t num_words); + +/** + * sensirion_i2c_write_cmd() - writes a command to the sensor + * @param i2c_spec: Sensor I2C specification + * @param command: Sensor command + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_write_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t command); + +/** + * sensirion_i2c_write_cmd_with_args() - writes a command with arguments to the + * sensor + * @param i2c_spec: Sensor I2C specification + * @param command: Sensor command + * @param data: Argument buffer with words to send + * @param num_words: Number of data words to send (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_write_cmd_with_args(const struct i2c_dt_spec *i2c_spec, uint16_t command, + const uint16_t *data_words, uint16_t num_words); + +/** + * sensirion_i2c_delayed_read_cmd() - send a command, wait for the sensor to + * process and read data back + * @param i2c_spec: Sensor I2C specification + * @param cmd: Command + * @param delay: Time in microseconds to delay sending the read request + * @param data_words: Allocated buffer to store the read data + * @param num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_delayed_read_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t cmd, + uint32_t delay_us, uint16_t *data_words, uint16_t num_words); +/** + * sensirion_i2c_read_cmd() - reads data words from the sensor after a command + * is issued + * @param i2c_spec: Sensor I2C specification + * @param cmd: Command + * @param data_words: Allocated buffer to store the read data + * @param num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_read_cmd(const struct i2c_dt_spec *i2c_spec, uint16_t cmd, uint16_t *data_words, + uint16_t num_words); + +/** + * sensirion_i2c_add_command_to_buffer() - Add a command to the buffer at + * offset. Adds 2 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param command Command to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_command_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t command); + +/** + * sensirion_i2c_add_command16_to_buffer() - Add a command to the buffer at + * the specified offset. This function is equivalent to the + * function sensirion_i2c_add_command_to_buffer(). + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param command Command to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_command16_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t command); + +/** + * sensirion_i2c_add_command8_to_buffer() - Add a command to the buffer at + * offset. Adds one bytes command to the buffer. + * This is used for sensor that only take one command byte such as + * SHT. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param command Command to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_command8_to_buffer(uint8_t *buffer, uint16_t offset, uint8_t command); + +/** + * sensirion_i2c_add_uint32_t_to_buffer() - Add a uint32_t to the buffer at + * offset. Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data uint32_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_uint32_t_to_buffer(uint8_t *buffer, uint16_t offset, uint32_t data); + +/** + * sensirion_i2c_add_int32_t_to_buffer() - Add a int32_t to the buffer at + * offset. Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data int32_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_int32_t_to_buffer(uint8_t *buffer, uint16_t offset, int32_t data); + +/** + * sensirion_i2c_add_uint16_t_to_buffer() - Add a uint16_t to the buffer at + * offset. Adds 3 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data uint16_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_uint16_t_to_buffer(uint8_t *buffer, uint16_t offset, uint16_t data); + +/** + * sensirion_i2c_add_int16_t_to_buffer() - Add a int16_t to the buffer at + * offset. Adds 3 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data int16_t to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_int16_t_to_buffer(uint8_t *buffer, uint16_t offset, int16_t data); + +/** + * sensirion_i2c_add_float_to_buffer() - Add a float to the buffer at offset. + * Adds 6 bytes to the buffer. + * + * @param buffer Pointer to buffer in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data float to be written into the buffer. + * + * @return Offset of next free byte in the buffer after writing the data. + */ +uint16_t sensirion_i2c_add_float_to_buffer(uint8_t *buffer, uint16_t offset, float data); + +/** + * sensirion_i2c_add_bytes_to_buffer() - Add a byte array to the buffer at + * offset. + * + * @param buffer Pointer to buffer in which the write frame will be + * prepared. Caller needs to make sure that there is + * enough space after offset left to write the data + * into the buffer. + * @param offset Offset of the next free byte in the buffer. + * @param data Pointer to data to be written into the buffer. + * @param data_length Number of bytes to be written into the buffer. Needs to + * be a multiple of SENSIRION_WORD_SIZE otherwise the + * function returns BYTE_NUM_ERROR. + * + * @return Offset of next free byte in the buffer after writing the + * data. + */ +uint16_t sensirion_i2c_add_bytes_to_buffer(uint8_t *buffer, uint16_t offset, const uint8_t *data, + uint16_t data_length); + +/** + * sensirion_i2c_write_data() - Writes data to the Sensor. + * + * @note This is just a wrapper for sensirion_i2c_hal_write() to + * not need to include the HAL in the drivers. + * + * @param i2c_spec Sensor I2C specification + * @param data Pointer to the buffer containing the data to write. + * @param data_length Number of bytes to send to the Sensor. + * + * @return NO_ERROR on success, error code otherwise + */ +int sensirion_i2c_write_data(const struct i2c_dt_spec *i2c_spec, const uint8_t *data, + uint16_t data_length); + +/** + * sensirion_i2c_read_data_inplace() - Reads data from the Sensor. + * + * @param i2c_spec Sensor I2C specification + * @param buffer Allocated buffer to store data as bytes. Needs + * to be big enough to store the data including + * CRC. Twice the size of data should always + * suffice. + * @param expected_data_length Number of bytes to read (without CRC). Needs + * to be a multiple of SENSIRION_WORD_SIZE, + * otherwise the function returns BYTE_NUM_ERROR. + * + * @return NO_ERROR on success, an error code otherwise + */ +int sensirion_i2c_read_data_inplace(const struct i2c_dt_spec *i2c_spec, uint8_t *buffer, + uint16_t expected_data_length); + +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_I2C_H */ diff --git a/drivers/sensor/sensirion/stcc4/CMakeLists.txt b/drivers/sensor/sensirion/stcc4/CMakeLists.txt new file mode 100644 index 0000000000000..20307cd58604f --- /dev/null +++ b/drivers/sensor/sensirion/stcc4/CMakeLists.txt @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(stcc4.c) diff --git a/drivers/sensor/sensirion/stcc4/Kconfig b/drivers/sensor/sensirion/stcc4/Kconfig new file mode 100644 index 0000000000000..e04f468d1437b --- /dev/null +++ b/drivers/sensor/sensirion/stcc4/Kconfig @@ -0,0 +1,12 @@ +# STCC4 sensor configuration options. + +# Copyright (c) 2025, Sensirion AG +# SPDX-License-Identifier: Apache-2.0 + +config STCC4 + bool "STCC4 Sensor" + default y + depends on DT_HAS_SENSIRION_STCC4_ENABLED + select I2C + help + Enable driver for STCC4 Sensor diff --git a/drivers/sensor/sensirion/stcc4/stcc4.c b/drivers/sensor/sensirion/stcc4/stcc4.c new file mode 100644 index 0000000000000..d257d119e496b --- /dev/null +++ b/drivers/sensor/sensirion/stcc4/stcc4.c @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT sensirion_stcc4 + +#include "stcc4.h" +#include "../sensirion_core/sensirion_common.h" +#include "../sensirion_core/sensirion_i2c.h" + +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(STCC4, CONFIG_SENSOR_LOG_LEVEL); + +static const struct i2c_dt_spec *i2c_spec; + +static uint8_t communication_buffer[18] = {0}; + +int stcc4_start_continuous_measurement(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer( + buffer_ptr, local_offset, STCC4_START_CONTINUOUS_MEASUREMENT_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + return local_error; +} + +int stcc4_read_measurement_raw(int16_t *co2_concentration_raw, uint16_t *temperature_raw, + uint16_t *relative_humidity_raw, uint16_t *sensor_status_raw) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_READ_MEASUREMENT_RAW_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(1); + local_error = sensirion_i2c_read_data_inplace(i2c_spec, buffer_ptr, 8); + if (local_error != NO_ERROR) { + return local_error; + } + *co2_concentration_raw = sensirion_common_bytes_to_int16_t(&buffer_ptr[0]); + *temperature_raw = sensirion_common_bytes_to_uint16_t(&buffer_ptr[2]); + *relative_humidity_raw = sensirion_common_bytes_to_uint16_t(&buffer_ptr[4]); + *sensor_status_raw = sensirion_common_bytes_to_uint16_t(&buffer_ptr[6]); + return local_error; +} + +int stcc4_stop_continuous_measurement(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer( + buffer_ptr, local_offset, STCC4_STOP_CONTINUOUS_MEASUREMENT_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(1200); + return local_error; +} + +int stcc4_measure_single_shot(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_MEASURE_SINGLE_SHOT_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(500); + return local_error; +} + +int stcc4_perform_forced_recalibration(int16_t target_co2_concentration, int16_t *frc_correction) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer( + buffer_ptr, local_offset, STCC4_PERFORM_FORCED_RECALIBRATION_CMD_ID); + local_offset = sensirion_i2c_add_int16_t_to_buffer(buffer_ptr, local_offset, + target_co2_concentration); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(90); + local_error = sensirion_i2c_read_data_inplace(i2c_spec, buffer_ptr, 2); + if (local_error != NO_ERROR) { + return local_error; + } + *frc_correction = sensirion_common_bytes_to_int16_t(&buffer_ptr[0]); + return local_error; +} + +int stcc4_get_product_id(uint32_t *product_id, uint64_t *serial_number) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_GET_PRODUCT_ID_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(1); + local_error = sensirion_i2c_read_data_inplace(i2c_spec, buffer_ptr, 12); + if (local_error != NO_ERROR) { + return local_error; + } + *product_id = sensirion_common_bytes_to_uint32_t(&buffer_ptr[0]); + sensirion_common_to_integer(&buffer_ptr[4], (uint8_t *)serial_number, LONG_INTEGER, 8); + return local_error; +} + +int stcc4_set_rht_compensation(uint16_t raw_temperature, uint16_t raw_humidity) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_SET_RHT_COMPENSATION_CMD_ID); + local_offset = + sensirion_i2c_add_uint16_t_to_buffer(buffer_ptr, local_offset, raw_temperature); + local_offset = sensirion_i2c_add_uint16_t_to_buffer(buffer_ptr, local_offset, raw_humidity); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(1); + return local_error; +} + +int stcc4_set_pressure_compensation_raw(uint16_t pressure) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer( + buffer_ptr, local_offset, STCC4_SET_PRESSURE_COMPENSATION_RAW_CMD_ID); + local_offset = sensirion_i2c_add_uint16_t_to_buffer(buffer_ptr, local_offset, pressure); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(1); + return local_error; +} + +int stcc4_perform_self_test(uint16_t *test_result) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_PERFORM_SELF_TEST_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(360); + local_error = sensirion_i2c_read_data_inplace(i2c_spec, buffer_ptr, 2); + if (local_error != NO_ERROR) { + return local_error; + } + *test_result = sensirion_common_bytes_to_uint16_t(&buffer_ptr[0]); + return local_error; +} + +int stcc4_perform_conditioning(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_PERFORM_CONDITIONING_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(22000); + return local_error; +} + +int stcc4_enter_sleep_mode(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_ENTER_SLEEP_MODE_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(2); + return local_error; +} + +int stcc4_exit_sleep_mode(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command8_to_buffer(buffer_ptr, local_offset, + STCC4_EXIT_SLEEP_MODE_CMD_ID); + sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + k_msleep(5); + return local_error; +} + +int stcc4_enable_testing_mode(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_ENABLE_TESTING_MODE_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + return local_error; +} + +int stcc4_disable_testing_mode(void) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_DISABLE_TESTING_MODE_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + return local_error; +} + +int stcc4_perform_factory_reset(uint16_t *factory_reset_result) +{ + int local_error = NO_ERROR; + uint8_t *buffer_ptr = communication_buffer; + uint16_t local_offset = 0; + + local_offset = sensirion_i2c_add_command16_to_buffer(buffer_ptr, local_offset, + STCC4_PERFORM_FACTORY_RESET_CMD_ID); + local_error = sensirion_i2c_write_data(i2c_spec, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + k_msleep(90); + local_error = sensirion_i2c_read_data_inplace(i2c_spec, buffer_ptr, 2); + if (local_error != NO_ERROR) { + return local_error; + } + *factory_reset_result = sensirion_common_bytes_to_uint16_t(&buffer_ptr[0]); + return local_error; +} + +float stcc4_signal_temperature(uint16_t raw_temperature) +{ + float temperature = 0.0; + + temperature = -45.0 + ((175.0 * raw_temperature) / 65535.0); + return temperature; +} + +float stcc4_signal_relative_humidity(uint16_t raw_relative_humidity) +{ + float relative_humidity = 0.0; + + relative_humidity = -6.0 + ((125.0 * raw_relative_humidity) / 65535.0); + return relative_humidity; +} + +int stcc4_read_measurement(int16_t *co2_concentration, float *temperature, float *relative_humidity, + uint16_t *sensor_status) +{ + int16_t raw_co2_concentration = 0; + uint16_t raw_temperature = 0; + uint16_t raw_relative_humidity = 0; + uint16_t sensor_status_raw = 0; + int local_error = 0; + + local_error = stcc4_read_measurement_raw(&raw_co2_concentration, &raw_temperature, + &raw_relative_humidity, &sensor_status_raw); + if (local_error != NO_ERROR) { + return local_error; + } + *co2_concentration = raw_co2_concentration; + *temperature = stcc4_signal_temperature(raw_temperature); + *relative_humidity = stcc4_signal_relative_humidity(raw_relative_humidity); + *sensor_status = sensor_status_raw; + return local_error; +} + +int stcc4_set_pressure_compensation(uint32_t pressure) +{ + int local_error = 0; + + local_error = stcc4_set_pressure_compensation_raw((uint32_t)(pressure / 2)); + if (local_error != NO_ERROR) { + return local_error; + } + return local_error; +} + +int stcc4_select_rht_compensation(uint16_t temperature_compensation, uint16_t humidity_compensation) +{ + int local_error = 0; + + if (temperature_compensation || humidity_compensation) { + local_error = + stcc4_set_rht_compensation(temperature_compensation, humidity_compensation); + if (local_error != NO_ERROR) { + return local_error; + } + } + return local_error; +} + +int stcc4_check_self_test(uint16_t *test_result) +{ + int local_error = 0; + + local_error = stcc4_perform_self_test(test_result); + return local_error; +} + +int stcc4_select_perform_conditioning(bool do_perform_conditioning) +{ + int local_error = 0; + + if (do_perform_conditioning) { + local_error = stcc4_perform_conditioning(); + if (local_error != NO_ERROR) { + return local_error; + } + } + return local_error; +} + +static int stcc4_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + struct stcc4_data *data = dev->data; + + if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_CO2 && chan != SENSOR_CHAN_HUMIDITY && + chan != SENSOR_CHAN_AMBIENT_TEMP) { + LOG_ERR("Channel not supported."); + return -ENOTSUP; + } + int ret = + stcc4_read_measurement_raw(&data->co2_concentration_raw, &data->temperature_raw, + &data->relative_humidity_raw, &data->sensor_status_raw); + if (ret < 0) { + LOG_ERR("Failed to sample fetch."); + return ret; + } + return 0; +}; + +static int stcc4_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct stcc4_data *data = dev->data; + int ret = 0; + + switch (chan) { + case SENSOR_CHAN_CO2: { + int16_t local_var = data->co2_concentration_raw; + + val->val1 = local_var; + val->val2 = 0; + } break; + case SENSOR_CHAN_HUMIDITY: { + float local_var = stcc4_signal_relative_humidity(data->relative_humidity_raw); + + ret = sensor_value_from_float(val, local_var); + } break; + case SENSOR_CHAN_AMBIENT_TEMP: { + float local_var = stcc4_signal_temperature(data->temperature_raw); + + ret = sensor_value_from_float(val, local_var); + } break; + default: + LOG_ERR("Channel not supported."); + return -ENOTSUP; + } + + if (ret != 0) { + LOG_ERR("Failed to convert value."); + return ret; + } + + return 0; +} + +int stcc4_init(const struct device *dev) +{ + int local_error = 0; + const struct stcc4_config *cfg = dev->config; + uint16_t result; + + i2c_spec = &cfg->bus; + + if (!i2c_is_ready_dt(&cfg->bus)) { + LOG_ERR("Device not ready."); + return -ENODEV; + } + + local_error = stcc4_stop_continuous_measurement(); + if (local_error != NO_ERROR) { + return local_error; + } + local_error = stcc4_check_self_test(&result); + if (result != 0) { + return -EIO; + } + if (local_error != NO_ERROR) { + return local_error; + } + local_error = stcc4_set_pressure_compensation(cfg->pressure); + if (local_error != NO_ERROR) { + return local_error; + } + local_error = stcc4_select_rht_compensation(cfg->temperature_compensation, + cfg->humidity_compensation); + if (local_error != NO_ERROR) { + return local_error; + } + local_error = stcc4_select_perform_conditioning(cfg->do_perform_conditioning); + if (local_error != NO_ERROR) { + return local_error; + } + local_error = stcc4_start_continuous_measurement(); + if (local_error != NO_ERROR) { + return local_error; + } + return local_error; +} + +static DEVICE_API(sensor, stcc4_api) = { + .sample_fetch = stcc4_sample_fetch, + .channel_get = stcc4_channel_get, +}; + +#define STCC4_INIT(inst) \ + static struct stcc4_data stcc4_data_##inst; \ + \ + static const struct stcc4_config stcc4_config_##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + .pressure = DT_INST_PROP(inst, pressure), \ + .humidity_compensation = DT_INST_PROP(inst, humidity_compensation), \ + .temperature_compensation = DT_INST_PROP(inst, temperature_compensation), \ + .do_perform_conditioning = DT_INST_PROP(inst, do_perform_conditioning), \ + }; \ + SENSOR_DEVICE_DT_INST_DEFINE(inst, stcc4_init, NULL, &stcc4_data_##inst, \ + &stcc4_config_##inst, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &stcc4_api); + +DT_INST_FOREACH_STATUS_OKAY(STCC4_INIT) diff --git a/drivers/sensor/sensirion/stcc4/stcc4.h b/drivers/sensor/sensirion/stcc4/stcc4.h new file mode 100644 index 0000000000000..eb79a6f278e48 --- /dev/null +++ b/drivers/sensor/sensirion/stcc4/stcc4.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_DRIVERS_SENSOR_STCC4_STCC4_H_ +#define ZEPHYR_DRIVERS_SENSOR_STCC4_STCC4_H_ + +#include +#include + +#define STCC4_I2C_ADDR_64 0x64 + +typedef enum { + STCC4_START_CONTINUOUS_MEASUREMENT_CMD_ID = 0x218b, + STCC4_READ_MEASUREMENT_RAW_CMD_ID = 0xec05, + STCC4_STOP_CONTINUOUS_MEASUREMENT_CMD_ID = 0x3f86, + STCC4_MEASURE_SINGLE_SHOT_CMD_ID = 0x219d, + STCC4_PERFORM_FORCED_RECALIBRATION_CMD_ID = 0x362f, + STCC4_GET_PRODUCT_ID_CMD_ID = 0x365b, + STCC4_SET_RHT_COMPENSATION_CMD_ID = 0xe000, + STCC4_SET_PRESSURE_COMPENSATION_RAW_CMD_ID = 0xe016, + STCC4_PERFORM_SELF_TEST_CMD_ID = 0x278c, + STCC4_PERFORM_CONDITIONING_CMD_ID = 0x29bc, + STCC4_ENTER_SLEEP_MODE_CMD_ID = 0x3650, + STCC4_EXIT_SLEEP_MODE_CMD_ID = 0x0, + STCC4_ENABLE_TESTING_MODE_CMD_ID = 0x3fbc, + STCC4_DISABLE_TESTING_MODE_CMD_ID = 0x3f3d, + STCC4_PERFORM_FACTORY_RESET_CMD_ID = 0x3632, +} STCC4_CMD_ID; + +struct stcc4_config { + struct i2c_dt_spec bus; + uint32_t pressure; + uint16_t humidity_compensation; + uint16_t temperature_compensation; + bool do_perform_conditioning; +}; + +struct stcc4_data { + int16_t co2_concentration_raw; + uint16_t temperature_raw; + uint16_t relative_humidity_raw; + uint16_t sensor_status_raw; +}; + +#endif /* ZEPHYR_DRIVERS_SENSOR_STCC4_STCC4_H_*/ diff --git a/dts/bindings/sensor/sensirion,stcc4.yaml b/dts/bindings/sensor/sensirion,stcc4.yaml new file mode 100644 index 0000000000000..3b9c29e4a12e0 --- /dev/null +++ b/dts/bindings/sensor/sensirion,stcc4.yaml @@ -0,0 +1,41 @@ +# Copyright (c) 2025, Sensirion AG +# SPDX-License-Identifier: Apache-2.0 + +compatible: "sensirion,stcc4" + +include: [sensor-device.yaml, i2c-device.yaml] + +description: | + The STCC4 is Sensirion's next generation miniature CO2 sensor for indoor air quality applications. + Learn more: https://sensirion.com/products/catalog/STCC4 + Supported I2C addresses: 0x64(default) + +properties: + pressure: + type: int + default: 101300 + description: | + Set the pressure compensation in pascal (Pa). + Default: 101'300 Pa + humidity-compensation: + type: int + default: 0 + description: | + By default this option is not set and the SHT4x sensor is used for compensation. + For manual compensation: + - Set a compensation value for temperature and humidity in ticks. + humidity ticks = (humidity + 6) * 65535 / 125 + temperature-compensation: + type: int + default: 0 + description: | + By default this option is not set and the SHT4x sensor is used for compensation. + For manual compensation: + - Set a compensation value for temperature and humidity in ticks. + temperature ticks = (temperature + 45) * 65535 / 175 + do-perform-conditioning: + type: boolean + description: | + The perform_conditioning command is recommended to improve sensor performance + when the sensor has not completed measurements for more than 3 hours. + Please note that conditioning takes 22 seconds. diff --git a/include/zephyr/drivers/sensor/stcc4.h b/include/zephyr/drivers/sensor/stcc4.h new file mode 100644 index 0000000000000..13004552090a4 --- /dev/null +++ b/include/zephyr/drivers/sensor/stcc4.h @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2025 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_STCC4_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_STCC4_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** + * @brief Performs a single shot measurement. + * + * The measure_single_shot command conducts an on-demand measurement of CO2 gas + * concentration. The typical communication sequence is as follows: 1. The + * sensor is powered up with the exit_sleep_mode command if previously powered + * down using the enter_sleep_mode command. 2. The I2C controller sends a + * measure_single_shot command and waits for the execution time. 3. The I2C + * controller reads out data with the read_measurement command. 4. Repeat steps + * 2-3 as required by the application. 5. If desired, set the sensor to sleep + * with the enter_sleep_mode command. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_measure_single_shot(void); + +/** + * @brief Perform a forced recalibration (FRC) of the CO₂ concentration. + * + * The forced recalibration (FRC) can be applied to correct the sensor's output + * by means of an externally obtained CO 2 reference value. The recommended + * communication sequence for a successful FRC in continuous mode is as follows: + * 1. Operate the STCC4 for at least 3 minutes. Ensure the sensor reading and + * environmental conditions, including CO2 concentration, are stable. 2. The I2C + * controller sends the stop_continuous_measurement command (Section 5.3.2) 3. + * Wait for the specified execution time of stop_continuous_measurement command. + * 4. Issue the perform_forced_recalibration command with the target CO2 + * concentration and read out the applied FRC correction. + * + * The recommended communication sequence for a successful FRC in single shot + * mode is as follows: 1. Operate the STCC4 for at least 3 minutes. For sampling + * intervals significantly larger than 10 seconds, increase the operation time + * accordingly. Ensure the sensor reading and environmental conditions, + * including CO2 concentration, are stable. 2. Issue the + * perform_forced_recalibration command with the reference CO2 concentration and + * read out the applied FRC correction. The sensor must remain in idle mode + * after operation before command execution. + * + * @param[in] target_co2_concentration Target CO₂ concentration in ppm. + * @param[out] frc_correction Returns the FRC correction value if FRC has been + * successful. 0xFFFF on failure. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_perform_forced_recalibration(int16_t target_co2_concentration, int16_t *frc_correction); + +/** + * @brief Read the sensor's 32-bit product id and 64-bit serial number. + * + * The get_product_ID command retrieves the product identifier and serial + * number. The command can be used to check communication with the sensor. + * + * @param[out] product_id 32-bit + * @param[out] serial_number 64-bit unique serial number of the sensor. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_get_product_id(uint32_t *product_id, uint64_t *serial_number); + +/** + * @brief Conditions the sensor with a set operation profile. + * + * The perform_conditioning command is recommended to improve sensor performance + * when the sensor has not completed measurements for more than 3 hours. It is + * recommended to allow a settling time of 2 seconds after the execution time is + * complete. The recommended communication sequence is as follows: 1. The sensor + * is woken up with the exit_sleep_mode command if previously put to sleep using + * the enter_sleep_mode command. 2. The I2C controller sends a + * perform_conditioning command. 3. Wait for the specified execution time of + * perform_conditioning command plus a 2 second settling time. 4. The I2C + * controller sends a measurement command (e.g. start_continuous_measurement + * command or measure_single_shot command. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_perform_conditioning(void); + +/** + * @brief Sets the sensor from idle mode into sleep mode. + * + * The enter_sleep_mode command sets the sensor to sleep mode through the I2C + * interface. The written relative humidity, temperature, and pressure + * compensation values as well as the ASC state will be retained while in sleep + * mode. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_enter_sleep_mode(void); + +/** + * @brief The sensor is set from sleep mode into idle mode when it receives the + * valid I²C address and a write bit (‘0’). + * + * The exit_sleep_mode command sets the sensor to idle mode through the I2C + * interface. The sensor's idle state can be verified by reading out the product + * ID. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_exit_sleep_mode(void); + +/** + * @brief Enable the sensor testing mode. + * + * The enable_testing_mode command is used to test the sensor with the ASC + * algorithm disabled. The sensor state can be verified by reading out the + * sensor status word in the read_measurement command. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_enable_testing_mode(void); + +/** + * @brief Disable the sensor testing mode. + * + * The disable_testing_mode command is used to exit the testing mode. The sensor + * state can be verified by reading out the sensor status word in the + * read_measurement command. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_disable_testing_mode(void); + +/** + * @brief Reset the FRC and ASC algorithm history. + * + * The perform_factory_reset command can be used to reset the FRC and ASC + * algorithm history. + * + * @param[out] factory_reset_result The result of the factory reset. If the + * result is ≠ 0, the factory reset failed. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_perform_factory_reset(uint16_t *factory_reset_result); + +/** + * @brief Set the pressure compensation in Pa + * + * External pressure values can be set through the set_pressure_compensation + * command. The written pressure value is applied for pressure compensation + * after a maximum of one measurement interval. Power cycling resets the sensor + * to the default value of 101'300 Pa. + * + * @param[in] pressure + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_set_pressure_compensation(uint32_t pressure); + +/** + * @brief Start a continuous measurement (interval 1 s). + * + * After sending the start_continuous_measurement command, the sensor will begin + * measuring the CO2 gas concentration continuously with a sampling interval of + * 1 second. The recommended communication sequence for continuous measurement + * is as follows: 1. The sensor is powered up into the idle state. 2. The I2C + * controller sends a start_continuous_measurement command. 3. The I2C + * controller periodically reads out data with the read_measurement command. 4. + * If desired, stop taking measurements using the stop_continuous_measurement + * command. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_start_continuous_measurement(void); + +/** + * @brief The command stops the continuous measurement and puts the sensor into + * idle mode. + * + * After receiving the stop_continuous_measurement command, the sensor will + * finish the currently running measurement before returning to idle mode. + * Therefore, a wait time of one measurement interval plus a 200 ms clock + * tolerance is required before a new command is acknowledged. + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_stop_continuous_measurement(void); + +/** + * @brief stcc4_read_measurement + * + * reads measurement data + * + * @param[out] co2_concentration + * @param[out] temperature + * @param[out] relative_humidity + * @param[out] sensor_status + * + * @return error_code 0 on success, an error code otherwise. + */ +int stcc4_read_measurement(int16_t *co2_concentration, float *temperature, float *relative_humidity, + uint16_t *sensor_status); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_STCC4_H_ */ diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index b7bf34f0764af..f0a696fedf74f 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -593,6 +593,7 @@ test_i2c_fdc2x1x: fdc2x1x@54 { sd-gpios = <&test_gpio 0 0>; deglitch = <5>; fref = <43360>; + channel_0 { rcount = <7499>; settlecount = <48>; @@ -1444,3 +1445,8 @@ test_i2c_wsen_pdms_25131308XXX05: wsen_pdms_25131308XXX05@c0 { reg = <0xc0>; sensor-type = <4>; }; + +test_i2c_stcc4: stcc4@c1 { + compatible = "sensirion,stcc4"; + reg = <0xc1>; +};