From 0eeada72d7f415a993d89688daf7f44500152298 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 28 Jun 2021 14:39:06 +0100 Subject: [PATCH] New driver for SCD4X series CO2 sensors Submodule Sensirion's embedded-i2c-scd4x driver Add i2c_hal.cpp to binds it to Pimoroni::I2C Port (loosely) scd4x_i2c_example_usage.c to Pico --- .gitmodules | 3 + drivers/CMakeLists.txt | 1 + drivers/scd4x/CMakeLists.txt | 1 + drivers/scd4x/i2c_hal.cpp | 122 +++++++++++++++++++++++++ drivers/scd4x/scd4x.cmake | 15 +++ drivers/scd4x/scd4x.cpp | 0 drivers/scd4x/sensirion_i2c_hal.h | 113 +++++++++++++++++++++++ drivers/scd4x/src | 1 + examples/CMakeLists.txt | 1 + examples/breakout_scd41/CMakeLists.txt | 16 ++++ examples/breakout_scd41/scd41_demo.cpp | 101 ++++++++++++++++++++ 11 files changed, 374 insertions(+) create mode 100644 drivers/scd4x/CMakeLists.txt create mode 100644 drivers/scd4x/i2c_hal.cpp create mode 100644 drivers/scd4x/scd4x.cmake create mode 100644 drivers/scd4x/scd4x.cpp create mode 100644 drivers/scd4x/sensirion_i2c_hal.h create mode 160000 drivers/scd4x/src create mode 100644 examples/breakout_scd41/CMakeLists.txt create mode 100644 examples/breakout_scd41/scd41_demo.cpp diff --git a/.gitmodules b/.gitmodules index 07595540..87e37f06 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ path = drivers/bmp280/src url = https://github.com/pimoroni/BMP280_driver branch = patch-intf-ptr +[submodule "drivers/scd4x/src"] + path = drivers/scd4x/src + url = https://github.com/Sensirion/embedded-i2c-scd4x diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index c8e4cbb8..5783e3b8 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -23,4 +23,5 @@ add_subdirectory(button) add_subdirectory(plasma) add_subdirectory(rgbled) add_subdirectory(icp10125) +add_subdirectory(scd4x) add_subdirectory(hub75) diff --git a/drivers/scd4x/CMakeLists.txt b/drivers/scd4x/CMakeLists.txt new file mode 100644 index 00000000..61515f73 --- /dev/null +++ b/drivers/scd4x/CMakeLists.txt @@ -0,0 +1 @@ +include(scd4x.cmake) \ No newline at end of file diff --git a/drivers/scd4x/i2c_hal.cpp b/drivers/scd4x/i2c_hal.cpp new file mode 100644 index 00000000..5559212d --- /dev/null +++ b/drivers/scd4x/i2c_hal.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2018, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "sensirion_i2c_hal.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +#define SCD4X_I2C_ADDRESS 98 + +pimoroni::I2C *__i2c = nullptr; + +/* + * INSTRUCTIONS + * ============ + * + * Implement all functions where they are marked as IMPLEMENT. + * Follow the function specification in the comments. + */ + +/** + * Select the current i2c bus by index. + * All following i2c operations will be directed at that bus. + * + * THE IMPLEMENTATION IS OPTIONAL ON SINGLE-BUS SETUPS (all sensors on the same + * bus) + * + * @param bus_idx Bus index to select + * @returns 0 on success, an error code otherwise + */ +int16_t sensirion_i2c_hal_select_bus(uint8_t bus_idx) { + /* TODO:IMPLEMENT or leave empty if all sensors are located on one single + * bus + */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(pimoroni::I2C *i2c) { + __i2c = i2c; +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + /* TODO:IMPLEMENT or leave empty if no resources need to be freed */ +} + +/** + * Execute one read transaction on the I2C bus, reading a given number of bytes. + * If the device does not acknowledge the read command, an error shall be + * returned. + * + * @param address 7-bit I2C address to read from + * @param data pointer to the buffer where the data is to be stored + * @param count number of bytes to read from I2C and store in the buffer + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count) { + int result = __i2c->read_blocking(address, data, count, false); + return result >= 0 ? 0 : result; +} + +/** + * Execute one write transaction on the I2C bus, sending a given number of + * bytes. The bytes in the supplied buffer must be sent to the given address. If + * the slave device does not acknowledge any of the bytes, an error shall be + * returned. + * + * @param address 7-bit I2C address to write to + * @param data pointer to the buffer containing the data to write + * @param count number of bytes to read from the buffer and send over I2C + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, + uint16_t count) { + int result = __i2c->write_blocking(address, data, count, false); + return result >= 0 ? 0 : result; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * Despite the unit, a <10 millisecond precision is sufficient. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + sleep_us(useconds); +} diff --git a/drivers/scd4x/scd4x.cmake b/drivers/scd4x/scd4x.cmake new file mode 100644 index 00000000..22e7e574 --- /dev/null +++ b/drivers/scd4x/scd4x.cmake @@ -0,0 +1,15 @@ +set(DRIVER_NAME scd4x) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/scd4x_i2c.c + ${CMAKE_CURRENT_LIST_DIR}/src/sensirion_common.c + ${CMAKE_CURRENT_LIST_DIR}/src/sensirion_i2c.c + ${CMAKE_CURRENT_LIST_DIR}/i2c_hal.cpp + ${CMAKE_CURRENT_LIST_DIR}/scd4x.cpp +) + +target_link_libraries(${DRIVER_NAME} INTERFACE pimoroni_i2c hardware_i2c) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src) \ No newline at end of file diff --git a/drivers/scd4x/scd4x.cpp b/drivers/scd4x/scd4x.cpp new file mode 100644 index 00000000..e69de29b diff --git a/drivers/scd4x/sensirion_i2c_hal.h b/drivers/scd4x/sensirion_i2c_hal.h new file mode 100644 index 00000000..14d8a022 --- /dev/null +++ b/drivers/scd4x/sensirion_i2c_hal.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2018, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SENSIRION_I2C_HAL_H +#define SENSIRION_I2C_HAL_H + +#include "sensirion_config.h" +#include "common/pimoroni_i2c.hpp" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Select the current i2c bus by index. + * All following i2c operations will be directed at that bus. + * + * THE IMPLEMENTATION IS OPTIONAL ON SINGLE-BUS SETUPS (all sensors on the same + * bus) + * + * @param bus_idx Bus index to select + * @returns 0 on success, an error code otherwise + */ +int16_t sensirion_i2c_hal_select_bus(uint8_t bus_idx); + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(pimoroni::I2C *_i2c); + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void); + +/** + * Execute one read transaction on the I2C bus, reading a given number of bytes. + * If the device does not acknowledge the read command, an error shall be + * returned. + * + * @param address 7-bit I2C address to read from + * @param data pointer to the buffer where the data is to be stored + * @param count number of bytes to read from I2C and store in the buffer + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint16_t count); + +/** + * Execute one write transaction on the I2C bus, sending a given number of + * bytes. The bytes in the supplied buffer must be sent to the given address. If + * the slave device does not acknowledge any of the bytes, an error shall be + * returned. + * + * @param address 7-bit I2C address to write to + * @param data pointer to the buffer containing the data to write + * @param count number of bytes to read from the buffer and send over I2C + * @returns 0 on success, error code otherwise + */ +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, + uint16_t count); + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * When using hardware i2c: + * Despite the unit, a <10 millisecond precision is sufficient. + * + * When using software i2c: + * The precision needed depends on the desired i2c frequency, i.e. should be + * exact to about half a clock cycle (defined in + * `SENSIRION_I2C_CLOCK_PERIOD_USEC` in `sensirion_sw_i2c_gpio.h`). + * + * Example with 400kHz requires a precision of 1 / (2 * 400kHz) == 1.25usec. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SENSIRION_I2C_HAL_H */ diff --git a/drivers/scd4x/src b/drivers/scd4x/src new file mode 160000 index 00000000..bcbb2190 --- /dev/null +++ b/drivers/scd4x/src @@ -0,0 +1 @@ +Subproject commit bcbb2190324d9d39ae38619dd4a8931a0b8cf049 diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index e44f6b32..90b6025c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,6 +20,7 @@ add_subdirectory(breakout_bme280) add_subdirectory(breakout_as7262) add_subdirectory(breakout_bh1745) add_subdirectory(breakout_icp10125) +add_subdirectory(breakout_scd41) add_subdirectory(pico_display) add_subdirectory(pico_display_2) diff --git a/examples/breakout_scd41/CMakeLists.txt b/examples/breakout_scd41/CMakeLists.txt new file mode 100644 index 00000000..af6c3ecb --- /dev/null +++ b/examples/breakout_scd41/CMakeLists.txt @@ -0,0 +1,16 @@ +set(OUTPUT_NAME scd41_demo) + +add_executable( + ${OUTPUT_NAME} + scd41_demo.cpp +) + +# enable usb output, disable uart output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) +pico_enable_stdio_uart(${OUTPUT_NAME} 1) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib scd4x) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_scd41/scd41_demo.cpp b/examples/breakout_scd41/scd41_demo.cpp new file mode 100644 index 00000000..1486f2e6 --- /dev/null +++ b/examples/breakout_scd41/scd41_demo.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2021, Sensirion AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Sensirion AG nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include // printf + +#include "scd4x_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c_hal.h" + +#include "pico/stdlib.h" +#include "common/pimoroni_i2c.hpp" + +using namespace pimoroni; + +I2C i2c(BOARD::BREAKOUT_GARDEN); + +/** + * TO USE CONSOLE OUTPUT (PRINTF) IF NOT PRESENT ON YOUR PLATFORM + */ +//#define printf(...) + +int main(void) { + stdio_init_all(); + int16_t error = 0; + + sensirion_i2c_hal_init(&i2c); + + // Clean up potential SCD40 states + scd4x_wake_up(); + scd4x_stop_periodic_measurement(); + scd4x_reinit(); + + uint16_t serial_0; + uint16_t serial_1; + uint16_t serial_2; + error = scd4x_get_serial_number(&serial_0, &serial_1, &serial_2); + if (error) { + printf("Error executing scd4x_get_serial_number(): %i\n", error); + } else { + printf("serial: 0x%04x%04x%04x\n", serial_0, serial_1, serial_2); + } + + // Start Measurement + + error = scd4x_start_periodic_measurement(); + if (error) { + printf("Error executing scd4x_start_periodic_measurement(): %i\n", + error); + } + + printf("Waiting for first measurement... (5 sec)\n"); + + for (;;) { + // Read Measurement + sensirion_i2c_hal_sleep_usec(5000000); + + uint16_t co2; + int32_t temperature; + int32_t humidity; + error = scd4x_read_measurement(&co2, &temperature, &humidity); + if (error) { + printf("Error executing scd4x_read_measurement(): %i\n", error); + } else if (co2 == 0) { + printf("Invalid sample detected, skipping.\n"); + } else { + printf("CO2: %u\n", co2); + printf("Temperature: %ld m°C\n", temperature); + printf("Humidity: %ld mRH\n", humidity); + } + } + + return 0; +}