diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 0bace9305b9..ff1cf9ec4eb 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -36,6 +36,7 @@ add_subdirectory_ifdef(CONFIG_DPS310 dps310) add_subdirectory_ifdef(CONFIG_DS18B20 ds18b20) add_subdirectory_ifdef(CONFIG_ENS210 ens210) add_subdirectory_ifdef(CONFIG_ESP32_TEMP esp32_temp) +add_subdirectory_ifdef(CONFIG_EXPLORIR_M explorir_m) add_subdirectory_ifdef(CONFIG_F75303 f75303) add_subdirectory_ifdef(CONFIG_FDC2X1X fdc2x1x) add_subdirectory_ifdef(CONFIG_FXAS21002 fxas21002) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index e334617cf0b..55ef3ffec51 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -109,6 +109,7 @@ source "drivers/sensor/dps310/Kconfig" source "drivers/sensor/ds18b20/Kconfig" source "drivers/sensor/ens210/Kconfig" source "drivers/sensor/esp32_temp/Kconfig" +source "drivers/sensor/explorir_m/Kconfig" source "drivers/sensor/f75303/Kconfig" source "drivers/sensor/fdc2x1x/Kconfig" source "drivers/sensor/fxas21002/Kconfig" diff --git a/drivers/sensor/explorir_m/CMakeLists.txt b/drivers/sensor/explorir_m/CMakeLists.txt new file mode 100644 index 00000000000..71125b505f3 --- /dev/null +++ b/drivers/sensor/explorir_m/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(explorir_m.c) diff --git a/drivers/sensor/explorir_m/Kconfig b/drivers/sensor/explorir_m/Kconfig new file mode 100644 index 00000000000..02806bf82a8 --- /dev/null +++ b/drivers/sensor/explorir_m/Kconfig @@ -0,0 +1,13 @@ +# ExplorIR-M CO2 sensor configuration options + +# Copyright (c) 2023, Vitrolife A/S +# SPDX-License-Identifier: Apache-2.0 + +config EXPLORIR_M + bool "ExplorIR-M CO2 Sensor" + default y + depends on DT_HAS_GSS_EXPLORIR_M_ENABLED + depends on UART_INTERRUPT_DRIVEN + select UART + help + Enable driver for ExplorIR-M CO2 Sensor. diff --git a/drivers/sensor/explorir_m/explorir_m.c b/drivers/sensor/explorir_m/explorir_m.c new file mode 100644 index 00000000000..058c43faf2b --- /dev/null +++ b/drivers/sensor/explorir_m/explorir_m.c @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2023, Vitrolife A/S + * + * SPDX-License-Identifier: Apache-2.0 + * + * Datasheet: + * https://www.gassensing.co.uk/wp-content/uploads/2023/05/ExplorIR-M-Data-Sheet-Rev-4.13_3.pdf + * + */ + +#define DT_DRV_COMPAT gss_explorir_m + +#include + +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(explorir_m_sensor, CONFIG_SENSOR_LOG_LEVEL); + +#define EXPLORIR_M_BEGIN_CHAR ' ' + +#define EXPLORIR_M_SET_FILTER_CHAR 'A' +#define EXPLORIR_M_GET_FILTER_CHAR 'a' +#define EXPLORIR_M_MODE_CHAR 'K' +#define EXPLORIR_M_CO2_FILTERED_CHAR 'Z' +#define EXPLORIR_M_SCALING_CHAR '.' +#define EXPLORIR_M_NOT_RECOGNISED_CHAR '?' + +#define EXPLORIR_M_SEPARATOR_CHAR ' ' +#define EXPLORIR_M_PRE_END_CHAR '\r' +#define EXPLORIR_M_END_CHAR '\n' + +#define EXPLORIR_M_TYPE_INDEX 1 +#define EXPLORIR_M_VALUE_INDEX 3 + +#define EXPLORIR_M_BUFFER_LENGTH 16 + +#define EXPLORIR_M_MAX_RESPONSE_DELAY 200 /* Add margin to the specified 100 in datasheet */ +#define EXPLORIR_M_CO2_VALID_DELAY 1200 + +struct explorir_m_data { + struct k_mutex uart_mutex; + struct k_sem uart_rx_sem; + uint16_t filtered; + uint16_t scaling; + uint8_t read_index; + uint8_t read_buffer[EXPLORIR_M_BUFFER_LENGTH]; +}; + +struct explorir_m_cfg { + const struct device *uart_dev; + uart_irq_callback_user_data_t cb; +}; + +enum explorir_m_uart_set_usage { + EXPLORIR_M_SET_NONE, + EXPLORIR_M_SET_VAL_ONE, + EXPLORIR_M_SET_VAL_ONE_TWO, +}; + +enum EXPLORIR_M_MODE { + EXPLORIR_M_MODE_COMMAND, + EXPLORIR_M_MODE_STREAM, + EXPLORIR_M_MODE_POLL, +}; + +static void explorir_m_uart_flush(const struct device *uart_dev) +{ + uint8_t tmp; + + while (uart_fifo_read(uart_dev, &tmp, 1) > 0) { + continue; + } +} + +static void explorir_m_uart_flush_until_end(const struct device *uart_dev) +{ + uint8_t tmp; + uint32_t uptime; + + uptime = k_uptime_get_32(); + do { + uart_poll_in(uart_dev, &tmp); + } while (tmp != EXPLORIR_M_END_CHAR && + k_uptime_get_32() - uptime < EXPLORIR_M_MAX_RESPONSE_DELAY); +} + +static void explorir_m_buffer_reset(struct explorir_m_data *data) +{ + memset(data->read_buffer, 0, data->read_index); + data->read_index = 0; +} + +static int explorir_m_buffer_verify(const struct explorir_m_data *data, char type) +{ + char buffer_type = data->read_buffer[EXPLORIR_M_TYPE_INDEX]; + + if (data->read_buffer[0] == EXPLORIR_M_NOT_RECOGNISED_CHAR) { + LOG_WRN("Sensor did not recognise the command"); + return -EIO; + } + + if (buffer_type != type) { + LOG_WRN("Expected type %c but got %c", type, buffer_type); + return -EIO; + } + + if (data->read_buffer[0] != EXPLORIR_M_BEGIN_CHAR || + data->read_buffer[2] != EXPLORIR_M_SEPARATOR_CHAR || + data->read_buffer[data->read_index - 2] != EXPLORIR_M_PRE_END_CHAR) { + LOG_HEXDUMP_WRN(data->read_buffer, data->read_index, "Invalid buffer"); + return -EIO; + } + + return 0; +} + +static int explorir_m_buffer_process(struct explorir_m_data *data, char type, + struct sensor_value *val) +{ + if (explorir_m_buffer_verify(data, type) != 0) { + return -EIO; + } + + switch (type) { + case EXPLORIR_M_SET_FILTER_CHAR: + case EXPLORIR_M_MODE_CHAR: + break; + + case EXPLORIR_M_CO2_FILTERED_CHAR: + data->scaling = strtol(&data->read_buffer[EXPLORIR_M_VALUE_INDEX], NULL, 10); + break; + + case EXPLORIR_M_SCALING_CHAR: + data->filtered = strtol(&data->read_buffer[EXPLORIR_M_VALUE_INDEX], NULL, 10); + break; + + case EXPLORIR_M_GET_FILTER_CHAR: + val->val1 = strtol(&data->read_buffer[EXPLORIR_M_VALUE_INDEX], NULL, 10); + break; + + default: + LOG_ERR("Unknown type %c/0x%02x", type, type); + return -EIO; + } + + return 0; +} + +static void explorir_m_uart_isr(const struct device *uart_dev, void *user_data) +{ + const struct device *dev = user_data; + struct explorir_m_data *data = dev->data; + int rc, read_len; + + if (!device_is_ready(uart_dev)) { + LOG_DBG("UART device is not ready"); + return; + } + + if (!uart_irq_update(uart_dev)) { + LOG_DBG("Unable to process interrupts"); + return; + } + + if (!uart_irq_rx_ready(uart_dev)) { + LOG_DBG("No RX data"); + return; + } + + read_len = EXPLORIR_M_BUFFER_LENGTH - data->read_index; + rc = uart_fifo_read(uart_dev, &data->read_buffer[data->read_index], read_len); + + if (rc < 0 || rc == read_len) { + LOG_ERR("UART read failed: %d", rc < 0 ? rc : -ERANGE); + explorir_m_uart_flush(uart_dev); + LOG_HEXDUMP_WRN(data->read_buffer, data->read_index, "Discarding"); + explorir_m_buffer_reset(data); + } else { + data->read_index += rc; + + if (data->read_buffer[data->read_index - 1] != EXPLORIR_M_END_CHAR) { + return; + } + } + + k_sem_give(&data->uart_rx_sem); +} + +static void explorir_m_uart_terminate(const struct device *uart_dev) +{ + uart_poll_out(uart_dev, EXPLORIR_M_PRE_END_CHAR); + uart_poll_out(uart_dev, EXPLORIR_M_END_CHAR); +} + +static int explorir_m_await_receive(struct explorir_m_data *data) +{ + int rc = k_sem_take(&data->uart_rx_sem, K_MSEC(EXPLORIR_M_MAX_RESPONSE_DELAY)); + + /* Reset semaphore if sensor did not respond within maximum specified response time */ + if (rc == -EAGAIN) { + k_sem_reset(&data->uart_rx_sem); + } + + return rc; +} + +static int explorir_m_uart_transceive(const struct device *dev, char type, struct sensor_value *val, + enum explorir_m_uart_set_usage set) +{ + const struct explorir_m_cfg *cfg = dev->config; + struct explorir_m_data *data = dev->data; + char buf[EXPLORIR_M_BUFFER_LENGTH]; + int rc, len; + + if (val == NULL && set != EXPLORIR_M_SET_NONE) { + LOG_ERR("val is NULL but set is not NONE"); + return -EINVAL; + } + + k_mutex_lock(&data->uart_mutex, K_FOREVER); + + explorir_m_buffer_reset(data); + + uart_poll_out(cfg->uart_dev, type); + + if (set == EXPLORIR_M_SET_VAL_ONE) { + len = snprintf(buf, EXPLORIR_M_BUFFER_LENGTH, "%c%u", EXPLORIR_M_SEPARATOR_CHAR, + val->val1); + } else if (set == EXPLORIR_M_SET_VAL_ONE_TWO) { + len = snprintf(buf, EXPLORIR_M_BUFFER_LENGTH, "%c%u%c%u", EXPLORIR_M_SEPARATOR_CHAR, + val->val1, EXPLORIR_M_SEPARATOR_CHAR, val->val2); + } else { + len = 0; + } + + if (len == EXPLORIR_M_BUFFER_LENGTH) { + LOG_WRN("Set value truncated"); + } + for (int i = 0; i != len; i++) { + uart_poll_out(cfg->uart_dev, buf[i]); + } + + explorir_m_uart_terminate(cfg->uart_dev); + + rc = explorir_m_await_receive(data); + if (rc != 0) { + LOG_WRN("%c did not receive a response: %d", type, rc); + } + + if (rc == 0) { + rc = explorir_m_buffer_process(data, type, val); + } + + k_mutex_unlock(&data->uart_mutex); + + return rc; +} + +static int explorir_m_attr_get(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, struct sensor_value *val) +{ + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_EXPLORIR_M_FILTER: + return explorir_m_uart_transceive(dev, EXPLORIR_M_GET_FILTER_CHAR, val, + EXPLORIR_M_SET_NONE); + default: + return -ENOTSUP; + } +} + +static int explorir_m_attr_set(const struct device *dev, enum sensor_channel chan, + enum sensor_attribute attr, const struct sensor_value *val) +{ + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_EXPLORIR_M_FILTER: + if (val->val1 < 0 || val->val1 > 255) { + return -ERANGE; + } + return explorir_m_uart_transceive(dev, EXPLORIR_M_SET_FILTER_CHAR, + (struct sensor_value *)val, + EXPLORIR_M_SET_VAL_ONE); + default: + return -ENOTSUP; + } +} + +static int explorir_m_sample_fetch(const struct device *dev, enum sensor_channel chan) +{ + if (chan != SENSOR_CHAN_CO2 && chan != SENSOR_CHAN_ALL) { + return -ENOTSUP; + } + + return explorir_m_uart_transceive(dev, EXPLORIR_M_CO2_FILTERED_CHAR, NULL, + EXPLORIR_M_SET_NONE); +} + +static int explorir_m_channel_get(const struct device *dev, enum sensor_channel chan, + struct sensor_value *val) +{ + struct explorir_m_data *data = dev->data; + + if (chan != SENSOR_CHAN_CO2) { + return -ENOTSUP; + } + + if (k_uptime_get() < EXPLORIR_M_CO2_VALID_DELAY) { + return -EAGAIN; + } + + val->val1 = data->filtered * data->scaling; + val->val2 = 0; + + return 0; +} + +static const struct sensor_driver_api explorir_m_api_funcs = { + .attr_set = explorir_m_attr_set, + .attr_get = explorir_m_attr_get, + .sample_fetch = explorir_m_sample_fetch, + .channel_get = explorir_m_channel_get, +}; + +static int explorir_m_init(const struct device *dev) +{ + const struct explorir_m_cfg *cfg = dev->config; + struct explorir_m_data *data = dev->data; + struct sensor_value val; + int rc; + + LOG_DBG("Initializing %s", dev->name); + + if (!device_is_ready(cfg->uart_dev)) { + return -ENODEV; + } + + k_mutex_init(&data->uart_mutex); + k_sem_init(&data->uart_rx_sem, 0, 1); + + uart_irq_rx_disable(cfg->uart_dev); + uart_irq_tx_disable(cfg->uart_dev); + + rc = uart_irq_callback_user_data_set(cfg->uart_dev, cfg->cb, (void *)dev); + if (rc != 0) { + LOG_ERR("UART IRQ setup failed: %d", rc); + return rc; + } + + /* Terminate garbled tx due to GPIO setup or crash during unfinished send */ + explorir_m_uart_terminate(cfg->uart_dev); + explorir_m_uart_flush_until_end(cfg->uart_dev); + + uart_irq_rx_enable(cfg->uart_dev); + + val.val1 = EXPLORIR_M_MODE_POLL; + explorir_m_uart_transceive(dev, EXPLORIR_M_MODE_CHAR, &val, EXPLORIR_M_SET_VAL_ONE); + explorir_m_uart_transceive(dev, EXPLORIR_M_SCALING_CHAR, NULL, EXPLORIR_M_SET_NONE); + + return rc; +} + +#define EXPLORIR_M_INIT(n) \ + \ + static struct explorir_m_data explorir_m_data_##n; \ + \ + static const struct explorir_m_cfg explorir_m_cfg_##n = { \ + .uart_dev = DEVICE_DT_GET(DT_INST_BUS(n)), \ + .cb = explorir_m_uart_isr, \ + }; \ + \ + SENSOR_DEVICE_DT_INST_DEFINE(n, explorir_m_init, NULL, &explorir_m_data_##n, \ + &explorir_m_cfg_##n, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &explorir_m_api_funcs); + +DT_INST_FOREACH_STATUS_OKAY(EXPLORIR_M_INIT) diff --git a/include/zephyr/drivers/sensor/explorir_m.h b/include/zephyr/drivers/sensor/explorir_m.h new file mode 100644 index 00000000000..efef562447c --- /dev/null +++ b/include/zephyr/drivers/sensor/explorir_m.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023, Vitrolife A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SENSOR_EXPLORIR_M_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SENSOR_EXPLORIR_M_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +enum sensor_attribute_explorir_m { + /* Sensor integrated low-pass filter. Values 16, 32, 64, and 128 is allowed */ + SENSOR_ATTR_EXPLORIR_M_FILTER = SENSOR_ATTR_PRIV_START, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_DRIVERS_SENSOR_EXPLORIR_M_H_ */