diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..047f2ad --- /dev/null +++ b/.clang-format @@ -0,0 +1,14 @@ +--- +Language: Cpp +BasedOnStyle: LLVM +IndentWidth: 4 +AlignAfterOpenBracket: Align +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +IndentCaseLabels: true +SpacesBeforeTrailingComments: 2 +PointerAlignment: Left +AlignEscapedNewlines: Left +ForEachMacros: ['TEST_GROUP', 'TEST'] +... diff --git a/.github/workflows/embedded_quality_check.yml b/.github/workflows/embedded_quality_check.yml new file mode 100644 index 0000000..95e97ee --- /dev/null +++ b/.github/workflows/embedded_quality_check.yml @@ -0,0 +1,21 @@ +name: Quality check + +on: + pull_request: + branches: + - main + push: + branches: + - main + +jobs: + driver-quality: + uses: sensirion/.github/.github/workflows/driver.c.check.yml@main + + todo-check: + if: github.event_name == 'push' && github.ref != 'refs/head/main' + uses: sensirion/.github/.github/workflows/driver.common.todo_check.yml@main + + code-generation-check: + if: github.event_name == 'push' && github.ref != 'refs/head/main' + uses: sensirion/.github/.github/workflows/driver.generated.metadata_check.yml@main diff --git a/.github/workflows/github_release.yml b/.github/workflows/github_release.yml new file mode 100644 index 0000000..d98ee0e --- /dev/null +++ b/.github/workflows/github_release.yml @@ -0,0 +1,10 @@ +name: Github Release + +on: + push: + tags: + - '*' + +jobs: + github-release: + uses: sensirion/.github/.github/workflows/driver.common.github_release.yml@main diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b333179 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/example-usage/sen66_i2c_example_usage +/tests/sen66_test_hw_i2c +/tests/sen66_test_sw_i2c diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d1e7060 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# CHANGELOG + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.1.0] - 2024-10-31 + +### Added + +- Add product picture +- Add interfaces to start, stop and read measurements. +- Add interfaces to read product name, serial number and version + +[Unreleased]: https://github.com/Sensirion/embedded-i2c-sen66/compare/0.1.0...HEAD +[0.1.0]: https://github.com/Sensirion/embedded-i2c-sen66/releases/tag/0.1.0 \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1daaf92 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2024, 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: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. 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. + +3. Neither the name of the copyright holder 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..65d8f71 --- /dev/null +++ b/README.md @@ -0,0 +1,170 @@ +# Sensirion I2C SEN66 embedded Library + +This document explains how to set up a SEN66 sensor to run on an embedded device using the I²C interface. + + + +Click [here](https://sensirion.com/sen6x-air-quality-sensor-platform) to learn more about the Sensirion SEN66 sensor. + + +unknown + + +The default I²C address of [SEN66](https://www.sensirion.com/products/catalog/SEN6x) is **0x6B**. + + + +## Setup Guide + +### Connecting the Sensor + +Your sensor has 4 different signals that need to be connected to your board: VDD, GND, SDA, SCL. +Use the following pins to connect your SEN66: + + + +| *Pin* | *Cable Color* | *Name* | *Description* | *Comments* | +|-------|---------------|:------:|----------------|------------| +| 1 | red | VDD | Supply Voltage | 3.3V ±10% +| 2 | black | GND | Ground | +| 3 | green | SDA | I2C: Serial data input / output | TTL 5V compatible +| 4 | yellow | SCL | I2C: Serial clock input | TTL 5V compatible +| 5 | | NC | Do not connect | +| 6 | | NC | Do not connect | + + + +The recommended voltage is 3.3V. + +### Configure the code + +In order to use the provided code we need to adjust two files according to your platform. + +### Edit `sensirion_i2c_hal.c` + +This file contains the implementation of the sensor communication, which +depends on your hardware platform. We provide function stubs for your +hardware's own implementation. +Sample implementations are available for some platforms: +[`sample-implementations`](sample-implementations). For Linux based platforms +like Raspberry Pi you can just replace the unimplemented HAL template with the +implementation in `sample-implementations/linux_user_space/`: + +``` +cp sample-implementations/linux_user_space/sensirion_i2c_hal.c ./ +``` + +### Edit `sensirion_config.h` + +Skip this part for Linux based platforms since everything is already setup for +this case. + +Otherwise you need to check if the libraries `` and `` are +provided by your toolchain, compiler or system. If you have no idea on how to +do that you can skip this step for now and come back when you get errors +related to these names when compiling the driver. +The features we use from those libraries are type definitions for integer sizes +from `` and `NULL` from ``. If they are not available you +need to specify the following integer types yourself: + +* `int64_t` = signed 64bit integer +* `uint64_t` = unsigned 64bit integer +* `int32_t` = signed 32bit integer +* `uint32_t` = unsigned 32bit integer +* `int16_t` = signed 16bit integer +* `uint16_t` = unsigned 16bit integer +* `int8_t` = signed 8bit integer +* `uint8_t` = unsigned 8bit integer + +In addition to that you will need to specify `NULL`. For both we have a +detailed template where you just need to fill in your system specific values. + + + +Now we are ready to compile and run the example usage for your sensor. + +## Compile and Run + +Pass the source `.c` and header `.h` files in this folder into your C compiler +and run the resulting binary. This step may vary, depending on your platform. +Here we demonstrate the procedure for Linux based platforms: + +1. Open up a terminal. +2. Navigate to the directory where this README is located. +3. Navigate to the subdirectory example-usage. +4. Run `make` (this compiles the example code into one executable binary). +5. Run the compiled executable with `./sen66_i2c_example_usage` +6. Now you should see the first measurement values appear in your terminal. As + a next step you can adjust the example usage file or write your own main + function to use the sensor. + +## Compile and Run Tests + +The testframekwork used is CppUTest. Pass the source `.cpp`, `.c` and header `.h` +files from the tests and top level folder into your CPP compiler and run the +resulting binary. This step may vary, depending on your platform. +Here we demonstrate the procedure for Linux based platforms: + +1. Open up a terminal. +2. Install CppUTest framework `apt install cpputest`. +3. Navigate to the directory `tests`. +4. Run `make` (this compiles the test code into one executable binary). +5. Run the compiled executable with `./sen66_test_hw_i2c`. +6. Now you should see the test output on your console. + +# Background + +## Files + +### sensirion\_i2c.[ch] + +In these files you can find the implementation of the I2C protocol used by +Sensirion sensors. The functions in these files are used by the embedded driver +to build the correct frame out of data to be sent to the sensor or receive a +frame of data from the sensor and convert it back to data readable by your +machine. The functions in here calculate and check CRCs, reorder bytes for +different byte orders and build the correct formatted frame for your sensor. + +### sensirion\_i2c\_hal.[ch] + +These files contain the implementation of the hardware abstraction layer used +by Sensirion's I2C embedded drivers. This part of the code is specific to the +underlying hardware platform. This is an unimplemented template for the user to +implement. In the `sample-implementations/` folder we provide implementations +for the most common platforms. + +### sensirion\_config.h + +In this file we keep all the included libraries for our drivers and global +defines. Next to `sensirion_i2c_hal.c` *it's the only file you should need to +edit to get your driver working.* + +### sensirion\_common.[ch] + +In these files you can find some helper functions used by Sensirion's embedded +drivers. It mostly contains byte order conversions for different variable +types. These functions are also used by the UART embedded drivers therefore +they are kept in their own file. + +## Contributing + +**Contributions are welcome!** + +This Sensirion library uses +[`clang-format`](https://releases.llvm.org/download.html) to standardize the +formatting of all our `.c` and `.h` files. Make sure your contributions are +formatted accordingly: + +The `-i` flag will apply the format changes to the files listed. + +```bash +clang-format -i *.c *.h +``` + +Note that differences from this formatting will result in a failed build until +they are fixed. + + +# License + +See [LICENSE](LICENSE). \ No newline at end of file diff --git a/example-usage/Makefile b/example-usage/Makefile new file mode 100644 index 0000000..7ce1b66 --- /dev/null +++ b/example-usage/Makefile @@ -0,0 +1,23 @@ +src_dir = .. +common_sources = ${src_dir}/sensirion_config.h ${src_dir}/sensirion_common.h ${src_dir}/sensirion_common.c +i2c_sources = ${src_dir}/sensirion_i2c_hal.h ${src_dir}/sensirion_i2c.h ${src_dir}/sensirion_i2c.c +driver_sources = ${src_dir}/sen66_i2c.h ${src_dir}/sen66_i2c.c + +i2c_implementation ?= ${src_dir}/sensirion_i2c_hal.c + +CFLAGS = -Os -Wall -fstrict-aliasing -Wstrict-aliasing=1 -Wsign-conversion -fPIC -I${src_dir} -I. + +ifdef CI + CFLAGS += -Werror +endif + +.PHONY: all clean + +all: sen66_i2c_example_usage + +sen66_i2c_example_usage: clean + $(CC) $(CFLAGS) -o $@ ${driver_sources} ${i2c_sources} \ + ${i2c_implementation} ${common_sources} sen66_i2c_example_usage.c + +clean: + $(RM) sen66_i2c_example_usage \ No newline at end of file diff --git a/example-usage/sen66_i2c_example_usage.c b/example-usage/sen66_i2c_example_usage.c new file mode 100644 index 0000000..d7edc75 --- /dev/null +++ b/example-usage/sen66_i2c_example_usage.c @@ -0,0 +1,117 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 1.0.1 + * Product: sen66 + * Model-Version: 1.2.0 + */ +/* + * Copyright (c) 2024, 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 "sen66_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c_hal.h" +#include // PRIx64 +#include // printf + +#define sensirion_hal_sleep_us sensirion_i2c_hal_sleep_usec + +void print_byte_array(uint8_t* array, uint16_t len) { + uint16_t i = 0; + printf("0x"); + for (; i < len; i++) { + printf("%02x", array[i]); + } +} + +int main(void) { + int16_t error = NO_ERROR; + sensirion_i2c_hal_init(); + sen66_init(SEN66_I2C_ADDR_6B); + + error = sen66_device_reset(); + if (error != NO_ERROR) { + printf("error executing device_reset(): %i\n", error); + return error; + } + sensirion_hal_sleep_us(1200000); + uint8_t serial_number[32] = {0}; + error = sen66_get_serial_number(serial_number, 32); + if (error != NO_ERROR) { + printf("error executing get_serial_number(): %i\n", error); + return error; + } + printf("serial_number: "); + print_byte_array(serial_number, 32); + printf("\n"); + error = sen66_start_continuous_measurement(); + if (error != NO_ERROR) { + printf("error executing start_continuous_measurement(): %i\n", error); + return error; + } + uint16_t mass_concentration_pm1p0 = 0; + uint16_t mass_concentration_pm2p5 = 0; + uint16_t mass_concentration_pm4p0 = 0; + uint16_t mass_concentration_pm10p0 = 0; + int16_t ambient_humidity = 0; + int16_t ambient_temperature = 0; + int16_t voc_index = 0; + int16_t nox_index = 0; + uint16_t co2 = 0; + uint16_t repetition = 0; + for (repetition = 0; repetition < 50; repetition++) { + sensirion_hal_sleep_us(1000000); + error = sen66_read_measured_values_as_integers( + &mass_concentration_pm1p0, &mass_concentration_pm2p5, + &mass_concentration_pm4p0, &mass_concentration_pm10p0, + &ambient_humidity, &ambient_temperature, &voc_index, &nox_index, + &co2); + if (error != NO_ERROR) { + printf("error executing read_measured_values_as_integers(): %i\n", + error); + continue; + } + printf("mass_concentration_pm1p0: %u ", mass_concentration_pm1p0); + printf("mass_concentration_pm2p5: %u ", mass_concentration_pm2p5); + printf("mass_concentration_pm4p0: %u ", mass_concentration_pm4p0); + printf("mass_concentration_pm10p0: %u ", mass_concentration_pm10p0); + printf("ambient_humidity: %i ", ambient_humidity); + printf("ambient_temperature: %i ", ambient_temperature); + printf("voc_index: %i ", voc_index); + printf("nox_index: %i ", nox_index); + printf("co2: %u\n", co2); + } + + error = sen66_stop_measurement(); + if (error != NO_ERROR) { + return error; + } + return NO_ERROR; +} diff --git a/images/sen6x-pinout.png b/images/sen6x-pinout.png new file mode 100644 index 0000000..42b5f0a Binary files /dev/null and b/images/sen6x-pinout.png differ diff --git a/images/sen6x.png b/images/sen6x.png new file mode 100644 index 0000000..b97bde3 Binary files /dev/null and b/images/sen6x.png differ diff --git a/metadata.yml b/metadata.yml new file mode 100644 index 0000000..5a15951 --- /dev/null +++ b/metadata.yml @@ -0,0 +1,7 @@ +# driver generation metadata +generator_version: 1.0.1 +model_version: 1.2.0 +dg_status: released +is_manually_modified: false +first_generated: '2024-10-30 08:14' +last_generated: '2024-10-30 08:14' diff --git a/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_hal.c b/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_hal.c new file mode 100644 index 0000000..30ea666 --- /dev/null +++ b/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_hal.c @@ -0,0 +1,85 @@ +/* + * 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 +#include + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +struct i2c_master_module i2c_master_instance; + +void sensirion_i2c_hal_init(void) { + /* initialize config structure and software module */ + struct i2c_master_config config_i2c_master; + i2c_master_get_config_defaults(&config_i2c_master); + + /* change buffer timeout to something longer */ + config_i2c_master.buffer_timeout = 10000; + + /* initialize and enable device with config. */ + i2c_master_init(&i2c_master_instance, SERCOM2, &config_i2c_master); + i2c_master_enable(&i2c_master_instance); +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { +} + +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint8_t count) { + struct i2c_master_packet packet = { + .address = address, + .data_length = count, + .data = (uint8_t*)data, + .ten_bit_address = false, + .high_speed = false, + }; + return i2c_master_read_packet_wait(&i2c_master_instance, &packet); +} + +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, + uint8_t count) { + struct i2c_master_packet packet = { + .address = address, + .data_length = count, + .data = (uint8_t*)data, + .ten_bit_address = false, + .high_speed = false, + }; + return i2c_master_write_packet_wait_no_stop(&i2c_master_instance, &packet); +} + +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + delay_us(useconds); +} diff --git a/sample-implementations/GPIO_bit_banging/README.md b/sample-implementations/GPIO_bit_banging/README.md new file mode 100644 index 0000000..1fd1d5d --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/README.md @@ -0,0 +1,19 @@ +# GPIO based Software I2C + +This implementation of the I2C HAL uses bit banging on GPIOs to run +the I2C communication. It is different from other sample-implementations. +It uses an additional pair of `.c` and `.h` file to make the implementation +generic, in a way that GPIO bitbanging can be performed on any system providing +access to GPIOs. Note that in this case the pull-up resistors in the electric +circuit are still needed, or need to be enabled manually if your microprocessor +provides this option. These files are called `sensirion_i2c_gpio.[ch]`. +Similar to the I2C HAL, we provide an unimplemented template in this in this +folder to implement yourself for your platform and sample implementations in the +so called folder. + +## Getting started + +To use this I2C HAL copy `sensirion_i2c_hal.c` and `sensirion_i2c_gpio.h` +from this folder to the main folder of your driver. Then either choose a +sample implementation or implement `sensirion_i2c_gpio.c` and copy +it to the main driver folder as well. diff --git a/sample-implementations/GPIO_bit_banging/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_gpio.c b/sample-implementations/GPIO_bit_banging/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_gpio.c new file mode 100644 index 0000000..958111a --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sample-implementations/Atmel_SAMD2_series/sensirion_i2c_gpio.c @@ -0,0 +1,133 @@ +/* + * 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_gpio.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +#include + +/* + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ + +ioport_pin_t scl_port = IOPORT_CREATE_PIN(IOPORT_PORTA, 9); +ioport_pin_t sda_port = IOPORT_CREATE_PIN(IOPORT_PORTA, 8); + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + ioport_enable_pin(scl_port); + ioport_enable_pin(sda_port); + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_SCL_in(); +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + ioport_set_pin_dir(sda_port, IOPORT_DIR_INPUT); + ioport_set_pin_mode(sda_port, IOPORT_MODE_OPEN_DRAIN); +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + ioport_set_pin_dir(sda_port, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(sda_port, false); +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + return ioport_get_pin_level(sda_port); +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + ioport_set_pin_dir(scl_port, IOPORT_DIR_INPUT); + ioport_set_pin_mode(scl_port, IOPORT_MODE_OPEN_DRAIN); +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + ioport_set_pin_dir(scl_port, IOPORT_DIR_OUTPUT); + ioport_set_pin_level(scl_port, false); +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + return ioport_get_pin_level(scl_port); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + /* TODO: IMPLEMENT */ +} diff --git a/sample-implementations/GPIO_bit_banging/sample-implementations/Nordic_nRF5_series/sensirion_i2c_gpio.c b/sample-implementations/GPIO_bit_banging/sample-implementations/Nordic_nRF5_series/sensirion_i2c_gpio.c new file mode 100644 index 0000000..8009cd0 --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sample-implementations/Nordic_nRF5_series/sensirion_i2c_gpio.c @@ -0,0 +1,158 @@ +/* + * 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_gpio.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +#include + +/* + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ + +/* Nordic specific include for Nordic specific configuration below */ +#include + +/** + * Nordic specific configuration. Change the pin numbers if you use other pins + * than defined below. + */ +#define SENSIRION_SDA_PIN 0 +#define SENSIRION_SCL_PIN 2 + +uint32_t sda; +uint32_t scl; +volatile uint32_t* sclPinRegAddr; +volatile uint32_t* sdaPinRegAddr; + +/* configuration register value for I2C output pin */ +static const uint32_t REGVALOUT = + (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) | + (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | + (GPIO_PIN_CNF_INPUT_Disconnect << GPIO_PIN_CNF_INPUT_Pos) | + (GPIO_PIN_CNF_DIR_Output << GPIO_PIN_CNF_DIR_Pos); +/* configuration register value for I2C input pin */ +static const uint32_t REGVALIN = + (GPIO_PIN_CNF_SENSE_Disabled << GPIO_PIN_CNF_SENSE_Pos) | + (GPIO_PIN_CNF_DRIVE_S0S1 << GPIO_PIN_CNF_DRIVE_Pos) | + (GPIO_PIN_CNF_PULL_Disabled << GPIO_PIN_CNF_PULL_Pos) | + (GPIO_PIN_CNF_INPUT_Connect << GPIO_PIN_CNF_INPUT_Pos) | + (GPIO_PIN_CNF_DIR_Input << GPIO_PIN_CNF_DIR_Pos); + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + scl = SENSIRION_SCL_PIN; + sda = SENSIRION_SDA_PIN; + sclPinRegAddr = &NRF_GPIO->PIN_CNF[(uint32_t)scl]; + sdaPinRegAddr = &NRF_GPIO->PIN_CNF[(uint32_t)sda]; + *sclPinRegAddr = REGVALIN; + *sdaPinRegAddr = REGVALIN; +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + *sdaPinRegAddr = REGVALIN; +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + *sdaPinRegAddr = REGVALOUT; +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + return (uint8_t)nrf_gpio_pin_read(sda); +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + *sclPinRegAddr = REGVALIN; +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + *sclPinRegAddr = REGVALOUT; +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + return (uint8_t)nrf_gpio_pin_read(scl); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + nrf_delay_us(useconds); +} diff --git a/sample-implementations/GPIO_bit_banging/sample-implementations/STM32F1_series/sensirion_i2c_gpio.c b/sample-implementations/GPIO_bit_banging/sample-implementations/STM32F1_series/sensirion_i2c_gpio.c new file mode 100644 index 0000000..ead2498 --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sample-implementations/STM32F1_series/sensirion_i2c_gpio.c @@ -0,0 +1,152 @@ +/* + * 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 +#include + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_gpio.h" + +/* + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + __GPIOB_CLK_ENABLE(); + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_SCL_in(); +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + GPIO_InitTypeDef GPIO_InitStruct = { + .Pin = GPIO_PIN_9, + .Mode = GPIO_MODE_INPUT, + .Pull = GPIO_NOPULL, + .Speed = GPIO_SPEED_HIGH, + }; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + GPIO_InitTypeDef GPIO_InitStruct = { + .Pin = GPIO_PIN_9, + .Mode = GPIO_MODE_OUTPUT_PP, + .Pull = GPIO_NOPULL, + .Speed = GPIO_SPEED_HIGH, + }; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET); +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + return (uint8_t)HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_9) == GPIO_PIN_SET; +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + GPIO_InitTypeDef GPIO_InitStruct = { + .Pin = GPIO_PIN_8, + .Mode = GPIO_MODE_INPUT, + .Pull = GPIO_NOPULL, + .Speed = GPIO_SPEED_FREQ_HIGH, + }; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + GPIO_InitTypeDef GPIO_InitStruct = { + .Pin = GPIO_PIN_8, + .Mode = GPIO_MODE_OUTPUT_PP, + .Pull = GPIO_NOPULL, + .Speed = GPIO_SPEED_FREQ_HIGH, + }; + HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); + HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET); +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + return (uint8_t)HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_8) == GPIO_PIN_SET; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + HAL_Delay(useconds / 1000 + 1); +} diff --git a/sample-implementations/GPIO_bit_banging/sample-implementations/linux_user_space/sensirion_i2c_gpio.c b/sample-implementations/GPIO_bit_banging/sample-implementations/linux_user_space/sensirion_i2c_gpio.c new file mode 100644 index 0000000..c7ce9f2 --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sample-implementations/linux_user_space/sensirion_i2c_gpio.c @@ -0,0 +1,232 @@ +/* + * 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. + */ + +/* Enable usleep function */ +#define _DEFAULT_SOURCE + +#include /* close, open */ +#include /* fprintf, perror, stderr */ +#include /* exit */ +#include /* strlen */ +#include /* mode_t */ +#include /* access, lseek, read, usleep */ + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_gpio.h" + +/* + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ +#define GPIO_PIN_SCL 12 +#define GPIO_PIN_SDA 13 +#define GPIO_DIR "/sys/class/gpio/" + +#define __str(x) #x +#define GPIO_PIN_STR(p) __str(p) +#define GPIO_PIN_SCL_STR GPIO_PIN_STR(GPIO_PIN_SCL) +#define GPIO_PIN_SDA_STR GPIO_PIN_STR(GPIO_PIN_SDA) +#define GPIO_ID "gpio" +#define GPIO(p) GPIO_ID __str(p) +#define GPIO_PATH(p) GPIO_DIR p +#define GPIO_EXPORT_PATH GPIO_PATH("export") +#define GPIO_SCL_DIR GPIO_PATH(GPIO(GPIO_PIN_SCL)) +#define GPIO_SDA_DIR GPIO_PATH(GPIO(GPIO_PIN_SDA)) +#define GPIO_SCL_PATH(d) GPIO_SCL_DIR d +#define GPIO_SDA_PATH(d) GPIO_SDA_DIR d +#define GPIO_SCL_DIRECTION GPIO_SCL_PATH("/direction") +#define GPIO_SDA_DIRECTION GPIO_SDA_PATH("/direction") +#define GPIO_SCL_VALUE GPIO_SCL_PATH("/value") +#define GPIO_SDA_VALUE GPIO_SDA_PATH("/value") + +#define GPIO_DIRECTION_IN "in" +#define GPIO_DIRECTION_OUT "out" +#define GPIO_LOW 0 + +static int scl_dir_fd; +static int scl_val_fd; +static int sda_dir_fd; +static int sda_val_fd; + +static int open_or_exit(const char* path, int flags) { + int fd = open(path, flags); + if (fd < 0) { + perror(NULL); + fprintf(stderr, "Error opening %s (mode %d)\n", path, flags); + exit(-1); + } + return fd; +} + +static void rev_or_exit(int fd) { + if (lseek(fd, 0, SEEK_SET) < 0) { + perror("Error seeking gpio"); + exit(-1); + } +} + +static void write_or_exit(int fd, const char* buf) { + size_t len = strlen(buf); + + ssize_t w = write(fd, buf, len); + + /* Adapted from stackoverflow answer by Stephen Canon + See: https://www.stackoverflow.com/a/16086724 */ + if (w < 0 || (size_t)w != len) { + perror("Error writing"); + exit(-1); + } +} + +static void gpio_export(const char* path, const char* export_pin) { + int fd; + + if (access(path, F_OK) == -1) { + fd = open_or_exit(GPIO_EXPORT_PATH, O_WRONLY); + write_or_exit(fd, export_pin); + close(fd); + } +} + +static void gpio_set_value(int fd, int value) { + char buf[] = {'0', '\0'}; + + buf[0] += value; + rev_or_exit(fd); + write_or_exit(fd, buf); +} + +static void gpio_set_direction(int fd, const char* dir) { + rev_or_exit(fd); + write_or_exit(fd, dir); +} + +static uint8_t gpio_get_value(int fd) { + char c; + + rev_or_exit(fd); + if (read(fd, &c, 1) != 1) { + perror("Error reading GPIO value"); + exit(-1); + } + return c == '1'; +} + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + gpio_export(GPIO_SCL_DIR, GPIO_PIN_SCL_STR); + gpio_export(GPIO_SDA_DIR, GPIO_PIN_SDA_STR); + + scl_dir_fd = open_or_exit(GPIO_SCL_DIRECTION, O_WRONLY); + scl_val_fd = open_or_exit(GPIO_SCL_VALUE, O_RDWR); + sda_dir_fd = open_or_exit(GPIO_SDA_DIRECTION, O_WRONLY); + sda_val_fd = open_or_exit(GPIO_SDA_VALUE, O_RDWR); +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + gpio_set_direction(sda_dir_fd, GPIO_DIRECTION_IN); +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + gpio_set_direction(sda_dir_fd, GPIO_DIRECTION_OUT); + gpio_set_value(sda_val_fd, GPIO_LOW); +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + return gpio_get_value(sda_val_fd); +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + gpio_set_direction(scl_dir_fd, GPIO_DIRECTION_IN); +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + gpio_set_direction(scl_dir_fd, GPIO_DIRECTION_OUT); + gpio_set_value(scl_val_fd, GPIO_LOW); +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + return gpio_get_value(scl_val_fd); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + usleep(useconds); +} diff --git a/sample-implementations/GPIO_bit_banging/sample-implementations/mbed/sensirion_i2c_gpio.cpp b/sample-implementations/GPIO_bit_banging/sample-implementations/mbed/sensirion_i2c_gpio.cpp new file mode 100644 index 0000000..ee0d8fa --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sample-implementations/mbed/sensirion_i2c_gpio.cpp @@ -0,0 +1,132 @@ +/* + * 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 + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_gpio.h" + +/* + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ + +/* Create SDA and SCL pin objects. Different pins can be used too. */ +DigitalInOut sda(p28); +DigitalInOut scl(p27); + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + /* Do nothing */ +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { + /* Do nothing */ +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + sda.input(); + sda = 1; +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + sda.output(); + sda = 0; +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + return sda.read(); +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + scl.input(); + scl = 1; +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + scl.output(); + scl = 0; +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + return scl.read(); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + wait_us(useconds); +} diff --git a/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.c b/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.c new file mode 100644 index 0000000..d1d03bb --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.c @@ -0,0 +1,130 @@ +/* + * 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_gpio.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +/* + * INSTRUCTIONS + * ============ + * + * Implement all functions where they are marked as IMPLEMENT. + * Follow the function specification in the comments. + * + * We use the following names for the two I2C signal lines: + * SCL for the clock line + * SDA for the data line + * + * Both lines must be equipped with pull-up resistors appropriate to the bus + * frequency. + */ + +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void) { + /* TODO:IMPLEMENT */ +} + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void) { + /* TODO:IMPLEMENT or leave empty if no resources need to be freed */ +} + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void) { + /* TODO:IMPLEMENT */ +} + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void) { + /* TODO:IMPLEMENT */ +} + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void) { + /* TODO:IMPLEMENT */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void) { + /* TODO:IMPLEMENT */ +} + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void) { + /* TODO:IMPLEMENT */ +} + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void) { + /* TODO:IMPLEMENT */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds) { + /* TODO:IMPLEMENT */ +} diff --git a/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.h b/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.h new file mode 100644 index 0000000..19dcc62 --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sensirion_i2c_gpio.h @@ -0,0 +1,115 @@ +/* + * 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_SW_I2C_H +#define SENSIRION_SW_I2C_H + +#include "sensirion_common.h" +#include "sensirion_config.h" + +/** + * The clock period of the i2c bus in microseconds. Increase this, if your GPIO + * ports cannot support a 200 kHz output rate. (2 * 1 / 10usec == 200Khz) + * + * This is only relevant for the sw-i2c HAL (bit-banging on GPIO pins). The + * pulse length is half the clock period, the number should thus be even. + */ +#define SENSIRION_I2C_CLOCK_PERIOD_USEC 10 + +#ifdef __cplusplus +extern "C" { +#endif +/** + * Initialize all hard- and software components that are needed to set the + * SDA and SCL pins. + */ +void sensirion_i2c_gpio_init_pins(void); + +/** + * Release all resources initialized by sensirion_i2c_gpio_init_pins() + */ +void sensirion_i2c_gpio_release_pins(void); + +/** + * Configure the SDA pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SDA_in(void); + +/** + * Configure the SDA pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SDA_out(void); + +/** + * Read the value of the SDA pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SDA_read(void); + +/** + * Configure the SCL pin as an input. With an external pull-up resistor the line + * should be left floating, without external pull-up resistor, the input must be + * configured to use the internal pull-up resistor. + */ +void sensirion_i2c_gpio_SCL_in(void); + +/** + * Configure the SCL pin as an output and drive it low or set to logical false. + */ +void sensirion_i2c_gpio_SCL_out(void); + +/** + * Read the value of the SCL pin. + * @returns 0 if the pin is low and 1 otherwise. + */ +uint8_t sensirion_i2c_gpio_SCL_read(void); + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution approximately, but no less than, the given time. + * + * 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_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_gpio_sleep_usec(uint32_t useconds); + +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_SW_I2C_H */ diff --git a/sample-implementations/GPIO_bit_banging/sensirion_i2c_hal.c b/sample-implementations/GPIO_bit_banging/sensirion_i2c_hal.c new file mode 100644 index 0000000..3d2f6d0 --- /dev/null +++ b/sample-implementations/GPIO_bit_banging/sensirion_i2c_hal.c @@ -0,0 +1,251 @@ +/* + * 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" +#include "sensirion_i2c.h" +#include "sensirion_i2c_gpio.h" + +#define DELAY_USEC (SENSIRION_I2C_CLOCK_PERIOD_USEC / 2) + +/** + * Declaration of static helpers. + */ +static int8_t sensirion_i2c_gpio_write_byte(uint8_t data); +static uint8_t sensirion_i2c_gpio_read_byte(uint8_t ack); +static int8_t sensirion_i2c_gpio_start(void); +static void sensirion_i2c_gpio_stop(void); + +/** + * 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) { + return NO_ERROR; /* not relevant for software I2C */ +} + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + sensirion_i2c_gpio_init_pins(); + sensirion_i2c_gpio_SCL_in(); + sensirion_i2c_gpio_SDA_in(); +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + sensirion_i2c_gpio_SCL_in(); + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_release_pins(); +} + +/** + * 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, uint8_t count) { + int8_t ret; + uint8_t send_ack; + uint16_t i; + + ret = sensirion_i2c_gpio_start(); + if (ret != NO_ERROR) + return ret; + + ret = sensirion_i2c_gpio_write_byte((address << 1) | 1); + if (ret != NO_ERROR) { + sensirion_i2c_gpio_stop(); + return ret; + } + for (i = 0; i < count; i++) { + send_ack = i < (count - 1); /* last byte must be NACK'ed */ + data[i] = sensirion_i2c_gpio_read_byte(send_ack); + } + + sensirion_i2c_gpio_stop(); + return NO_ERROR; +} + +/** + * 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, + uint8_t count) { + int8_t ret; + uint16_t i; + + ret = sensirion_i2c_gpio_start(); + if (ret != NO_ERROR) + return ret; + + ret = sensirion_i2c_gpio_write_byte(address << 1); + if (ret != NO_ERROR) { + sensirion_i2c_gpio_stop(); + return ret; + } + for (i = 0; i < count; i++) { + ret = sensirion_i2c_gpio_write_byte(data[i]); + if (ret != NO_ERROR) { + sensirion_i2c_gpio_stop(); + break; + } + } + sensirion_i2c_gpio_stop(); + return ret; +} + +/** + * 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) { + sensirion_i2c_gpio_sleep_usec(useconds); +} + +/** + * The following functions are static helpers. + */ + +static int8_t sensirion_wait_while_clock_stretching(void) { + /* Maximal timeout of 150ms (SCD30) in sleep polling cycles */ + uint32_t timeout_cycles = 150000 / SENSIRION_I2C_CLOCK_PERIOD_USEC; + + while (--timeout_cycles) { + if (sensirion_i2c_gpio_SCL_read()) + return NO_ERROR; + sensirion_i2c_gpio_sleep_usec(SENSIRION_I2C_CLOCK_PERIOD_USEC); + } + + return I2C_BUS_ERROR; +} + +static int8_t sensirion_i2c_gpio_write_byte(uint8_t data) { + int8_t nack, i; + for (i = 7; i >= 0; i--) { + sensirion_i2c_gpio_SCL_out(); + if ((data >> i) & 0x01) + sensirion_i2c_gpio_SDA_in(); + else + sensirion_i2c_gpio_SDA_out(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + if (sensirion_wait_while_clock_stretching()) + return I2C_BUS_ERROR; + } + sensirion_i2c_gpio_SCL_out(); + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_in(); + if (sensirion_wait_while_clock_stretching()) + return I2C_BUS_ERROR; + nack = (sensirion_i2c_gpio_SDA_read() != 0); + sensirion_i2c_gpio_SCL_out(); + + return nack; +} + +static uint8_t sensirion_i2c_gpio_read_byte(uint8_t ack) { + int8_t i; + uint8_t data = 0; + sensirion_i2c_gpio_SDA_in(); + for (i = 7; i >= 0; i--) { + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_in(); + if (sensirion_wait_while_clock_stretching()) + return 0xFF; /* return 0xFF on error */ + data |= (sensirion_i2c_gpio_SDA_read() != 0) << i; + sensirion_i2c_gpio_SCL_out(); + } + if (ack) + sensirion_i2c_gpio_SDA_out(); + else + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + if (sensirion_wait_while_clock_stretching()) + return 0xFF; /* return 0xFF on error */ + sensirion_i2c_gpio_SCL_out(); + sensirion_i2c_gpio_SDA_in(); + + return data; +} + +static int8_t sensirion_i2c_gpio_start(void) { + sensirion_i2c_gpio_SCL_in(); + if (sensirion_wait_while_clock_stretching()) + return I2C_BUS_ERROR; + + sensirion_i2c_gpio_SDA_out(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_out(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + return NO_ERROR; +} + +static void sensirion_i2c_gpio_stop(void) { + sensirion_i2c_gpio_SDA_out(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SCL_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); + sensirion_i2c_gpio_SDA_in(); + sensirion_i2c_gpio_sleep_usec(DELAY_USEC); +} diff --git a/sample-implementations/Nordic_nRF5_series/sensirion_i2c_hal.c b/sample-implementations/Nordic_nRF5_series/sensirion_i2c_hal.c new file mode 100644 index 0000000..e26ba9c --- /dev/null +++ b/sample-implementations/Nordic_nRF5_series/sensirion_i2c_hal.c @@ -0,0 +1,128 @@ +/* + * 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 +#include +#include + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +/** + * Nordic specific configuration. Change the pin numbers if you use other pins + * than defined below. + */ +#define SENSIRION_SDA_PIN 0 +#define SENSIRION_SCL_PIN 2 + +/** + * Create new TWI instance. You may also use a different interface. In this + * case, please adapt the code below. + */ +static const nrf_drv_twi_t i2c_instance = NRF_DRV_TWI_INSTANCE(0); + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + int8_t err; + const nrf_drv_twi_config_t i2c_instance_config = {.scl = SENSIRION_SCL_PIN, + .sda = SENSIRION_SDA_PIN, + .frequency = + NRF_TWI_FREQ_100K, + .interrupt_priority = 0}; + /* initiate TWI instance */ + err = nrf_drv_twi_init(&i2c_instance, &i2c_instance_config, NULL, NULL); + if (err) { + /* Could be omitted if the prototyp is changed to non-void or an error + * flag is introduced */ + printf("Error %d: Initialization of I2C connection failed!\n", err); + } + /* enable TWI instance */ + nrf_drv_twi_enable(&i2c_instance); + return; +} + +/** + * 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 + * + * error codes: 3 -> error detected by hardware (internal error) + * 17 -> driver not ready for new transfer (busy) + */ +int8_t sensirion_i2c_hal_read(uint8_t address, uint8_t* data, uint8_t count) { + int8_t err = nrf_drv_twi_rx(&i2c_instance, address, data, count); + return err; +} + +/** + * 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 + * + * error codes: 3 -> error detected by hardware (internal error) + * 17 -> driver not ready for new transfer (busy) + */ +int8_t sensirion_i2c_hal_write(uint8_t address, const uint8_t* data, + uint8_t count) { + int8_t err = nrf_drv_twi_tx(&i2c_instance, address, data, count, false); + return err; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + nrf_delay_us(useconds); +} diff --git a/sample-implementations/RaspberryPi_Pico/cMakeLists.txt b/sample-implementations/RaspberryPi_Pico/cMakeLists.txt new file mode 100644 index 0000000..4a9c75a --- /dev/null +++ b/sample-implementations/RaspberryPi_Pico/cMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.12) +include(${PICO_SDK_PATH}/pico_sdk_init.cmake) + +project(SCD4XSensor C CXX ASM) +pico_sdk_init() + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +add_executable(main + main.c + sensirion_i2c.c + sensirion_i2c.hal.c + scd4x_i2c.c + sensirion_common.c) + +# pull in common dependencies and additional i2c hardware support +target_link_libraries(main pico_stdlib hardware_i2c) + +pico_enable_stdio_usb(main 1) + +pico_enable_stdio_uart(main 0) + +# create map/bin/hex file etc. +pico_add_extra_outputs(main) + diff --git a/sample-implementations/RaspberryPi_Pico/main.c b/sample-implementations/RaspberryPi_Pico/main.c new file mode 100644 index 0000000..195febe --- /dev/null +++ b/sample-implementations/RaspberryPi_Pico/main.c @@ -0,0 +1,77 @@ + + +#include "hardware/i2c.h" +#include "pico/binary_info.h" +#include "pico/stdlib.h" +#include "scd4x_i2c.h" +#include + +/// I2C address +static int addr = 0x62; + +// I2C Pins +static uint sda_pin = 16; +static uint scl_pin = 17; + +// This is the main entry for your c application. U +// is +int main() { + + stdio_init_all(); + + // Setup I2c using pins 16 & 17 + i2c_init(i2c_default, 400 * 1000); + gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); + gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); + + // This variable will hold the return status of the function calls. + // You can separate each function call result into their own variable or re + // - use this. + + int status = 0; + + // Stop any readings if occuring + status = scd4x_stop_periodic_measurement(); + + // Perform self test + uint16_t* selfTest = 0; + scd4x_perform_self_test(selfTest); + + // Get Serial number 3 parts + uint16_t one; + uint16_t two; + uint16_t three; + + scd4x_get_serial_number(&one, &two, &three); + + // Start the readings. + status1 = scd4x_start_periodic_measurement(); + + while (1) { + + // Check if data is ready to read + bool dataReady; + while (dataReady == false) { + + status1 = scd4x_get_data_ready_flag(&dataReady); + } + + // Get the ticks. The scd4x_read_measurement function is giving + // incorrect data due to the arthimetic + uint16_t co2; + uint16_t temp; + uint16_t humidity; + status1 = scd4x_read_measurement_ticks(&co2, &temp, &humidity); + + // Arithemtic to change raw data into information + int tempInCelsius = -45 + 175 * temp / 65536; + int tempInFarenheit = tempInCelsius * 1.8 + 32; + int humidityPercent = 100 * humidity / 65536; + + // Print results to terminal (output) + printf("C:%d,T:%d,H:%d", co2, tempInFarenheit, humidityPercent); + + // Sleep for 5 seconds. + sleep_ms(5000); + } +} diff --git a/sample-implementations/RaspberryPi_Pico/sensirion_i2c_hal.c b/sample-implementations/RaspberryPi_Pico/sensirion_i2c_hal.c new file mode 100644 index 0000000..d7299ba --- /dev/null +++ b/sample-implementations/RaspberryPi_Pico/sensirion_i2c_hal.c @@ -0,0 +1,127 @@ +#include +/* + * 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_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +/* + * 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(void) { + /* TODO:IMPLEMENT */ +} + +/** + * 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 status = i2c_read_blocking(i2c_default, address, data, count, false); + if (status == 0) + return 1; + else + return 0; +} + +/** + * 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) { + // I2C Default is used (I2C0). + int status = i2c_write_blocking(i2c_default, address, data, count, true); + + if (status == 0) + return 1; + else + return 0; +} + +/** + * 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_ms(useconds / 1000); +} diff --git a/sample-implementations/STM32F1_series/sensirion_i2c_hal.c b/sample-implementations/STM32F1_series/sensirion_i2c_hal.c new file mode 100644 index 0000000..83db176 --- /dev/null +++ b/sample-implementations/STM32F1_series/sensirion_i2c_hal.c @@ -0,0 +1,127 @@ +/* + * 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 + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +/** + * Create new I2C instance. You may also use a different interface, e.g. hi2c2, + * depending on your CubeMX configuration + */ +static I2C_HandleTypeDef hi2c1; + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + hi2c1.Instance = I2C1; + hi2c1.Init.ClockSpeed = 100000; + hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; + hi2c1.Init.OwnAddress1 = 0; + hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; + hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; + hi2c1.Init.OwnAddress2 = 0; + hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + /* Enable the remapping of Pins 6/7 to 8/9 and the I2C clock before the + * initialization of the GPIO Pins in HAL_I2C_Init(). This is a fix of the + * code generated by CubeMX v4.16.0 */ + __HAL_AFIO_REMAP_I2C1_ENABLE(); + __HAL_RCC_I2C1_CLK_ENABLE(); + HAL_I2C_Init(&hi2c1); +} + +/** + * 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, uint8_t count) { + return (int8_t)HAL_I2C_Master_Receive(&hi2c1, (uint16_t)(address << 1), + data, count, 100); +} + +/** + * 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, + uint8_t count) { + return (int8_t)HAL_I2C_Master_Transmit(&hi2c1, (uint16_t)(address << 1), + (uint8_t*)data, count, 100); +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + uint32_t msec = useconds / 1000; + if (useconds % 1000 > 0) { + msec++; + } + + /* + * Increment by 1 if STM32F1 driver version less than 1.1.1 + * Old firmwares of STM32F1 sleep 1ms shorter than specified in HAL_Delay. + * This was fixed with firmware 1.6 (driver version 1.1.1), so we have to + * fix it ourselves for older firmwares + */ + if (HAL_GetHalVersion() < 0x01010100) { + msec++; + } + + HAL_Delay(msec); +} diff --git a/sample-implementations/esp32/sensirion_i2c_esp32_config.h b/sample-implementations/esp32/sensirion_i2c_esp32_config.h new file mode 100644 index 0000000..a7e4d15 --- /dev/null +++ b/sample-implementations/esp32/sensirion_i2c_esp32_config.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 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_ESP32_CONFIG_H +#define SENSIRION_I2C_ESP32_CONFIG_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int esp_err_t; + +struct esp32_i2c_config { + uint32_t freq; + uint8_t addr; + i2c_port_t port; + gpio_num_t sda; + gpio_num_t scl; + bool sda_pullup; + bool scl_pullup; +}; + +extern esp_err_t sensirion_i2c_config_esp32(struct esp32_i2c_config* cfg); +extern esp_err_t sensirion_i2c_esp32_ok(void); + +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_I2C_ESP32_CONFIG_H */ diff --git a/sample-implementations/esp32/sensirion_i2c_hal.c b/sample-implementations/esp32/sensirion_i2c_hal.c new file mode 100644 index 0000000..b6c89dc --- /dev/null +++ b/sample-implementations/esp32/sensirion_i2c_hal.c @@ -0,0 +1,194 @@ +/* + * Copyright (c) 2023, 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" + +#include +#include +#include +#include + +#include "sensirion_i2c_esp32_config.h" + +static const char* TAG = "sensirion_i2c_hal"; + +#define SLEEP_MS(x) \ + vTaskDelay(((x) + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS) +#define CHECK(x) \ + do { \ + esp_err_t __; \ + if ((__ = x) != ESP_OK) \ + return __; \ + } while (0) +#define CHECK_ARG(VAL) \ + do { \ + if (!(VAL)) \ + return ESP_ERR_INVALID_ARG; \ + } while (0) +#define UNUSED_PARAM(x) (void)x + +static i2c_dev_t dev = {0}; +static struct esp32_i2c_config i2c_cfg = {0}; +static esp_err_t i2c_ok = ESP_OK; + +/* + * 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; +} + +esp_err_t sensirion_i2c_config_esp32(struct esp32_i2c_config* cfg) { + if (cfg != NULL) { + memcpy(&i2c_cfg, cfg, sizeof(*cfg)); + return ESP_OK; + } else { + return ESP_FAIL; + } +} + +esp_err_t sensirion_i2c_esp32_ok(void) { + return i2c_ok; +} + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + memset(&dev, 0, sizeof(i2c_dev_t)); + // dev.addr = addr; + dev.port = i2c_cfg.port; + dev.cfg.mode = I2C_MODE_MASTER; + dev.cfg.sda_io_num = i2c_cfg.sda; + dev.cfg.scl_io_num = i2c_cfg.scl; + dev.cfg.sda_pullup_en = i2c_cfg.sda_pullup; + dev.cfg.scl_pullup_en = i2c_cfg.scl_pullup; +#if HELPER_TARGET_IS_ESP32 + dev.cfg.master.clk_speed = i2c_cfg.freq; +#endif + + esp_err_t err = i2c_dev_create_mutex(&dev); + if (err == ESP_OK) { + ESP_LOGI( + TAG, + "Sensirion I2C initialized. Address: 0x%x Port: %d SDA: %d SCL: %d", + i2c_cfg.addr, i2c_cfg.port, i2c_cfg.sda, i2c_cfg.scl); + } else { + ESP_LOGE(TAG, + "Error initializing Sensirion I2C! Address: 0x%x Port: %d " + "SDA: %d SCL: %d", + i2c_cfg.addr, i2c_cfg.port, i2c_cfg.sda, i2c_cfg.scl); + } + + i2c_ok = err; +} + +/** + * 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) { + ESP_LOGI(TAG, "sensirion_i2c_hal_read: len: %d", count); + dev.addr = address; + I2C_DEV_TAKE_MUTEX(&dev); + I2C_DEV_CHECK(&dev, i2c_dev_read(&dev, NULL, 0, data, count)); + I2C_DEV_GIVE_MUTEX(&dev); + ESP_LOGI(TAG, "READ OK"); + return (int8_t)ESP_OK; +} + +/** + * 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) { + ESP_LOGI(TAG, "sensirion_i2c_hal_write: len: %d", count); + dev.addr = address; + I2C_DEV_TAKE_MUTEX(&dev); + I2C_DEV_CHECK(&dev, i2c_dev_write(&dev, NULL, 0, data, count)); + I2C_DEV_GIVE_MUTEX(&dev); + ESP_LOGI(TAG, "WRITE OK"); + return (int8_t)ESP_OK; +} + +/** + * 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) { + ESP_LOGI(TAG, "sensirion_i2c_hal_sleep: %d usec", useconds); + SLEEP_MS(useconds / 1000); +} diff --git a/sample-implementations/linux_user_space/sensirion_i2c_hal.c b/sample-implementations/linux_user_space/sensirion_i2c_hal.c new file mode 100644 index 0000000..74a7f57 --- /dev/null +++ b/sample-implementations/linux_user_space/sensirion_i2c_hal.c @@ -0,0 +1,136 @@ +/* + * 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. + */ + +/* Enable usleep function */ +#define _DEFAULT_SOURCE + +#include "sensirion_i2c_hal.h" +#include "sensirion_common.h" +#include "sensirion_config.h" + +#include +#include +#include +#include + +/** + * Linux specific configuration. Adjust the following define to the device path + * of your sensor. + */ +#define I2C_DEVICE_PATH "/dev/i2c-1" + +/** + * The following define was taken from i2c-dev.h. Alternatively the header file + * can be included. The define was added in Linux v3.10 and never changed since + * then. + */ +#define I2C_SLAVE 0x0703 + +#define I2C_WRITE_FAILED -1 +#define I2C_READ_FAILED -1 + +static int i2c_device = -1; +static uint8_t i2c_address = 0; + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + /* open i2c adapter */ + i2c_device = open(I2C_DEVICE_PATH, O_RDWR); + if (i2c_device == -1) + return; /* no error handling */ +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + if (i2c_device >= 0) + close(i2c_device); +} + +/** + * 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, uint8_t count) { + if (i2c_address != address) { + ioctl(i2c_device, I2C_SLAVE, address); + i2c_address = address; + } + + if (read(i2c_device, data, count) != count) { + return I2C_READ_FAILED; + } + return 0; +} + +/** + * 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, + uint8_t count) { + if (i2c_address != address) { + ioctl(i2c_device, I2C_SLAVE, address); + i2c_address = address; + } + + if (write(i2c_device, data, count) != count) { + return I2C_WRITE_FAILED; + } + return 0; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + usleep(useconds); +} diff --git a/sample-implementations/mbed/sensirion_i2c_hal.cpp b/sample-implementations/mbed/sensirion_i2c_hal.cpp new file mode 100644 index 0000000..ce846a1 --- /dev/null +++ b/sample-implementations/mbed/sensirion_i2c_hal.cpp @@ -0,0 +1,98 @@ +/* + * 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" +#include + +#define E_MBED_I2C_READ_FAILED -1 +#define E_MBED_I2C_WRITE_FAILED -1 + +/* open I2C connection */ +static I2C i2c_connection(I2C_SDA, I2C_SCL); + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { +} + +/** + * 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, uint8_t count) { + if (i2c_connection.read(address << 1, (char*)data, count) != 0) + return E_MBED_I2C_READ_FAILED; + return 0; +} + +/** + * 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, + uint8_t count) { + if (i2c_connection.write(address << 1, (char*)data, count) != 0) + return E_MBED_I2C_WRITE_FAILED; + return 0; +} + +/** + * Sleep for a given number of microseconds. The function should delay the + * execution for at least the given time, but may also sleep longer. + * + * @param useconds the sleep time in microseconds + */ +void sensirion_i2c_hal_sleep_usec(uint32_t useconds) { + wait_us(useconds); +} diff --git a/sample-implementations/zephyr_user_space/sensirion_i2c_hal.c b/sample-implementations/zephyr_user_space/sensirion_i2c_hal.c new file mode 100644 index 0000000..a0dd0d4 --- /dev/null +++ b/sample-implementations/zephyr_user_space/sensirion_i2c_hal.c @@ -0,0 +1,127 @@ +/* + * 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 +#include +#include + +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +/* I2C device. */ +static struct device* i2c_dev; + +/** + * Select the current i2c bus by index. + * All following i2c operations will be directed at that 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) { + char bus_name[6] = "I2C_0"; + + if (bus_idx > 9) { + /* Invalid bus index */ + return STATUS_FAIL; + } + + bus_name[4] = bus_idx + '0'; + i2c_dev = device_get_binding(bus_name); + if (i2c_dev == NULL) { + /* No valid device found */ + return STATUS_FAIL; + } + + return STATUS_OK; +} + +/** + * Initialize all hard- and software components that are needed for the I2C + * communication. + */ +void sensirion_i2c_hal_init(void) { + /* Device (specified by sps30_i2c_dev) is already initialized by the Zephyr + * boot-up process. Nothing to be done here. */ +} + +/** + * Release all resources initialized by sensirion_i2c_hal_init(). + */ +void sensirion_i2c_hal_free(void) { + i2c_dev = NULL; +} + +/** + * 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, uint8_t count) { + return i2c_read(i2c_dev, data, count, address); +} + +/** + * 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, + uint8_t count) { + return i2c_write(i2c_dev, data, count, address); +} + +/** + * 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) { + int32_t remaining = useconds; + while (remaining > 0) { + remaining = k_usleep(remaining); + } +} diff --git a/sen66_i2c.c b/sen66_i2c.c new file mode 100644 index 0000000..97a9f4c --- /dev/null +++ b/sen66_i2c.c @@ -0,0 +1,252 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 1.0.1 + * Product: sen66 + * Model-Version: 1.2.0 + */ +/* + * Copyright (c) 2024, 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 "sen66_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c.h" +#include "sensirion_i2c_hal.h" + +#define sensirion_hal_sleep_us sensirion_i2c_hal_sleep_usec + +static uint8_t communication_buffer[48] = {0}; + +static uint8_t _i2c_address; + +void sen66_init(uint8_t i2c_address) { + _i2c_address = i2c_address; +} + +int16_t sen66_device_reset() { + int16_t 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, 0xd304); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(1200 * 1000); + return local_error; +} + +int16_t sen66_start_continuous_measurement() { + int16_t 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, 0x21); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(50 * 1000); + return local_error; +} + +int16_t sen66_stop_measurement() { + int16_t 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, 0x104); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(160 * 1000); + return local_error; +} + +int16_t sen66_get_data_ready(uint8_t* padding, bool* data_ready) { + int16_t 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, 0x202); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(20 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 2); + if (local_error != NO_ERROR) { + return local_error; + } + *padding = (uint8_t)buffer_ptr[0]; + *data_ready = (bool)buffer_ptr[1]; + return local_error; +} + +int16_t sen66_read_measured_values_as_integers( + uint16_t* mass_concentration_pm1p0, uint16_t* mass_concentration_pm2p5, + uint16_t* mass_concentration_pm4p0, uint16_t* mass_concentration_pm10p0, + int16_t* ambient_humidity, int16_t* ambient_temperature, int16_t* voc_index, + int16_t* nox_index, uint16_t* co2) { + int16_t 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, 0x300); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(20 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 18); + if (local_error != NO_ERROR) { + return local_error; + } + *mass_concentration_pm1p0 = + sensirion_common_bytes_to_uint16_t(&buffer_ptr[0]); + *mass_concentration_pm2p5 = + sensirion_common_bytes_to_uint16_t(&buffer_ptr[2]); + *mass_concentration_pm4p0 = + sensirion_common_bytes_to_uint16_t(&buffer_ptr[4]); + *mass_concentration_pm10p0 = + sensirion_common_bytes_to_uint16_t(&buffer_ptr[6]); + *ambient_humidity = sensirion_common_bytes_to_int16_t(&buffer_ptr[8]); + *ambient_temperature = sensirion_common_bytes_to_int16_t(&buffer_ptr[10]); + *voc_index = sensirion_common_bytes_to_int16_t(&buffer_ptr[12]); + *nox_index = sensirion_common_bytes_to_int16_t(&buffer_ptr[14]); + *co2 = sensirion_common_bytes_to_uint16_t(&buffer_ptr[16]); + return local_error; +} + +int16_t +sen66_perform_forced_co2_recalibration(uint16_t target_co2_concentration, + uint16_t* correction) { + int16_t 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, 0x6707); + local_offset = sensirion_i2c_add_uint16_t_to_buffer( + buffer_ptr, local_offset, target_co2_concentration); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(500 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 2); + if (local_error != NO_ERROR) { + return local_error; + } + *correction = sensirion_common_bytes_to_uint16_t(&buffer_ptr[0]); + return local_error; +} + +int16_t sen66_get_product_name(uint8_t* product_name, + uint16_t product_name_size) { + int16_t 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, 0xd014); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(20 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 32); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_common_copy_bytes(&buffer_ptr[0], (uint8_t*)product_name, + product_name_size); + return local_error; +} + +int16_t sen66_get_serial_number(uint8_t* serial_number, + uint16_t serial_number_size) { + int16_t 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, 0xd033); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(20 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 32); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_common_copy_bytes(&buffer_ptr[0], (uint8_t*)serial_number, + serial_number_size); + return local_error; +} + +int16_t sen66_get_version(uint8_t* firmware_major, uint8_t* firmware_minor, + bool* firmware_debug, uint8_t* hardware_major, + uint8_t* hardware_minor, uint8_t* protocol_major, + uint8_t* protocol_minor, uint8_t* padding) { + int16_t 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, 0xd000); + local_error = + sensirion_i2c_write_data(_i2c_address, buffer_ptr, local_offset); + if (local_error != NO_ERROR) { + return local_error; + } + sensirion_i2c_hal_sleep_usec(20 * 1000); + local_error = sensirion_i2c_read_data_inplace(_i2c_address, buffer_ptr, 8); + if (local_error != NO_ERROR) { + return local_error; + } + *firmware_major = (uint8_t)buffer_ptr[0]; + *firmware_minor = (uint8_t)buffer_ptr[1]; + *firmware_debug = (bool)buffer_ptr[2]; + *hardware_major = (uint8_t)buffer_ptr[3]; + *hardware_minor = (uint8_t)buffer_ptr[4]; + *protocol_major = (uint8_t)buffer_ptr[5]; + *protocol_minor = (uint8_t)buffer_ptr[6]; + *padding = (uint8_t)buffer_ptr[7]; + return local_error; +} diff --git a/sen66_i2c.h b/sen66_i2c.h new file mode 100644 index 0000000..e88b470 Binary files /dev/null and b/sen66_i2c.h differ diff --git a/sensirion_common.c b/sensirion_common.c new file mode 100644 index 0000000..3cab3c1 --- /dev/null +++ b/sensirion_common.c @@ -0,0 +1,120 @@ +/* + * 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_common.h" +#include "sensirion_config.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; // we do not read at all if data_length is bigger than + // the provided integer! + } + + // pad missing bytes + 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/sensirion_common.h b/sensirion_common.h new file mode 100644 index 0000000..e290933 --- /dev/null +++ b/sensirion_common.h @@ -0,0 +1,197 @@ +/* + * 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_COMMON_H +#define SENSIRION_COMMON_H + +#include "sensirion_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NO_ERROR 0 +#define NOT_IMPLEMENTED_ERROR 31 + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) +#endif + +#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/sensirion_config.h b/sensirion_config.h new file mode 100644 index 0000000..1e88ceb --- /dev/null +++ b/sensirion_config.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019, 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_CONFIG_H +#define SENSIRION_CONFIG_H + +/** + * If your platform does not provide the library stdlib.h you have to remove the + * include and define NULL yourself (see below). + */ +#include + +/** + * #ifndef NULL + * #define NULL ((void *)0) + * #endif + */ + +/** + * If your platform does not provide the library stdint.h you have to + * define the integral types yourself (see below). + */ +#include + +/** + * Typedef section for types commonly defined in + * If your system does not provide stdint headers, please define them + * accordingly. Please make sure to define int64_t and uint64_t. + */ +/* typedef unsigned long long int uint64_t; + * typedef long long int int64_t; + * typedef long int32_t; + * typedef unsigned long uint32_t; + * typedef short int16_t; + * typedef unsigned short uint16_t; + * typedef char int8_t; + * typedef unsigned char uint8_t; + */ + +#ifndef __cplusplus + +/** + * If your platform doesn't define the bool type we define it as int. Depending + * on your system update the definition below. + */ +#if __STDC_VERSION__ >= 199901L +#include +#else + +#ifndef bool +#define bool int +#define true 1 +#define false 0 +#endif /* bool */ + +#endif /* __STDC_VERSION__ */ + +#endif /* __cplusplus */ + +#endif /* SENSIRION_CONFIG_H */ diff --git a/sensirion_i2c.c b/sensirion_i2c.c new file mode 100644 index 0000000..5f709fc --- /dev/null +++ b/sensirion_i2c.c @@ -0,0 +1,307 @@ +/* + * 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.h" +#include "sensirion_common.h" +#include "sensirion_config.h" +#include "sensirion_i2c_hal.h" + +uint8_t sensirion_i2c_generate_crc(const uint8_t* data, uint16_t count) { + uint16_t current_byte; + uint8_t crc = CRC8_INIT; + uint8_t crc_bit; + + /* calculates 8-Bit checksum with given polynomial */ + for (current_byte = 0; current_byte < count; ++current_byte) { + crc ^= (data[current_byte]); + for (crc_bit = 8; crc_bit > 0; --crc_bit) { + if (crc & 0x80) + crc = (crc << 1) ^ CRC8_POLYNOMIAL; + else + crc = (crc << 1); + } + } + 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; +} + +int16_t sensirion_i2c_general_call_reset(void) { + const uint8_t data = 0x06; + return sensirion_i2c_hal_write(0, &data, (uint16_t)sizeof(data)); +} + +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; +} + +int16_t sensirion_i2c_read_words_as_bytes(uint8_t address, uint8_t* data, + uint16_t num_words) { + int16_t ret; + 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; + + ret = sensirion_i2c_hal_read(address, buf8, size); + if (ret != NO_ERROR) + return ret; + + /* check the CRC for each word */ + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) { + + ret = sensirion_i2c_check_crc(&buf8[i], SENSIRION_WORD_SIZE, + buf8[i + SENSIRION_WORD_SIZE]); + if (ret != NO_ERROR) + return ret; + + data[j++] = buf8[i]; + data[j++] = buf8[i + 1]; + } + + return NO_ERROR; +} + +int16_t sensirion_i2c_read_words(uint8_t address, uint16_t* data_words, + uint16_t num_words) { + int16_t ret; + uint8_t i; + + ret = sensirion_i2c_read_words_as_bytes(address, (uint8_t*)data_words, + num_words); + if (ret != NO_ERROR) + return ret; + + 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; +} + +int16_t sensirion_i2c_write_cmd(uint8_t address, uint16_t command) { + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, command, NULL, 0); + return sensirion_i2c_hal_write(address, buf, SENSIRION_COMMAND_SIZE); +} + +int16_t sensirion_i2c_write_cmd_with_args(uint8_t address, 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 sensirion_i2c_hal_write(address, buf, buf_size); +} + +int16_t sensirion_i2c_delayed_read_cmd(uint8_t address, uint16_t cmd, + uint32_t delay_us, uint16_t* data_words, + uint16_t num_words) { + int16_t ret; + uint8_t buf[SENSIRION_COMMAND_SIZE]; + + sensirion_i2c_fill_cmd_send_buf(buf, cmd, NULL, 0); + ret = sensirion_i2c_hal_write(address, buf, SENSIRION_COMMAND_SIZE); + if (ret != NO_ERROR) + return ret; + + if (delay_us) + sensirion_i2c_hal_sleep_usec(delay_us); + + return sensirion_i2c_read_words(address, data_words, num_words); +} + +int16_t sensirion_i2c_read_cmd(uint8_t address, uint16_t cmd, + uint16_t* data_words, uint16_t num_words) { + return sensirion_i2c_delayed_read_cmd(address, 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; +} + +int16_t sensirion_i2c_write_data(uint8_t address, const uint8_t* data, + uint16_t data_length) { + return sensirion_i2c_hal_write(address, data, data_length); +} + +int16_t sensirion_i2c_read_data_inplace(uint8_t address, uint8_t* buffer, + uint16_t expected_data_length) { + int16_t 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 = sensirion_i2c_hal_read(address, 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/sensirion_i2c.h b/sensirion_i2c.h new file mode 100644 index 0000000..45ff5c3 --- /dev/null +++ b/sensirion_i2c.h @@ -0,0 +1,348 @@ +/* + * 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_H +#define SENSIRION_I2C_H + +#include "sensirion_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CRC_ERROR 1 +#define I2C_BUS_ERROR 2 +#define I2C_NACK_ERROR 3 +#define BYTE_NUM_ERROR 4 + +#define CRC8_POLYNOMIAL 0x31 +#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. + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_general_call_reset(void); + +/** + * 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. + * @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). + * @cmd: The i2c command to send. It already includes a checksum. + * @args: The arguments to the command. Can be NULL if none. + * @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 + * + * @address: Sensor i2c address + * @data_words: Allocated buffer to store the read words. + * The buffer may also have been modified in case of an error. + * @num_words: Number of data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_words(uint8_t address, 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. + * + * @address: Sensor i2c address + * @data: Allocated buffer to store the read bytes. + * The buffer may also have been modified in case of an error. + * @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 + */ +int16_t sensirion_i2c_read_words_as_bytes(uint8_t address, uint8_t* data, + uint16_t num_words); + +/** + * sensirion_i2c_write_cmd() - writes a command to the sensor + * @address: Sensor i2c address + * @command: Sensor command + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_write_cmd(uint8_t address, uint16_t command); + +/** + * sensirion_i2c_write_cmd_with_args() - writes a command with arguments to the + * sensor + * @address: Sensor i2c address + * @command: Sensor command + * @data: Argument buffer with words to send + * @num_words: Number of data words to send (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_write_cmd_with_args(uint8_t address, 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 + * @address: Sensor i2c address + * @cmd: Command + * @delay: Time in microseconds to delay sending the read request + * @data_words: Allocated buffer to store the read data + * @num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_delayed_read_cmd(uint8_t address, 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 + * @address: Sensor i2c address + * @cmd: Command + * @data_words: Allocated buffer to store the read data + * @num_words: Data words to read (without CRC bytes) + * + * @return NO_ERROR on success, an error code otherwise + */ +int16_t sensirion_i2c_read_cmd(uint8_t address, 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 address I2C address to write to. + * @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 + */ +int16_t sensirion_i2c_write_data(uint8_t address, const uint8_t* data, + uint16_t data_length); + +/** + * sensirion_i2c_read_data_inplace() - Reads data from the Sensor. + * + * @param address Sensor I2C address + * @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 + */ +int16_t sensirion_i2c_read_data_inplace(uint8_t address, uint8_t* buffer, + uint16_t expected_data_length); +#ifdef __cplusplus +} +#endif + +#endif /* SENSIRION_I2C_H */ diff --git a/sensirion_i2c_hal.c b/sensirion_i2c_hal.c new file mode 100644 index 0000000..d368cf2 --- /dev/null +++ b/sensirion_i2c_hal.c @@ -0,0 +1,118 @@ +/* + * 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" + +/* + * 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(void) { + /* TODO:IMPLEMENT */ +} + +/** + * 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, uint8_t count) { + /* TODO:IMPLEMENT */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * 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, + uint8_t count) { + /* TODO:IMPLEMENT */ + return NOT_IMPLEMENTED_ERROR; +} + +/** + * 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) { + /* TODO:IMPLEMENT */ +} diff --git a/sensirion_i2c_hal.h b/sensirion_i2c_hal.h new file mode 100644 index 0000000..d6267b8 --- /dev/null +++ b/sensirion_i2c_hal.h @@ -0,0 +1,112 @@ +/* + * 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" + +#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(void); + +/** + * 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, uint8_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, + uint8_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/tests/Makefile b/tests/Makefile new file mode 100644 index 0000000..5467dcc --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,53 @@ + +# use the second argument of make as argument to control test bed initialization +TEST_ARG := $(wordlist 2, 2, $(MAKECMDGOALS)) +# ...turn it into do-nothing target +$(eval $(TEST_ARG):;@:) + + ifeq ($(TEST_ARG), mux) + driver_dir := .. + mux_dir := ../i2c-mux-testbed + i2c_mux_sources = ${mux_dir}/i2c_mux.h ${mux_dir}/i2c_mux.c + macros := "-D USE_MUX" + else + mux_dir := . + i2c_mux_sources = + macros := + endif + +driver_dir := .. +common_sources = $(driver_dir)/sensirion_config.h $(driver_dir)/sensirion_common.h $(driver_dir)/sensirion_common.c +i2c_sources = $(driver_dir)/sensirion_i2c_hal.h ${driver_dir}/sensirion_i2c.h $(driver_dir)/sensirion_i2c.c +sensirion_test_sources = sensirion_test_setup.cpp $(i2c_mux_sources) + +sw_i2c_dir := $(driver_dir)/sample-implementations/GPIO_bit_banging + +hw_i2c_impl_src = $(driver_dir)/sample-implementations/linux_user_space/sensirion_i2c_hal.c +sw_i2c_impl_src = $(sw_i2c_dir)/sample-implementations/linux_user_space/sensirion_i2c_gpio.c $(sw_i2c_dir)/sensirion_i2c_hal.c + +sen66_sources = $(driver_dir)/sen66_i2c.h $(driver_dir)/sen66_i2c.c + +CXXFLAGS ?= $(CFLAGS) -fsanitize=address -I$(mux_dir) -I$(driver_dir) -I$(sw_i2c_dir) ${macros} +ifdef CI + CXXFLAGS += -Werror +endif +LDFLAGS ?= -lasan -lstdc++ -lCppUTest -lCppUTestExt + +.PHONY: clean test + + +sen66_test_binaries := sen66_test_hw_i2c sen66_test_sw_i2c + +all: $(sen66_test_binaries) + +sen66_test_hw_i2c: sen66_i2c_test.cpp $(sen66_sources) $(sensirion_test_sources) $(i2c_sources) $(hw_i2c_impl_src) $(common_sources) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +sen66_test_sw_i2c: sen66_i2c_test.cpp $(sen66_sources) $(sensirion_test_sources) $(i2c_sources) $(sw_i2c_impl_src) $(common_sources) + $(CXX) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) + +test: $(sen66_test_binaries) + set -ex; for test in $(sen66_test_binaries); do echo $${test}; ./$${test}; echo; done; + +clean: + $(RM) $(sen66_test_binaries) diff --git a/tests/sen66_i2c_test.cpp b/tests/sen66_i2c_test.cpp new file mode 100644 index 0000000..5399ef3 --- /dev/null +++ b/tests/sen66_i2c_test.cpp @@ -0,0 +1,126 @@ +/* + * THIS FILE IS AUTOMATICALLY GENERATED + * + * Generator: sensirion-driver-generator 1.0.1 + * Product: sen66 + * Model-Version: 1.2.0 + */ + +#include "sen66_i2c.h" +#include "sensirion_common.h" +#include "sensirion_i2c.h" +#include "sensirion_i2c_hal.h" +#include "sensirion_test_setup.h" +#include +#include + +#define sensirion_hal_sleep_us sensirion_i2c_hal_sleep_usec + +void print_byte_array(uint8_t* array, uint16_t len) { + uint16_t i = 0; + printf("0x"); + for (; i < len; i++) { + printf("%02x", array[i]); + } +} + +TEST_GROUP (SEN66_Tests) { + void setup() { + sen66_init(0x6B); + } +}; + +TEST (SEN66_Tests, test_perform_forced_co2_recalibration1) { + int16_t local_error = 0; + uint16_t correction = 0; + local_error = sen66_perform_forced_co2_recalibration(600, &correction); + CHECK_EQUAL_ZERO_TEXT(local_error, "perform_forced_co2_recalibration"); + printf("correction: %u\n", correction); +} + +TEST (SEN66_Tests, test_get_product_name1) { + int16_t local_error = 0; + uint8_t product_name[32] = {0}; + local_error = sen66_get_product_name(product_name, 32); + CHECK_EQUAL_ZERO_TEXT(local_error, "get_product_name"); + printf("product_name: "); + print_byte_array(product_name, 32); + printf("\n"); +} + +TEST (SEN66_Tests, test_get_serial_number1) { + int16_t local_error = 0; + uint8_t serial_number[32] = {0}; + local_error = sen66_get_serial_number(serial_number, 32); + CHECK_EQUAL_ZERO_TEXT(local_error, "get_serial_number"); + printf("serial_number: "); + print_byte_array(serial_number, 32); + printf("\n"); +} + +TEST (SEN66_Tests, test_get_version1) { + int16_t local_error = 0; + uint8_t firmware_major = 0; + uint8_t firmware_minor = 0; + bool firmware_debug = false; + uint8_t hardware_major = 0; + uint8_t hardware_minor = 0; + uint8_t protocol_major = 0; + uint8_t protocol_minor = 0; + uint8_t padding = 0; + local_error = sen66_get_version( + &firmware_major, &firmware_minor, &firmware_debug, &hardware_major, + &hardware_minor, &protocol_major, &protocol_minor, &padding); + CHECK_EQUAL_ZERO_TEXT(local_error, "get_version"); + printf("firmware_major: %u ", firmware_major); + printf("firmware_minor: %u ", firmware_minor); + printf("firmware_debug: %d ", firmware_debug); + printf("hardware_major: %u ", hardware_major); + printf("hardware_minor: %u ", hardware_minor); + printf("protocol_major: %u ", protocol_major); + printf("protocol_minor: %u ", protocol_minor); + printf("padding: %u\n", padding); +} + +TEST (SEN66_Tests, test_device_reset1) { + int16_t local_error = 0; + local_error = sen66_device_reset(); + CHECK_EQUAL_ZERO_TEXT(local_error, "device_reset"); +} + +TEST (SEN66_Tests, test_start_continuous_measurement1) { + int16_t local_error = 0; + uint16_t mass_concentration_pm1p0 = 0; + uint16_t mass_concentration_pm2p5 = 0; + uint16_t mass_concentration_pm4p0 = 0; + uint16_t mass_concentration_pm10p0 = 0; + int16_t ambient_humidity = 0; + int16_t ambient_temperature = 0; + int16_t voc_index = 0; + int16_t nox_index = 0; + uint16_t co2 = 0; + uint8_t padding = 0; + bool data_ready = false; + local_error = sen66_start_continuous_measurement(); + CHECK_EQUAL_ZERO_TEXT(local_error, "start_continuous_measurement"); + local_error = sen66_read_measured_values_as_integers( + &mass_concentration_pm1p0, &mass_concentration_pm2p5, + &mass_concentration_pm4p0, &mass_concentration_pm10p0, + &ambient_humidity, &ambient_temperature, &voc_index, &nox_index, &co2); + CHECK_EQUAL_ZERO_TEXT(local_error, "read_measured_values_as_integers"); + printf("mass_concentration_pm1p0: %u ", mass_concentration_pm1p0); + printf("mass_concentration_pm2p5: %u ", mass_concentration_pm2p5); + printf("mass_concentration_pm4p0: %u ", mass_concentration_pm4p0); + printf("mass_concentration_pm10p0: %u ", mass_concentration_pm10p0); + printf("ambient_humidity: %i ", ambient_humidity); + printf("ambient_temperature: %i ", ambient_temperature); + printf("voc_index: %i ", voc_index); + printf("nox_index: %i ", nox_index); + printf("co2: %u\n", co2); + local_error = sen66_get_data_ready(&padding, &data_ready); + CHECK_EQUAL_ZERO_TEXT(local_error, "get_data_ready"); + printf("padding: %u ", padding); + printf("data_ready: %d\n", data_ready); + local_error = sen66_stop_measurement(); + CHECK_EQUAL_ZERO_TEXT(local_error, "stop_measurement"); +} diff --git a/tests/sensirion_test_setup.cpp b/tests/sensirion_test_setup.cpp new file mode 100644 index 0000000..e47f022 --- /dev/null +++ b/tests/sensirion_test_setup.cpp @@ -0,0 +1,21 @@ +#include "sensirion_test_setup.h" +#include "CppUTest/CommandLineTestRunner.h" +#include "sensirion_i2c_hal.h" + +#ifdef USE_MUX +#include "i2c_mux.h" +#define INIT_TESTBED(x, y) sensirion_i2c_mux_set_single_channel((x), (y)) +#else +#define INIT_TESTBED(x, y) 0 +#endif + +#define MUX_CHANNEL 0x71 + +int main(int argc, char** argv) { + sensirion_i2c_hal_init(); + int16_t error = INIT_TESTBED(MUX_CHANNEL, 1); + CHECK_EQUAL_ZERO_TEXT(error, "test-bed initialization failed"); + int result = CommandLineTestRunner::RunAllTests(argc, argv); + sensirion_i2c_hal_free(); + return result; +} diff --git a/tests/sensirion_test_setup.h b/tests/sensirion_test_setup.h new file mode 100644 index 0000000..448eccc --- /dev/null +++ b/tests/sensirion_test_setup.h @@ -0,0 +1,20 @@ +#ifndef SENSIRION_TEST_SETUP_H +#define SENSIRION_TEST_SETUP_H + +#include + +#include "CppUTest/TestHarness.h" + +// provide macros which will only be available in a future cpputest release +// See https://github.com/cpputest/cpputest/pull/1423 +#ifndef CHECK_EQUAL_ZERO +#define CHECK_EQUAL_ZERO(actual) CHECK_EQUAL(0, (actual)) +#endif +#ifndef CHECK_EQUAL_ZERO_TEXT +#define CHECK_EQUAL_ZERO_TEXT(actual, text) \ + CHECK_EQUAL_TEXT(0, (actual), (text)) +#endif + +int main(int argc, char** argv); + +#endif /* SENSIRION_TEST_SETUP_H */