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/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/.github/workflows/raspberry_quality_check.yml b/.github/workflows/raspberry_quality_check.yml new file mode 100644 index 0000000..95e97ee --- /dev/null +++ b/.github/workflows/raspberry_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/.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..45d1857 --- /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/raspberry-pi-i2c-sen66/compare/0.1.0...HEAD +[0.1.0]: https://github.com/Sensirion/raspberry-pi-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..997df08 --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# Sensirion Raspberry Pi I²C SEN66 Driver + +The repository provides a driver for setting up a SEN66 sensor to run on a Raspberry Pi over I²C. + + + +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**. + + + +## Connect the sensor + +Your sensor has 4 different connectors: VDD, GND, SDA, SCL. +Use the following pins to connect your SEN66: + +| *SEN66* | *Cable Color* | *Raspberry Pi* | +| :----------------: | -------------- | ------------------ | +| VDD | red | Pin 1 +| GND | black | Pin 6 +| SDA | green | Pin 3 +| SCL | yellow | Pin 5 + + + + + +### Detailed sensor pinout + + + +| *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 | + + + +## Quick start example + +- [Install the Raspberry Pi OS on to your Raspberry Pi](https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up) +- [Enable the I²C interface in the raspi-config](https://www.raspberrypi.org/documentation/configuration/raspi-config.md) +- Download the SEN66 driver from [Github](https://github.com/Sensirion/raspberry-pi-i2c-sen66) and extract the `.zip` on your Raspberry Pi +- Connect the SEN66 sensor as explained in the [section above](#connect-the-sensor) + +- Compile the driver + 1. Open a [terminal](https://projects.raspberrypi.org/en/projects/raspberry-pi-using/8) + 2. Navigate to the driver directory. E.g. `cd ~/raspberry-pi-i2c-sen66` + 3. Navigate to the subdirectory example-usage. + 4. Run the `make` command to compile the driver + + Output: + ``` + rm -f sen66_i2c_example_usage + cc -Os -Wall -fstrict-aliasing -Wstrict-aliasing=1 -Wsign-conversion -fPIC -I. -o sen66_i2c_example_usage sen66_i2c.h sen66_i2c.c sensirion_i2c_hal.h sensirion_i2c.h sensirion_i2c.c \ + sensirion_i2c_hal.c sensirion_config.h sensirion_common.h sensirion_common.c sen66_i2c_example_usage.c + ``` +- Test your connected sensor + - Run `./sen66_i2c_example_usage` in the same directory you used to + compile the driver. You should see the measurement values in the console. + +## Troubleshooting + +### Building driver failed + +If the execution of `make` in the compilation step 3 fails with something like + +```bash + make: command not found +``` + +your RaspberryPi likely does not have the build tools installed. Proceed as follows: + +``` +$ sudo apt-get update +$ sudo apt-get upgrade +$ sudo apt-get install build-essential +``` + + +### Initialization failed + +If you run `./sen66_i2c_example_usage` but do not get sensor readings but something like this instead + +``` +Error executing device_reset(): -1 +Error executing get_serial_number(): -1 +Error executing start_continuous_measurement(): -1 +... +``` +then go through the below troubleshooting steps. + + +- Ensure that you connected the sensor correctly: All cables are fully + plugged in and connected to the correct pin. +- Ensure that I²C is enabled on the Raspberry Pi. For this redo the steps on + "Enable the I²C interface in the raspi-config" in the guide above. +- Ensure that your user account has read and write access to the I²C device. + If it only works with user root (`sudo ./sen66_i2c_example_usage`), it's + typically due to wrong permission settings. See the next chapter how to solve this. + +### Missing I²C permissions + +If your user is missing access to the I²C interface you should first verfiy +the user belongs to the `i2c` group. + +``` +$ groups +users input some other groups etc +``` +If `i2c` is missing in the list add the user and restart the Raspberry Pi. + +``` +$ sudo adduser your-user i2c +Adding user `your-user' to group `i2c' ... +Adding user your-user to group i2c +Done. +$ sudo reboot +``` + +If that did not help you can make globally accessible hardware interfaces +with a udev rule. Only do this if everything else failed and you are +reasoably confident you are the only one having access to your Pi. + +Go into the `/etc/udev/rules.d` folder and add a new file named +`local.rules`. +``` +$ cd /etc/udev/rules.d/ +$ sudo touch local.rules +``` +Then add a single line `ACTION=="add", KERNEL=="i2c-[0-1]*", MODE="0666"` +to the file with your favorite editor. +``` +$ sudo vi local.rules +``` + +## 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..922e729 --- /dev/null +++ b/example-usage/sen66_i2c_example_usage.c @@ -0,0 +1,115 @@ +/* + * 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; + } + float mass_concentration_pm1p0 = 0.0; + float mass_concentration_pm2p5 = 0.0; + float mass_concentration_pm4p0 = 0.0; + float mass_concentration_pm10p0 = 0.0; + float humidity = 0.0; + float temperature = 0.0; + float voc_index = 0.0; + float nox_index = 0.0; + float co2 = 0.0; + uint16_t repetition = 0; + for (repetition = 0; repetition < 50; repetition++) { + sensirion_hal_sleep_us(1000000); + error = sen66_read_measured_values( + &mass_concentration_pm1p0, &mass_concentration_pm2p5, + &mass_concentration_pm4p0, &mass_concentration_pm10p0, &humidity, + &temperature, &voc_index, &nox_index, &co2); + if (error != NO_ERROR) { + printf("error executing read_measured_values(): %i\n", error); + continue; + } + printf("mass_concentration_pm1p0: %.2f ", mass_concentration_pm1p0); + printf("mass_concentration_pm2p5: %.2f ", mass_concentration_pm2p5); + printf("mass_concentration_pm4p0: %.2f ", mass_concentration_pm4p0); + printf("mass_concentration_pm10p0: %.2f ", mass_concentration_pm10p0); + printf("humidity: %.2f ", humidity); + printf("temperature: %.2f ", temperature); + printf("voc_index: %.2f ", voc_index); + printf("nox_index: %.2f ", nox_index); + printf("co2: %.2f\n", co2); + } + + error = sen66_stop_measurement(); + if (error != NO_ERROR) { + return error; + } + return NO_ERROR; +} diff --git a/images/raspi-i2c-pinout-3.3V.png b/images/raspi-i2c-pinout-3.3V.png new file mode 100644 index 0000000..c4cef1e Binary files /dev/null and b/images/raspi-i2c-pinout-3.3V.png differ 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/sen66_i2c.c b/sen66_i2c.c new file mode 100644 index 0000000..8ef547d --- /dev/null +++ b/sen66_i2c.c @@ -0,0 +1,351 @@ +/* + * 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; +} + +float sen66_signal_mass_concentration_pm1p0( + uint16_t mass_concentration_pm1p0_raw) { + float mass_concentration_pm1p0 = 0.0; + mass_concentration_pm1p0 = mass_concentration_pm1p0_raw / 10.0; + return mass_concentration_pm1p0; +} + +float sen66_signal_mass_concentration_pm2p5( + uint16_t mass_concentration_pm2p5_raw) { + float mass_concentration_pm2p5 = 0.0; + mass_concentration_pm2p5 = mass_concentration_pm2p5_raw / 10.0; + return mass_concentration_pm2p5; +} + +float sen66_signal_mass_concentration_pm4p0( + uint16_t mass_concentration_pm4p0_raw) { + float mass_concentration_pm4p0 = 0.0; + mass_concentration_pm4p0 = mass_concentration_pm4p0_raw / 10.0; + return mass_concentration_pm4p0; +} + +float sen66_signal_mass_concentration_pm10p0( + uint16_t mass_concentration_pm10p0_raw) { + float mass_concentration_pm10p0 = 0.0; + mass_concentration_pm10p0 = mass_concentration_pm10p0_raw / 10.0; + return mass_concentration_pm10p0; +} + +float sen66_signal_temperature(int16_t temperature_raw) { + float temperature = 0.0; + temperature = temperature_raw / 200.0; + return temperature; +} + +float sen66_signal_humidity(int16_t humidity_raw) { + float humidity = 0.0; + humidity = humidity_raw / 100.0; + return humidity; +} + +float sen66_signal_voc_index(int16_t voc_index_raw) { + float voc_index = 0.0; + voc_index = voc_index_raw / 10.0; + return voc_index; +} + +float sen66_signal_nox_index(int16_t nox_index_raw) { + float nox_index = 0.0; + nox_index = nox_index_raw / 10.0; + return nox_index; +} + +float sen66_signal_co2(uint16_t co2_raw) { + float co2 = 0.0; + co2 = co2_raw / 1.0; + return co2; +} + +int16_t sen66_read_measured_values(float* mass_concentration_pm1p0, + float* mass_concentration_pm2p5, + float* mass_concentration_pm4p0, + float* mass_concentration_pm10p0, + float* humidity, float* temperature, + float* voc_index, float* nox_index, + float* co2) { + uint16_t mass_concentration_pm1p0_raw = 0; + uint16_t mass_concentration_pm2p5_raw = 0; + uint16_t mass_concentration_pm4p0_raw = 0; + uint16_t mass_concentration_pm10p0_raw = 0; + int16_t humidity_raw = 0; + int16_t temperature_raw = 0; + int16_t voc_index_raw = 0; + int16_t nox_index_raw = 0; + uint16_t co2_raw = 0; + int16_t local_error = 0; + local_error = sen66_read_measured_values_as_integers( + &mass_concentration_pm1p0_raw, &mass_concentration_pm2p5_raw, + &mass_concentration_pm4p0_raw, &mass_concentration_pm10p0_raw, + &humidity_raw, &temperature_raw, &voc_index_raw, &nox_index_raw, + &co2_raw); + if (local_error != NO_ERROR) { + return local_error; + } + *mass_concentration_pm1p0 = + sen66_signal_mass_concentration_pm1p0(mass_concentration_pm1p0_raw); + *mass_concentration_pm2p5 = + sen66_signal_mass_concentration_pm2p5(mass_concentration_pm2p5_raw); + *mass_concentration_pm4p0 = + sen66_signal_mass_concentration_pm4p0(mass_concentration_pm4p0_raw); + *mass_concentration_pm10p0 = + sen66_signal_mass_concentration_pm10p0(mass_concentration_pm10p0_raw); + *humidity = sen66_signal_humidity(humidity_raw); + *temperature = sen66_signal_temperature(temperature_raw); + *voc_index = sen66_signal_voc_index(voc_index_raw); + *nox_index = sen66_signal_nox_index(nox_index_raw); + *co2 = sen66_signal_co2(co2_raw); + return local_error; +} + +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..e07b047 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..74a7f57 --- /dev/null +++ b/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/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 */