├── .github └── workflows │ ├── run-ci-arduino.yml │ ├── stale.yml │ └── sync_issues.yml ├── .gitlab-ci.yml ├── .travis.yml ├── README.md ├── examples ├── absolute_humidity_example │ └── absolute_humidity_example.ino ├── base_example │ └── base_example.ino └── baseline_operation_example │ └── baseline_operation_example.ino ├── i2c.h ├── keywords.txt ├── library.properties ├── pictures ├── Get baseline program flow chart .png └── absolute humidity with the formula.png ├── sensirion_common.c ├── sensirion_common.h ├── sensirion_configuration.cpp ├── sensirion_configuration.h ├── sgp30.c ├── sgp30.h ├── sgp30_featureset.c └── sgp_featureset.h /.github/workflows/run-ci-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Run Ci Arduino 2 | 3 | on: 4 | push: 5 | pull_request: 6 | repository_dispatch: 7 | types: [trigger-workflow] 8 | 9 | jobs: 10 | ci-arduino: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Checkout script repository 18 | uses: actions/checkout@v4 19 | with: 20 | repository: Seeed-Studio/ci-arduino 21 | path: ci 22 | 23 | - name: Setup arduino cli 24 | uses: arduino/setup-arduino-cli@v2.0.0 25 | 26 | - name: Create a depend.list file 27 | run: | 28 | # eg: echo "" >> depend.list 29 | 30 | - name: Create a ignore.list file 31 | run: | 32 | # eg: echo "," >> ignore.list 33 | echo "baseline_operation_example,Seeeduino:samd:seeed_XIAO_m0" >> ignore.list 34 | echo "baseline_operation_example,Seeeduino:nrf52:xiaonRF52840" >> ignore.list 35 | echo "baseline_operation_example,Seeeduino:nrf52:xiaonRF52840Sense" >> ignore.list 36 | 37 | - name: Build sketch 38 | run: ./ci/tools/compile.sh 39 | 40 | - name: Build result 41 | run: | 42 | cat build.log 43 | if [ ${{ github.event_name }} == 'pull_request' ] && [ -f compile.failed ]; then 44 | exit 1 45 | fi 46 | 47 | - name: Generate issue 48 | if: ${{ github.event_name != 'pull_request' }} 49 | run: ./ci/tools/issue.sh 50 | env: 51 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues and PRs' 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 4 * * *' 7 | 8 | jobs: 9 | stale: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Checkout script repository 17 | uses: actions/checkout@v4 18 | with: 19 | repository: Seeed-Studio/sync-github-all-issues 20 | path: ci 21 | 22 | - name: Run script 23 | run: ./ci/tools/stale.sh 24 | env: 25 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/workflows/sync_issues.yml: -------------------------------------------------------------------------------- 1 | name: Automate Issue Management 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - edited 8 | - assigned 9 | - unassigned 10 | - labeled 11 | - unlabeled 12 | - reopened 13 | 14 | jobs: 15 | add_issue_to_project: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Add issue to GitHub Project 19 | uses: actions/add-to-project@v1.0.2 20 | with: 21 | project-url: https://github.com/orgs/Seeed-Studio/projects/17 22 | github-token: ${{ secrets.ISSUE_ASSEMBLE }} 23 | labeled: bug 24 | label-operator: NOT -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | build: 2 | tags: 3 | - nas 4 | script: 5 | - wget -c https://files.seeedstudio.com/arduino/seeed-arduino-ci.sh 6 | - chmod +x seeed-arduino-ci.sh 7 | - bash $PWD/seeed-arduino-ci.sh test 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: generic 3 | dist: bionic 4 | sudo: false 5 | cache: 6 | directories: 7 | - ~/arduino_ide 8 | - ~/.arduino15/packages/ 9 | 10 | before_install: 11 | - wget -c https://files.seeedstudio.com/arduino/seeed-arduino-ci.sh 12 | 13 | script: 14 | - chmod +x seeed-arduino-ci.sh 15 | - cat $PWD/seeed-arduino-ci.sh 16 | - bash $PWD/seeed-arduino-ci.sh test 17 | 18 | notifications: 19 | email: 20 | on_success: change 21 | on_failure: change 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SGP30_Gas_Sensor [![Build Status](https://travis-ci.com/Seeed-Studio/SGP30_Gas_Sensor.svg?branch=master)](https://travis-ci.com/Seeed-Studio/SGP30_Gas_Sensor) 2 | ================== 3 | A arduino example for SGP30_Gas_Sensor,Measurement of TVOC and CO2 4 | ------------------------------------------------------------------ 5 | *** 6 | Introduction of sensor 7 | ---------------------- 8 | The SGP30 is a digital multi-pixel gas sensor designed foreasy integration into air purifier, 9 | demand-controlledventilation, and IoT applications. Sensirion’s CMOSens®technology offers a 10 | complete sensor system on a single chip featuring a digital I2C interface, a temperature 11 | controlled micro hotplate, and two preprocessed indoor air quality signals. As the first 12 | metal-oxide gas sensor featuring multiple sensing elements on one chip, the SGP30 provides 13 | more detailed information about the air quality. 14 | 15 | *** 16 | Usage: 17 | =========== 18 | Download all the source files and open examples/ * / *.ino in arduino IDE. 19 | Compile and download and run it on a arduino board. 20 | 21 | Notice: 22 | ---------- 23 | * **The SGP30 uses a dynamic baseline compensation algorithm and on-chip calibration parameters to provide two 24 | complementary air quality signals. The baseline should be stored in EEPROM.When there is no baseline value 25 | in EEPROM at the first time power-ON or the baseline record is older than seven days.The sensor has to run 26 | for 12 hours until the baseline can be stored. 27 | You can refer to program flow chart blow** 28 | ![baseline operation](https://github.com/linux-downey/SGP30_Gas_Sensor/blob/master/pictures/Get%20baseline%20program%20flow%20chart%20.png) 29 | * **The H2_Signal and Ethanol_signal,Both signals can be used to calculate gas concentrations c relative to a reference concentration cref by 30 | ln(C/Cref)=(Sref-Sout)/a 31 | with a = 512, sref the H2_signal or Ethanol_signal output at the reference concentration, and sout = Sout_H2 or Sout = Sout_EthOH.** 32 | * **For more accurate measurement,You can set the abslute humidity compensation,Defalt value is 11.57g/m3,A little troublesome is 33 | that you should get relatively humidity value of environment from another way,Because there is no humidity measurement part integrated in SGP30.. 34 | ![Humidity caculation formula](https://github.com/linux-downey/SGP30_Gas_Sensor/blob/master/pictures/absolute%20humidity%20with%20the%20formula.png) 35 | Luckly, It's not much neccessary in a normal situation** 36 | * ***All relevant example is in examples/,To get more detail*** 37 | 38 | reference: 39 | ============ 40 | * [SGP30 Datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9_Gas_Sensors/Sensirion_Gas_Sensors_SGP30_Datasheet_EN.pdf) 41 | * [Driver Integration](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9_Gas_Sensors/Sensirion_Gas_Sensors_SGP30_Driver-Integration-Guide_HW_I2C.pdf) 42 | 43 | Or you can also visit [sensirion|home](https://www.sensirion.com/cn/environmental-sensors/gas-sensors/multi-pixel-gas-sensors/) to get more information about SGP30 44 | 45 | 46 | *** 47 | This software is written by downey for seeed studio
48 | Email:dao.huang@seeed.cc 49 | and is licensed under [The MIT License](http://opensource.org/licenses/mit-license.php). Check License.txt for more information.
50 | 51 | Contributing to this software is warmly welcomed. You can do this basically by
52 | [forking](https://help.github.com/articles/fork-a-repo), committing modifications and then [pulling requests](https://help.github.com/articles/using-pull-requests) (follow the links above
53 | for operating guide). Adding change log and your contact into file header is encouraged.
54 | Thanks for your contribution. 55 | 56 | Seeed Studio is an open hardware facilitation company based in Shenzhen, China.
57 | Benefiting from local manufacture power and convenient global logistic system,
58 | we integrate resources to serve new era of innovation. Seeed also works with
59 | global distributors and partners to push open hardware movement.
60 | 61 | 62 | [![Analytics](https://ga-beacon.appspot.com/UA-46589105-3/CAN_BUS_Shield)](https://github.com/igrigorik/ga-beacon) 63 | -------------------------------------------------------------------------------- /examples/absolute_humidity_example/absolute_humidity_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include "sensirion_common.h" 3 | #include "sgp30.h" 4 | 5 | 6 | void setup() { 7 | s16 err; 8 | u32 ah = 0; 9 | u16 scaled_ethanol_signal, scaled_h2_signal; 10 | Serial.begin(115200); 11 | Serial.println("serial start!!"); 12 | 13 | /*For wio link!*/ 14 | #if defined(ESP8266) 15 | pinMode(15, OUTPUT); 16 | digitalWrite(15, 1); 17 | Serial.println("Set wio link power!"); 18 | delay(500); 19 | #endif 20 | /* Init module,Reset all baseline,The initialization takes up to around 15 seconds, during which 21 | all APIs measuring IAQ(Indoor air quality ) output will not change.Default value is 400(ppm) for co2,0(ppb) for tvoc*/ 22 | while (sgp_probe() != STATUS_OK) { 23 | Serial.println("SGP failed"); 24 | while (1); 25 | } 26 | /*Read H2 and Ethanol signal in the way of blocking*/ 27 | err = sgp_measure_signals_blocking_read(&scaled_ethanol_signal, 28 | &scaled_h2_signal); 29 | if (err == STATUS_OK) { 30 | Serial.println("get ram signal!"); 31 | } else { 32 | Serial.println("error reading signals"); 33 | } 34 | 35 | //ah=get_relative_humidity(); 36 | /* 37 | The function sgp_set_absolute_humidity() need your own implementation 38 | */ 39 | //sgp_set_absolute_humidity(ah); 40 | 41 | // Set absolute humidity to 13.000 g/m^3 42 | //It's just a test value 43 | sgp_set_absolute_humidity(13000); 44 | err = sgp_iaq_init(); 45 | // 46 | } 47 | 48 | void loop() { 49 | s16 err = 0; 50 | u16 tvoc_ppb, co2_eq_ppm; 51 | err = sgp_measure_iaq_blocking_read(&tvoc_ppb, &co2_eq_ppm); 52 | if (err == STATUS_OK) { 53 | Serial.print("tVOC Concentration:"); 54 | Serial.print(tvoc_ppb); 55 | Serial.println("ppb"); 56 | 57 | Serial.print("CO2eq Concentration:"); 58 | Serial.print(co2_eq_ppm); 59 | Serial.println("ppm"); 60 | } else { 61 | Serial.println("error reading IAQ values\n"); 62 | } 63 | delay(1000); 64 | } 65 | -------------------------------------------------------------------------------- /examples/base_example/base_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "sensirion_common.h" 4 | #include "sgp30.h" 5 | 6 | 7 | void setup() { 8 | s16 err; 9 | u16 scaled_ethanol_signal, scaled_h2_signal; 10 | Serial.begin(115200); 11 | Serial.println("serial start!!"); 12 | 13 | /*For wio link!*/ 14 | #if defined(ESP8266) 15 | pinMode(15, OUTPUT); 16 | digitalWrite(15, 1); 17 | Serial.println("Set wio link power!"); 18 | delay(500); 19 | #endif 20 | /* Init module,Reset all baseline,The initialization takes up to around 15 seconds, during which 21 | all APIs measuring IAQ(Indoor air quality ) output will not change.Default value is 400(ppm) for co2,0(ppb) for tvoc*/ 22 | while (sgp_probe() != STATUS_OK) { 23 | Serial.println("SGP failed"); 24 | while (1); 25 | } 26 | /*Read H2 and Ethanol signal in the way of blocking*/ 27 | err = sgp_measure_signals_blocking_read(&scaled_ethanol_signal, 28 | &scaled_h2_signal); 29 | if (err == STATUS_OK) { 30 | Serial.println("get ram signal!"); 31 | } else { 32 | Serial.println("error reading signals"); 33 | } 34 | err = sgp_iaq_init(); 35 | // 36 | } 37 | 38 | void loop() { 39 | s16 err = 0; 40 | u16 tvoc_ppb, co2_eq_ppm; 41 | err = sgp_measure_iaq_blocking_read(&tvoc_ppb, &co2_eq_ppm); 42 | if (err == STATUS_OK) { 43 | Serial.print("tVOC Concentration:"); 44 | Serial.print(tvoc_ppb); 45 | Serial.println("ppb"); 46 | 47 | Serial.print("CO2eq Concentration:"); 48 | Serial.print(co2_eq_ppm); 49 | Serial.println("ppm"); 50 | } else { 51 | Serial.println("error reading IAQ values\n"); 52 | } 53 | delay(1000); 54 | } 55 | -------------------------------------------------------------------------------- /examples/baseline_operation_example/baseline_operation_example.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "sensirion_common.h" 5 | #include "sgp30.h" 6 | 7 | #define LOOP_TIME_INTERVAL_MS 1000 8 | #define BASELINE_IS_STORED_FLAG (0X55) 9 | //#define ARRAY_TO_U32(a) (a[0]<<24|a[1]<<16|a[2]<<8|a[3]) //MSB first //Not suitable for 8-bit platform 10 | 11 | void array_to_u32(u32* value, u8* array) { 12 | (*value) = (*value) | (u32)array[0] << 24; 13 | (*value) = (*value) | (u32)array[1] << 16; 14 | (*value) = (*value) | (u32)array[2] << 8; 15 | (*value) = (*value) | (u32)array[3]; 16 | } 17 | void u32_to_array(u32 value, u8* array) { 18 | if (!array) { 19 | return; 20 | } 21 | array[0] = value >> 24; 22 | array[1] = value >> 16; 23 | array[2] = value >> 8; 24 | array[3] = value; 25 | } 26 | 27 | /* 28 | Reset baseline per hour,store it in EEPROM; 29 | */ 30 | void store_baseline(void) { 31 | static u32 i = 0; 32 | u32 j = 0; 33 | u32 iaq_baseline = 0; 34 | u8 value_array[4] = {0}; 35 | i++; 36 | Serial.println(i); 37 | if (i == 3600) { 38 | i = 0; 39 | if (sgp_get_iaq_baseline(&iaq_baseline) != STATUS_OK) { 40 | Serial.println("get baseline failed!"); 41 | } else { 42 | Serial.println(iaq_baseline, HEX); 43 | Serial.println("get baseline"); 44 | u32_to_array(iaq_baseline, value_array); 45 | for (j = 0; j < 4; j++) { 46 | EEPROM.write(j, value_array[j]); 47 | Serial.print(value_array[j]); 48 | Serial.println("..."); 49 | } 50 | EEPROM.write(j, BASELINE_IS_STORED_FLAG); 51 | } 52 | } 53 | delay(LOOP_TIME_INTERVAL_MS); 54 | } 55 | 56 | /* Read baseline from EEPROM and set it.If there is no value in EEPROM,retrun . 57 | Another situation: When the baseline record in EEPROM is older than seven days,Discard it and return!! 58 | 59 | */ 60 | void set_baseline(void) { 61 | u32 i = 0; 62 | u8 baseline[5] = {0}; 63 | u32 baseline_value = 0; 64 | for (i = 0; i < 5; i++) { 65 | baseline[i] = EEPROM.read(i); 66 | Serial.print(baseline[i], HEX); 67 | Serial.print(".."); 68 | } 69 | Serial.println("!!!"); 70 | if (baseline[4] != BASELINE_IS_STORED_FLAG) { 71 | Serial.println("There is no baseline value in EEPROM"); 72 | return; 73 | } 74 | /* 75 | if(baseline record in EEPROM is older than seven days) 76 | { 77 | return; 78 | } 79 | */ 80 | array_to_u32(&baseline_value, baseline); 81 | sgp_set_iaq_baseline(baseline_value); 82 | Serial.println(baseline_value, HEX); 83 | } 84 | 85 | 86 | 87 | 88 | void setup() { 89 | s16 err; 90 | u16 scaled_ethanol_signal, scaled_h2_signal; 91 | Serial.begin(115200); 92 | Serial.println("serial start!!"); 93 | 94 | /*For wio link!*/ 95 | #if defined(ESP8266) 96 | pinMode(15, OUTPUT); 97 | digitalWrite(15, 1); 98 | Serial.println("Set wio link power!"); 99 | delay(500); 100 | #endif 101 | 102 | /* Init module,Reset all baseline,The initialization takes up to around 15 seconds, during which 103 | all APIs measuring IAQ(Indoor air quality ) output will not change.Default value is 400(ppm) for co2,0(ppb) for tvoc*/ 104 | while (sgp_probe() != STATUS_OK) { 105 | Serial.println("SGP failed"); 106 | while (1); 107 | } 108 | /*Read H2 and Ethanol signal in the way of blocking*/ 109 | err = sgp_measure_signals_blocking_read(&scaled_ethanol_signal, 110 | &scaled_h2_signal); 111 | if (err == STATUS_OK) { 112 | Serial.println("get ram signal!"); 113 | } else { 114 | Serial.println("error reading signals"); 115 | } 116 | // err = sgp_iaq_init(); 117 | set_baseline(); 118 | // 119 | } 120 | 121 | void loop() { 122 | s16 err = 0; 123 | u16 tvoc_ppb, co2_eq_ppm; 124 | err = sgp_measure_iaq_blocking_read(&tvoc_ppb, &co2_eq_ppm); 125 | if (err == STATUS_OK) { 126 | Serial.print("tVOC Concentration:"); 127 | Serial.print(tvoc_ppb); 128 | Serial.println("ppb"); 129 | 130 | Serial.print("CO2eq Concentration:"); 131 | Serial.print(co2_eq_ppm); 132 | Serial.println("ppm"); 133 | } else { 134 | Serial.println("error reading IAQ values\n"); 135 | } 136 | store_baseline(); 137 | } 138 | -------------------------------------------------------------------------------- /i2c.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef I2C_H 32 | #define I2C_H 33 | 34 | #include "sensirion_configuration.h" 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | void sensirion_i2c_init(void); 41 | 42 | int8_t sensirion_i2c_read(uint8_t address, uint8_t* data, uint16_t count); 43 | 44 | int8_t sensirion_i2c_write(uint8_t address, const uint8_t* data, uint16_t count); 45 | 46 | void sensirion_sleep_usec(uint32_t useconds); 47 | 48 | #ifdef __cplusplus 49 | } 50 | #endif 51 | 52 | #endif /* I2C_H */ 53 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | 2 | ####################################### 3 | # Syntax Coloring Map For Seeed Wio GPS Board 4 | ####################################### 5 | ####################################### 6 | # Datatypes (KEYWORD1) 7 | ####################################### 8 | 9 | GPSTracker KEYWORD1 10 | BlueTooth KEYWORD1 11 | GNSS KEYWORD1 12 | GPRS KEYWORD1 13 | serialMC20 KEYWORD1 14 | serialDebug KEYWORD1 15 | 16 | ####################################### 17 | # Methods and Functions (KEYWORD2) 18 | ####################################### 19 | Check_If_Power_On KEYWORD2 20 | Power_On KEYWORD2 21 | powerReset KEYWORD2 22 | io_init KEYWORD2 23 | waitForNetworkRegister KEYWORD2 24 | sendSMS KEYWORD2 25 | readSMS KEYWORD2 26 | deleteSMS KEYWORD2 27 | callUp KEYWORD2 28 | answer KEYWORD2 29 | hangup KEYWORD2 30 | getSignalStrength KEYWORD2 31 | recv KEYWORD2 32 | GSM_work_mode KEYWORD2 33 | GSM_config_slow_clk KEYWORD2 34 | AT_PowerDown KEYWORD2 35 | checkSIMStatus KEYWORD2 36 | 37 | init KEYWORD2 38 | join KEYWORD2 39 | BTPowerOn KEYWORD2 40 | BTPowerOff KEYWORD2 41 | scanForTargetDevice KEYWORD2 42 | sendPairingReqstToDevice KEYWORD2 43 | acceptPairing KEYWORD2 44 | unPair KEYWORD2 45 | acceptConnect KEYWORD2 46 | loopHandle KEYWORD2 47 | disconnect KEYWORD2 48 | getBTState KEYWORD2 49 | getPairedDeviceID KEYWORD2 50 | BTConnectPairedDevice KEYWORD2 51 | BTFastConnect KEYWORD2 52 | 53 | Check_If_Power_On KEYWORD2 54 | networkCheck KEYWORD2 55 | connectTCP KEYWORD2 56 | sendTCPData KEYWORD2 57 | closeTCP KEYWORD2 58 | 59 | initialize KEYWORD2 60 | open_GNSS KEYWORD2 61 | close_GNSS KEYWORD2 62 | open_GNSS_default_mode KEYWORD2 63 | open_GNSS_EPO_quick_mode KEYWORD2 64 | open_GNSS_EPO_LP_mode KEYWORD2 65 | open_GNSS_RL_mode KEYWORD2 66 | oopen_GNSS KEYWORD2 67 | settingContext KEYWORD2 68 | isNetworkRegistered KEYWORD2 69 | isTimeSynchronized KEYWORD2 70 | enableEPO KEYWORD2 71 | triggerEPO KEYWORD2 72 | getCoordinate KEYWORD2 73 | dataFlowMode KEYWORD2 74 | 75 | Enable_EASY KEYWORD2 76 | Enable_GLP KEYWORD2 77 | Set_DGPS_Mode KEYWORD2 78 | GetQueryStatus_LOCUS KEYWORD2 79 | EraseFlash_LOCUS KEYWORD2 80 | StopLogger_LOCUS KEYWORD2 81 | QueryData_LOCUS KEYWORD2 82 | SetPeriodicMode KEYWORD2 83 | Set1PPS KEYWORD2 84 | SetAlwaysLocateMode KEYWORD2 85 | Select_searching_satellite KEYWORD2 86 | SetWorkMode KEYWORD2 87 | SetStandbyMode KEYWORD2 88 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=Seeed Arduino SGP30 2 | version=1.0.0 3 | author=Seeed Studio 4 | maintainer=Seeed Studio 5 | sentence=Arduino library to control Grove SGP30_Gas_Sensor. 6 | paragraph=Arduino library to control Grove SGP30_Gas_Sensor. 7 | category=Sensors 8 | url=https://github.com/Seeed-Studio/SGP30_Gas_Sensor 9 | architectures=* 10 | -------------------------------------------------------------------------------- /pictures/Get baseline program flow chart .png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seeed-Studio/Seeed_Arduino_SGP30/a7451a81f8f7f0de05102c205c02c4a6219ad9a1/pictures/Get baseline program flow chart .png -------------------------------------------------------------------------------- /pictures/absolute humidity with the formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Seeed-Studio/Seeed_Arduino_SGP30/a7451a81f8f7f0de05102c205c02c4a6219ad9a1/pictures/absolute humidity with the formula.png -------------------------------------------------------------------------------- /sensirion_common.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Sensirion AG 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * * Neither the name of Sensirion AG nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | /** 32 | * \file 33 | * 34 | * This module provides functionality that is common to all Sensirion drivers 35 | */ 36 | 37 | #include "sensirion_common.h" 38 | 39 | #ifdef __cplusplus 40 | extern "C" { 41 | #endif 42 | 43 | u8 sensirion_common_generate_crc(u8 *data, u16 count) 44 | { 45 | u16 current_byte; 46 | u8 crc = CRC8_INIT; 47 | u8 crc_bit; 48 | 49 | /* calculates 8-Bit checksum with given polynomial */ 50 | for (current_byte = 0; current_byte < count; ++current_byte) { 51 | crc ^= (data[current_byte]); 52 | for (crc_bit = 8; crc_bit > 0; --crc_bit) { 53 | if (crc & 0x80) 54 | crc = (crc << 1) ^ CRC8_POLYNOMIAL; 55 | else 56 | crc = (crc << 1); 57 | } 58 | } 59 | return crc; 60 | } 61 | 62 | s8 sensirion_common_check_crc(u8 *data, u16 count, u8 checksum) 63 | { 64 | if (sensirion_common_generate_crc(data, count) != checksum) 65 | return STATUS_FAIL; 66 | return STATUS_OK; 67 | } 68 | #ifdef __cplusplus 69 | } // extern "C" 70 | #endif 71 | -------------------------------------------------------------------------------- /sensirion_common.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef SENSIRION_COMMON_H 32 | #define SENSIRION_COMMON_H 33 | 34 | #include "sensirion_configuration.h" 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | #define STATUS_OK 0 41 | #define STATUS_FAIL (-1) 42 | 43 | #ifndef NULL 44 | #define NULL ((void *)0) 45 | #endif 46 | 47 | #ifdef BIG_ENDIAN 48 | #define be16_to_cpu(s) (s) 49 | #define be32_to_cpu(s) (s) 50 | #define be64_to_cpu(s) (s) 51 | #else 52 | #define be16_to_cpu(s) (((u16)(s) << 8) | (0xff & ((u16)(s)) >> 8)) 53 | #define be32_to_cpu(s) (((u32)be16_to_cpu(s) << 16) | \ 54 | (0xffff & (be16_to_cpu((s) >> 16)))) 55 | #define be64_to_cpu(s) (((u64)be32_to_cpu(s) << 32) | \ 56 | (0xffffffff & ((u64)be32_to_cpu((s) >> 32)))) 57 | #endif 58 | 59 | #ifndef ARRAY_SIZE 60 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) 61 | #endif 62 | 63 | #define CRC8_POLYNOMIAL 0x31 64 | #define CRC8_INIT 0xFF 65 | #define CRC8_LEN 1 66 | 67 | u8 sensirion_common_generate_crc(u8* data, u16 count); 68 | 69 | s8 sensirion_common_check_crc(u8* data, u16 count, u8 checksum); 70 | 71 | #ifdef __cplusplus 72 | } 73 | #endif 74 | 75 | #endif /* SENSIRION_COMMON_H */ 76 | 77 | 78 | -------------------------------------------------------------------------------- /sensirion_configuration.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "i2c.h" 32 | // needed for delay() routine 33 | #include 34 | #include 35 | 36 | /* 37 | INSTRUCTIONS 38 | ============ 39 | 40 | Use the "arduino_example.ino" and copy 41 | sht.h, shtxx.c, sht_common.h, sht_common.c, i2c.h, 42 | hw_i2c/configuration.h, hw_i2c/configuration_Arduino.cpp 43 | in the .ino folder. 44 | Follow the function specification in the comments. 45 | */ 46 | 47 | #ifdef __cplusplus 48 | extern "C" { 49 | #endif 50 | 51 | /** 52 | Initialize all hard- and software components that are needed for the I2C 53 | communication. After this function has been called, the functions 54 | i2c_read() and i2c_write() must succeed. 55 | */ 56 | void sensirion_i2c_init() { 57 | Wire.begin(); 58 | } 59 | 60 | int8_t sensirion_i2c_read(uint8_t address, uint8_t* data, uint16_t count) { 61 | uint8_t readData[count]; 62 | uint8_t rxByteCount = 0; 63 | 64 | // 2 bytes RH, 1 CRC, 2 bytes T, 1 CRC 65 | Wire.requestFrom(address, count); 66 | 67 | while (Wire.available()) { // wait till all arrive 68 | readData[rxByteCount++] = Wire.read(); 69 | if (rxByteCount >= count) { 70 | break; 71 | } 72 | } 73 | 74 | memcpy(data, readData, count); 75 | 76 | return 0; 77 | } 78 | 79 | int8_t sensirion_i2c_write(uint8_t address, const uint8_t* data, uint16_t count) { 80 | Wire.beginTransmission(address); 81 | Wire.write(data, count); 82 | Wire.endTransmission(); 83 | 84 | return 0; 85 | } 86 | 87 | /** 88 | Sleep for a given number of microseconds. The function should delay the 89 | execution for at least the given time, but may also sleep longer. 90 | 91 | @param useconds the sleep time in microseconds 92 | */ 93 | void sensirion_sleep_usec(uint32_t useconds) { 94 | delay((useconds / 1000) + 1); 95 | } 96 | 97 | #ifdef __cplusplus 98 | } // extern "C" 99 | #endif 100 | -------------------------------------------------------------------------------- /sensirion_configuration.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef SENSIRION_CONFIGURATION_H 32 | #define SENSIRION_CONFIGURATION_H 33 | 34 | #ifdef __cplusplus 35 | extern "C" { 36 | #endif 37 | 38 | /** 39 | The I2C address of the SGP sensor. Only uncomment if you have a special 40 | version of a sensor. 41 | */ 42 | /* #define SGP_ADDRESS 0x58 */ 43 | 44 | /** 45 | The clock period of the i2c bus in microseconds. Increase this, if your GPIO 46 | ports cannot support a 200 kHz output rate. 47 | */ 48 | #define I2C_CLOCK_PERIOD_USEC 10 49 | 50 | /** 51 | Typedef section for types commonly defined in 52 | You are welcome to use #include instead and discard 53 | the list by uncommenting the following #define. In this case, please make 54 | sure that int64_t and uint64_t are also defined! 55 | */ 56 | #include 57 | #define USE_SENSIRION_STDINT_TYPES 0 58 | 59 | /** 60 | Uncomment the following line to enable clock stretching. 61 | Note that clock stretching will keep the bus busy until the sensor releases 62 | it and will impact the communication with other i2c devices on the same bus. 63 | */ 64 | /* #define USE_SENSIRION_CLOCK_STRETCHING 1 */ 65 | 66 | /** 67 | Set USE_SENSIRION_STDINT_TYPES to 0 if your platform already has a stdint 68 | implementation. 69 | */ 70 | #ifndef USE_SENSIRION_STDINT_TYPES 71 | #define USE_SENSIRION_STDINT_TYPES 1 72 | #endif /* USE_SENSIRION_STDINT_TYPES */ 73 | 74 | /** 75 | Set USE_SENSIRION_SHORT_TYPES to 0 if your platform already provides the 76 | type definitions u8, u16, u32, u64, s8, s16, s32, s64 77 | */ 78 | #ifndef USE_SENSIRION_SHORT_TYPES 79 | #define USE_SENSIRION_SHORT_TYPES 1 80 | #endif /* USE_SENSIRION_SHORT_TYPES */ 81 | 82 | #if USE_SENSIRION_STDINT_TYPES 83 | typedef unsigned long long int uint64_t; 84 | typedef long long int int64_t; 85 | typedef long int32_t; 86 | typedef unsigned long uint32_t; 87 | typedef short int16_t; 88 | typedef unsigned short uint16_t; 89 | typedef char int8_t; 90 | typedef unsigned char uint8_t; 91 | #endif /* USE_SENSIRION_STDINT_TYPES */ 92 | 93 | #if USE_SENSIRION_SHORT_TYPES 94 | typedef int64_t s64; 95 | typedef uint64_t u64; 96 | typedef int32_t s32; 97 | typedef uint32_t u32; 98 | typedef int16_t s16; 99 | typedef unsigned short u16; 100 | typedef int8_t s8; 101 | typedef uint8_t u8; 102 | #endif 103 | 104 | #ifndef USE_SENSIRION_CLOCK_STRETCHING 105 | #define USE_SENSIRION_CLOCK_STRETCHING 0 106 | #endif /* USE_SENSIRION_CLOCK_STRETCHING */ 107 | 108 | #ifdef __cplusplus 109 | } 110 | #endif 111 | 112 | #endif /* SENSIRION_CONFIGURATION_H */ 113 | -------------------------------------------------------------------------------- /sgp30.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Sensirion AG 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * * Neither the name of Sensirion AG nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #include "i2c.h" 32 | #include "sensirion_configuration.h" 33 | #include "sensirion_common.h" 34 | #include "sgp_featureset.h" 35 | #include "sgp30.h" 36 | 37 | 38 | #ifdef __cplusplus 39 | extern "C" { 40 | #endif 41 | 42 | 43 | 44 | #define SGP_DRV_VERSION_STR "2.2.2" 45 | #define SGP_RAM_WORDS 200 46 | //#define SGP_RAM_WORDS 512 47 | #define SGP_BUFFER_SIZE ((SGP_RAM_WORDS + 2) * \ 48 | (SGP_WORD_LEN + CRC8_LEN)) 49 | #define SGP_BUFFER_WORDS (SGP_BUFFER_SIZE / SGP_WORD_LEN) 50 | #define SGP_MAX_PROFILE_RET_LEN 1024 51 | //#define SGP_MAX_PROFILE_RET_LEN 1024 52 | #define SGP_VALID_IAQ_BASELINE(b) ((b) != 0) 53 | /** 54 | * Check if chip featureset is compatible with driver featureset: 55 | * Check product type mask 0xF000, 56 | * ignore reserved bits 0x0E00, 57 | * eng. bit not set 0x0100, 58 | * major version matches 0x00E0, 59 | * ignore minor version 0x001F if major version > 0 */ 60 | #define SGP_FS_COMPAT(chip_fs, drv_fs) ( \ 61 | ( /* major version > 0, ignore minor version */ \ 62 | (((drv_fs) & 0x00E0) > 0) && \ 63 | (((chip_fs) & 0xF1E0) == ((drv_fs) & 0xF1E0)) \ 64 | ) || \ 65 | ( /* major version == 0, check for minor version match */ \ 66 | (((drv_fs) & 0x00E0) == 0) && \ 67 | (((chip_fs) & 0xF1FF) == ((drv_fs) & 0xF1FF)) \ 68 | )) 69 | 70 | #ifdef SGP_ADDRESS 71 | static const uint8_t SGP_I2C_ADDRESS = SGP_ADDRESS; 72 | #else 73 | static const uint8_t SGP_I2C_ADDRESS = 0x58; 74 | #endif 75 | 76 | /* command and constants for reading the serial ID */ 77 | #define SGP_CMD_GET_SERIAL_ID_DURATION_US 500 78 | #define SGP_CMD_GET_SERIAL_ID_WORDS 2 79 | static const sgp_command sgp_cmd_get_serial_id = { 80 | .buf = {0x36, 0x82} 81 | }; 82 | 83 | /* command and constants for reading the featureset version */ 84 | #define SGP_CMD_GET_FEATURESET_DURATION_US 1000 85 | #define SGP_CMD_GET_FEATURESET_WORDS 1 86 | static const sgp_command sgp_cmd_get_featureset = { 87 | .buf = {0x20, 0x2f} 88 | }; 89 | 90 | /* command and constants for on-chip self-test */ 91 | #define SGP_CMD_MEASURE_TEST_DURATION_US 220000 92 | #define SGP_CMD_MEASURE_TEST_WORDS 1 93 | #define SGP_CMD_MEASURE_TEST_OK 0xd400 94 | static const sgp_command sgp_cmd_measure_test = { 95 | .buf = {0x20, 0x32} 96 | }; 97 | 98 | static const struct sgp_otp_featureset sgp_features_unknown = { 99 | .profiles = NULL, 100 | .number_of_profiles = 0, 101 | }; 102 | 103 | enum sgp_state_code { 104 | WAIT_STATE, 105 | MEASURING_PROFILE_STATE 106 | }; 107 | 108 | struct sgp_info { 109 | u64 serial_id; 110 | u16 feature_set_version; 111 | }; 112 | 113 | static struct sgp_data { 114 | enum sgp_state_code current_state; 115 | struct sgp_info info; 116 | const struct sgp_otp_featureset *otp_features; 117 | u16 word_buf[SGP_BUFFER_WORDS]; 118 | } client_data; 119 | 120 | 121 | /** 122 | * sgp_i2c_read_words() - read data words from SGP sensor 123 | * @data: Allocated buffer to store the read data. 124 | * The buffer may also have been modified on STATUS_FAIL return. 125 | * @data_words: Number of data words to read (without CRC bytes) 126 | * 127 | * Return: STATUS_OK on success, STATUS_FAIL otherwise 128 | */ 129 | static s16 sgp_i2c_read_words(u16 *data, u16 data_words) { 130 | s16 ret; 131 | u16 i, j; 132 | u16 size = data_words * (SGP_WORD_LEN + CRC8_LEN); 133 | u16 word_buf[SGP_MAX_PROFILE_RET_LEN / sizeof(u16)]; 134 | u8 * const buf8 = (u8 *)word_buf; 135 | 136 | ret = sensirion_i2c_read(SGP_I2C_ADDRESS, buf8, size); 137 | 138 | if (ret != 0) 139 | return STATUS_FAIL; 140 | 141 | /* check the CRC for each word */ 142 | for (i = 0, j = 0; 143 | i < size; 144 | i += SGP_WORD_LEN + CRC8_LEN, j += SGP_WORD_LEN) { 145 | 146 | if (sensirion_common_check_crc(&buf8[i], SGP_WORD_LEN, 147 | buf8[i + SGP_WORD_LEN]) == STATUS_FAIL) { 148 | return STATUS_FAIL; 149 | } 150 | ((u8 *)data)[j] = buf8[i]; 151 | ((u8 *)data)[j + 1] = buf8[i + 1]; 152 | } 153 | 154 | return STATUS_OK; 155 | } 156 | 157 | 158 | /** 159 | * sgp_i2c_write() - writes to the SGP sensor 160 | * @command: Command 161 | * 162 | * Return: STATUS_OK on success. 163 | */ 164 | static s16 sgp_i2c_write(const sgp_command *command) { 165 | s8 ret; 166 | 167 | ret = sensirion_i2c_write(SGP_I2C_ADDRESS, command->buf, SGP_COMMAND_LEN); 168 | if (ret != 0) 169 | return STATUS_FAIL; 170 | 171 | return STATUS_OK; 172 | } 173 | 174 | 175 | /** 176 | * unpack_signals() - unpack signals which are stored in client_data.word_buf 177 | * @profile: The profile 178 | */ 179 | static void unpack_signals(const struct sgp_profile *profile) { 180 | s16 i, j; 181 | const struct sgp_signal *signal; 182 | u16 data_words = profile->number_of_signals; 183 | u16 word_buf[data_words]; 184 | u16 value; 185 | 186 | /* copy buffer */ 187 | for (i = 0; i < data_words; i++) 188 | word_buf[i] = client_data.word_buf[i]; 189 | 190 | /* signals are in reverse order in the data buffer */ 191 | for (i = profile->number_of_signals - 1, j = 0; i >= 0; i -= 1, j += 1) { 192 | signal = profile->signals[profile->number_of_signals - i - 1]; 193 | value = be16_to_cpu(word_buf[i]); 194 | 195 | if (signal->conversion_function != NULL) 196 | client_data.word_buf[j] = signal->conversion_function(value); 197 | else 198 | client_data.word_buf[j] = value; 199 | } 200 | } 201 | 202 | /** 203 | * read_measurement() - reads the result of a profile measurement 204 | * 205 | * Return: Length of the written data to the buffer. Negative if it fails. 206 | */ 207 | static s16 read_measurement(const struct sgp_profile *profile) { 208 | 209 | s16 ret; 210 | 211 | switch (client_data.current_state) { 212 | 213 | case MEASURING_PROFILE_STATE: 214 | ret = sgp_i2c_read_words(client_data.word_buf, 215 | profile->number_of_signals); 216 | 217 | if (ret) 218 | /* Measurement in progress */ 219 | return STATUS_FAIL; 220 | 221 | unpack_signals(profile); 222 | client_data.current_state = WAIT_STATE; 223 | 224 | return STATUS_OK; 225 | 226 | default: 227 | /* No command issued */ 228 | return STATUS_FAIL; 229 | } 230 | } 231 | 232 | /** 233 | * sgp_i2c_read_words_from_cmd() - reads data words from the SGP sensor after a 234 | * command has been issued 235 | * @cmd: Command 236 | * @data_words: Allocated buffer to store the read data 237 | * @num_words: Data words to read (without CRC bytes) 238 | * 239 | * Return: STATUS_OK on success, else STATUS_FAIL 240 | */ 241 | static s16 sgp_i2c_read_words_from_cmd(const sgp_command *cmd, 242 | u32 duration_us, 243 | u16 *data_words, 244 | u16 num_words) { 245 | 246 | if (sgp_i2c_write(cmd) == STATUS_FAIL) 247 | return STATUS_FAIL; 248 | 249 | /* the chip needs some time to write the data into the RAM */ 250 | sensirion_sleep_usec(duration_us); 251 | return sgp_i2c_read_words(data_words, num_words); 252 | } 253 | 254 | 255 | /** 256 | * sgp_run_profile() - run a profile and read write its return to client_data 257 | * @profile A pointer to the profile 258 | * 259 | * Return: STATUS_OK on success, else STATUS_FAIL 260 | */ 261 | static s16 sgp_run_profile(const struct sgp_profile *profile) { 262 | u32 duration_us = profile->duration_us + 5; 263 | 264 | if (sgp_i2c_write(&profile->command) == STATUS_FAIL) 265 | return STATUS_FAIL; 266 | 267 | sensirion_sleep_usec(duration_us); 268 | 269 | if (profile->number_of_signals > 0) { 270 | client_data.current_state = MEASURING_PROFILE_STATE; 271 | return read_measurement(profile); 272 | } 273 | 274 | return STATUS_OK; 275 | } 276 | 277 | 278 | /** 279 | * sgp_get_profile_by_number() - get a profile by its identifier number 280 | * @number The number that identifies the profile 281 | * 282 | * Return: A pointer to the profile or NULL if the profile does not exists 283 | */ 284 | static const struct sgp_profile *sgp_get_profile_by_number(u16 number) { 285 | u8 i; 286 | const struct sgp_profile *profile = NULL; 287 | 288 | for (i = 0; i < client_data.otp_features->number_of_profiles; i++) { 289 | profile = client_data.otp_features->profiles[i]; 290 | if (number == profile->number) 291 | break; 292 | } 293 | 294 | if (i == client_data.otp_features->number_of_profiles) { 295 | return NULL; 296 | } 297 | 298 | return profile; 299 | } 300 | 301 | 302 | /** 303 | * sgp_run_profile_by_number() - run a profile by its identifier number 304 | * @number: The number that identifies the profile 305 | * 306 | * Return: STATUS_OK on success, else STATUS_FAIL 307 | */ 308 | static s16 sgp_run_profile_by_number(u16 number) { 309 | const struct sgp_profile *profile; 310 | 311 | profile = sgp_get_profile_by_number(number); 312 | if (profile == NULL) 313 | return STATUS_FAIL; 314 | 315 | if (sgp_run_profile(profile) == STATUS_FAIL) 316 | return STATUS_FAIL; 317 | 318 | return STATUS_OK; 319 | } 320 | 321 | 322 | /** 323 | * sgp_fill_cmd_send_buf() - create the i2c send buffer for a command and a set 324 | * of argument words. 325 | * The output buffer interleaves argument words with 326 | * their checksums. 327 | * @buf: The generated buffer to send over i2c. Then buffer length must 328 | * be at least SGP_COMMAND_LEN + num_args * (SGP_WORD_LEN + 329 | * CRC8_LEN). 330 | * @cmd: The sgp i2c command to send. It already includes a checksum. 331 | * @args: The arguments to the command. Can be NULL if none. 332 | * @num_args: The number of word arguments in args. 333 | * Return: Bytes written to buf: SGP_COMMAND_LEN + num_args * 334 | * (SGP_WORD_LEN + CRC8_LEN). 335 | */ 336 | static u16 sgp_fill_cmd_send_buf(u8 *buf, const sgp_command *cmd, 337 | const u16 *args, u8 num_args) { 338 | u16 word; 339 | u8 crc; 340 | u8 i; 341 | u8 idx = 0; 342 | 343 | buf[idx++] = cmd->buf[0]; 344 | buf[idx++] = cmd->buf[1]; 345 | 346 | for (i = 0; i < num_args; ++i) { 347 | word = be16_to_cpu(args[i]); 348 | crc = sensirion_common_generate_crc((u8 *)&word, SGP_WORD_LEN); 349 | 350 | buf[idx++] = (u8)((word & 0x00FF) >> 0); 351 | buf[idx++] = (u8)((word & 0xFF00) >> 8); 352 | buf[idx++] = crc; 353 | } 354 | return idx; 355 | } 356 | 357 | 358 | /** 359 | * sgp_detect_featureset_version() - extracts the featureset and initializes 360 | * client_data. 361 | * 362 | * @featureset: Pointer to the featureset bits 363 | * 364 | * Return: STATUS_OK on success 365 | */ 366 | static s16 sgp_detect_featureset_version(u16 *featureset) { 367 | s16 i, j; 368 | s16 ret = STATUS_FAIL; 369 | u16 feature_set_version = be16_to_cpu(*featureset); 370 | const struct sgp_otp_featureset *sgp_featureset; 371 | 372 | client_data.info.feature_set_version = feature_set_version; 373 | client_data.otp_features = &sgp_features_unknown; 374 | for (i = 0; i < sgp_supported_featuresets.number_of_supported_featuresets; ++i) { 375 | sgp_featureset = sgp_supported_featuresets.featuresets[i]; 376 | for (j = 0; j < sgp_featureset->number_of_supported_featureset_versions; ++j) { 377 | if (SGP_FS_COMPAT(feature_set_version, 378 | sgp_featureset->supported_featureset_versions[j])) { 379 | client_data.otp_features = sgp_featureset; 380 | ret = STATUS_OK; 381 | break; 382 | } 383 | } 384 | } 385 | return ret; 386 | } 387 | 388 | 389 | /** 390 | * sgp_measure_test() - Run the on-chip self-test 391 | * 392 | * This method is executed synchronously and blocks for the duration of the 393 | * measurement (~220ms) 394 | * 395 | * @test_result: Allocated buffer to store the chip's error code. 396 | * test_result is SGP_CMD_MEASURE_TEST_OK on success or set to 397 | * zero (0) in the case of a communication error. 398 | * 399 | * Return: STATUS_OK on a successful self-test, STATUS_FAIL otherwise. 400 | */ 401 | s16 sgp_measure_test(u16 *test_result) { 402 | u16 measure_test_word_buf[SGP_CMD_MEASURE_TEST_WORDS]; 403 | 404 | *test_result = 0; 405 | 406 | if (sgp_i2c_write(&sgp_cmd_measure_test) != STATUS_OK) 407 | return STATUS_FAIL; 408 | 409 | sensirion_sleep_usec(SGP_CMD_MEASURE_TEST_DURATION_US); 410 | 411 | if (sgp_i2c_read_words(measure_test_word_buf, 412 | SGP_CMD_MEASURE_TEST_WORDS) != STATUS_OK) 413 | return STATUS_FAIL; 414 | 415 | *test_result = be16_to_cpu(*measure_test_word_buf); 416 | if (*test_result == SGP_CMD_MEASURE_TEST_OK) 417 | return STATUS_OK; 418 | 419 | return STATUS_FAIL; 420 | } 421 | 422 | 423 | /** 424 | * sgp_measure_iaq() - Measure IAQ values async 425 | * 426 | * The profile is executed asynchronously. Use sgp_read_iaq to get the 427 | * values. 428 | * 429 | * Return: STATUS_OK on success, else STATUS_FAIL 430 | */ 431 | s16 sgp_measure_iaq() { 432 | const struct sgp_profile *profile; 433 | 434 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_IAQ_MEASURE); 435 | if (profile == NULL) { 436 | return STATUS_FAIL; 437 | } 438 | 439 | if (sgp_i2c_write(&(profile->command)) == STATUS_FAIL) 440 | return STATUS_FAIL; 441 | client_data.current_state = MEASURING_PROFILE_STATE; 442 | 443 | return STATUS_OK; 444 | } 445 | 446 | 447 | /** 448 | * sgp_read_iaq() - Read IAQ values async 449 | * 450 | * Read the IAQ values. This command can only be exectued after a measurement 451 | * has started with sgp_measure_iaq and is finished. 452 | * 453 | * @tvoc_ppb: The tVOC ppb value will be written to this location 454 | * @co2_eq_ppm: The CO2-Equivalent ppm value will be written to this location 455 | * 456 | * Return: STATUS_OK on success, else STATUS_FAIL 457 | */ 458 | s16 sgp_read_iaq(u16 *tvoc_ppb, u16 *co2_eq_ppm) { 459 | const struct sgp_profile *profile; 460 | 461 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_IAQ_MEASURE); 462 | if (profile == NULL) 463 | return STATUS_FAIL; 464 | 465 | if (read_measurement(profile) == STATUS_FAIL) 466 | return STATUS_FAIL; 467 | 468 | *tvoc_ppb = client_data.word_buf[0]; 469 | *co2_eq_ppm = client_data.word_buf[1]; 470 | 471 | return STATUS_OK; 472 | } 473 | 474 | 475 | /** 476 | * sgp_measure_iaq_blocking_read() - Measure IAQ concentrations tVOC, CO2-Eq. 477 | * 478 | * @tvoc_ppb: The tVOC ppb value will be written to this location 479 | * @co2_eq_ppm: The CO2-Equivalent ppm value will be written to this location 480 | * 481 | * The profile is executed synchronously. 482 | * 483 | * Return: STATUS_OK on success, else STATUS_FAIL 484 | */ 485 | s16 sgp_measure_iaq_blocking_read(u16 *tvoc_ppb, u16 *co2_eq_ppm) { 486 | if (sgp_run_profile_by_number(PROFILE_NUMBER_IAQ_MEASURE) == STATUS_FAIL) 487 | return STATUS_FAIL; 488 | 489 | *tvoc_ppb = client_data.word_buf[0]; 490 | *co2_eq_ppm = client_data.word_buf[1]; 491 | 492 | return STATUS_OK; 493 | } 494 | 495 | 496 | /** 497 | * sgp_measure_tvoc() - Measure tVOC concentration async 498 | * 499 | * The profile is executed asynchronously. Use sgp_read_tvoc to get the 500 | * ppb value. 501 | * 502 | * Return: STATUS_OK on success, else STATUS_FAIL 503 | */ 504 | s16 sgp_measure_tvoc() { 505 | return sgp_measure_iaq(); 506 | } 507 | 508 | 509 | /** 510 | * sgp_read_tvoc() - Read tVOC concentration async 511 | * 512 | * Read the tVOC value. This command can only be exectued after a measurement 513 | * has started with sgp_measure_tvoc and is finished. 514 | * 515 | * @tvoc_ppb: The tVOC ppb value will be written to this location 516 | * 517 | * Return: STATUS_OK on success, else STATUS_FAIL 518 | */ 519 | s16 sgp_read_tvoc(u16 *tvoc_ppb) { 520 | u16 co2_eq_ppm; 521 | return sgp_read_iaq(tvoc_ppb, &co2_eq_ppm); 522 | } 523 | 524 | 525 | /** 526 | * sgp_measure_tvoc_blocking_read() - Measure tVOC concentration 527 | * 528 | * The profile is executed synchronously. 529 | * 530 | * Return: tVOC concentration in ppb. Negative if it fails. 531 | */ 532 | s16 sgp_measure_tvoc_blocking_read(u16 *tvoc_ppb) { 533 | u16 co2_eq_ppm; 534 | return sgp_measure_iaq_blocking_read(tvoc_ppb, &co2_eq_ppm); 535 | } 536 | 537 | 538 | /** 539 | * sgp_measure_co2_eq() - Measure tVOC concentration async 540 | * 541 | * The profile is executed asynchronously. Use sgp_read_co2_eq to get the 542 | * ppm value. 543 | * 544 | * Return: STATUS_OK on success, else STATUS_FAIL 545 | */ 546 | s16 sgp_measure_co2_eq() { 547 | return sgp_measure_iaq(); 548 | } 549 | 550 | 551 | /** 552 | * sgp_read_co2_eq() - Read CO2-Equivalent concentration async 553 | * 554 | * Read the CO2-Equivalent value. This command can only be exectued after a 555 | * measurement was started with sgp_measure_co2_eq() and is finished. 556 | * 557 | * @co2_eq_ppm: The CO2-Equivalent ppm value will be written to this location 558 | * 559 | * Return: STATUS_OK on success, else STATUS_FAIL 560 | */ 561 | s16 sgp_read_co2_eq(u16 *co2_eq_ppm) { 562 | u16 tvoc_ppb; 563 | return sgp_read_iaq(&tvoc_ppb, co2_eq_ppm); 564 | } 565 | 566 | 567 | /** 568 | * sgp_measure_co2_eq_blocking_read() - Measure CO2-Equivalent concentration 569 | * 570 | * The profile is executed synchronously. 571 | * 572 | * Return: CO2-Equivalent concentration in ppm. Negative if it fails. 573 | */ 574 | s16 sgp_measure_co2_eq_blocking_read(u16 *co2_eq_ppm) { 575 | u16 tvoc_ppb; 576 | return sgp_measure_iaq_blocking_read(&tvoc_ppb, co2_eq_ppm); 577 | } 578 | 579 | 580 | /** 581 | * sgp_measure_signals_blocking_read() - Measure signals 582 | * The profile is executed synchronously. 583 | * 584 | * @scaled_ethanol_signal: Output variable for the ethanol signal 585 | * The signal is scaled by factor 512. Divide the scaled 586 | * value by 512 to get the real signal. 587 | * @scaled_h2_signal: Output variable for the h2 signal 588 | * The signal is scaled by factor 512. Divide the scaled 589 | * value by 512 to get the real signal. 590 | * 591 | * Return: STATUS_OK on success, else STATUS_FAIL 592 | */ 593 | s16 sgp_measure_signals_blocking_read(u16 *scaled_ethanol_signal, 594 | u16 *scaled_h2_signal) { 595 | 596 | if (sgp_run_profile_by_number(PROFILE_NUMBER_MEASURE_SIGNALS) == STATUS_FAIL) 597 | return STATUS_FAIL; 598 | 599 | *scaled_ethanol_signal = client_data.word_buf[0]; 600 | *scaled_h2_signal = client_data.word_buf[1]; 601 | 602 | return STATUS_OK; 603 | } 604 | 605 | 606 | /** 607 | * sgp_measure_signals() - Measure signals async 608 | * 609 | * The profile is executed asynchronously. Use sgp_read_signals to get 610 | * the signal values. 611 | * 612 | * Return: STATUS_OK on success, else STATUS_FAIL 613 | */ 614 | s16 sgp_measure_signals(void) { 615 | const struct sgp_profile *profile; 616 | 617 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_MEASURE_SIGNALS); 618 | if (profile == NULL) { 619 | return STATUS_FAIL; 620 | } 621 | 622 | if (sgp_i2c_write(&(profile->command)) == STATUS_FAIL) 623 | return STATUS_FAIL; 624 | client_data.current_state = MEASURING_PROFILE_STATE; 625 | 626 | return STATUS_OK; 627 | } 628 | 629 | 630 | /** 631 | * sgp_read_signals() - Read signals async 632 | * This command can only be exectued after a measurement started with 633 | * sgp_measure_signals and has finished. 634 | * 635 | * @scaled_ethanol_signal: Output variable for ethanol signal. 636 | * The signal is scaled by factor 512. Divide the scaled 637 | * value by 512 to get the real signal. 638 | * @scaled_h2_signal: Output variable for h2 signal. 639 | * The signal is scaled by factor 512. Divide the scaled 640 | * value by 512 to get the real signal. 641 | * 642 | * Return: STATUS_OK on success, else STATUS_FAIL 643 | */ 644 | s16 sgp_read_signals(u16 *scaled_ethanol_signal, u16 *scaled_h2_signal) { 645 | const struct sgp_profile *profile; 646 | 647 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_MEASURE_SIGNALS); 648 | if (profile == NULL) 649 | return STATUS_FAIL; 650 | 651 | if (read_measurement(profile) == STATUS_FAIL) 652 | return STATUS_FAIL; 653 | 654 | *scaled_ethanol_signal = client_data.word_buf[0]; 655 | *scaled_h2_signal = client_data.word_buf[1]; 656 | 657 | return STATUS_OK; 658 | } 659 | 660 | 661 | /** 662 | * sgp_get_iaq_baseline() - read out the baseline from the chip 663 | * 664 | * The IAQ baseline should be retrieved and persisted for a faster sensor 665 | * startup. See sgp_set_iaq_baseline() for further documentation. 666 | * 667 | * A valid baseline value is only returned approx. 60min after a call to 668 | * sgp_iaq_init() when it is not followed by a call to sgp_set_iaq_baseline() 669 | * with a valid baseline. 670 | * This functions returns STATUS_FAIL if the baseline value is not valid. 671 | * 672 | * @baseline: Pointer to raw u32 where to store the baseline 673 | * If the method returns STATUS_FAIL, the baseline value must be 674 | * discarded and must not be passed to sgp_set_iaq_baseline(). 675 | * 676 | * Return: STATUS_OK on success, else STATUS_FAIL 677 | */ 678 | s16 sgp_get_iaq_baseline(u32 *baseline) { 679 | s16 ret = sgp_run_profile_by_number(PROFILE_NUMBER_IAQ_GET_BASELINE); 680 | if (ret == STATUS_FAIL) 681 | return STATUS_FAIL; 682 | 683 | ((u16 *)baseline)[0] = client_data.word_buf[0]; 684 | ((u16 *)baseline)[1] = client_data.word_buf[1]; 685 | 686 | if (!SGP_VALID_IAQ_BASELINE(*baseline)) 687 | return STATUS_FAIL; 688 | 689 | return STATUS_OK; 690 | } 691 | 692 | 693 | /** 694 | * sgp_set_iaq_baseline() - set the on-chip baseline 695 | * @baseline: A raw u32 baseline 696 | * This value must be unmodified from what was retrieved by a 697 | * successful call to sgp_get_iaq_baseline() with return value 698 | * STATUS_OK. A persisted baseline should not be set if it is 699 | * older than one week. 700 | * 701 | * Return: STATUS_OK on success, else STATUS_FAIL 702 | */ 703 | s16 sgp_set_iaq_baseline(u32 baseline) { 704 | const u16 BUF_SIZE = SGP_COMMAND_LEN + 2 * (SGP_WORD_LEN + CRC8_LEN); 705 | u8 buf[BUF_SIZE]; 706 | const struct sgp_profile *profile; 707 | 708 | if (!SGP_VALID_IAQ_BASELINE(baseline)) 709 | return STATUS_FAIL; 710 | 711 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_IAQ_SET_BASELINE); 712 | sgp_fill_cmd_send_buf(buf, &profile->command, (u16 *)&baseline, 713 | sizeof(baseline) / SGP_WORD_LEN); 714 | 715 | if (sensirion_i2c_write(SGP_I2C_ADDRESS, buf, BUF_SIZE) != 0) 716 | return STATUS_FAIL; 717 | 718 | return STATUS_OK; 719 | } 720 | 721 | 722 | /** 723 | * sgp_set_absolute_humidity() - set the absolute humidity for compensation 724 | * 725 | * The absolute humidity must be provided in in mg/m^3 and the value must 726 | * be between 0 and 256000 mg/m^3. 727 | * If the absolute humidity is set to zero, humidity compensation will be 728 | * disabled 729 | * 730 | * @absolute_humidity: u32 absolute humidity in mg/m^3 731 | * 732 | * Return: STATUS_OK on success, else STATUS_FAIL 733 | */ 734 | s16 sgp_set_absolute_humidity(u32 absolute_humidity) { 735 | const struct sgp_profile *profile; 736 | u16 ah_scaled; 737 | const u16 BUF_SIZE = SGP_COMMAND_LEN + SGP_WORD_LEN + CRC8_LEN; 738 | u8 buf[BUF_SIZE]; 739 | 740 | profile = sgp_get_profile_by_number(PROFILE_NUMBER_SET_ABSOLUTE_HUMIDITY); 741 | 742 | if (profile == NULL) 743 | return STATUS_FAIL; 744 | 745 | if (absolute_humidity > 256000) 746 | return STATUS_FAIL; 747 | 748 | /* ah_scaled = (absolute_humidity / 1000) * 256 */ 749 | ah_scaled = (u16)(((u64)absolute_humidity * 256 * 16777) >> 24); 750 | 751 | sgp_fill_cmd_send_buf(buf, &profile->command, &ah_scaled, 752 | sizeof(ah_scaled) / SGP_WORD_LEN); 753 | 754 | if (sensirion_i2c_write(SGP_I2C_ADDRESS, buf, BUF_SIZE) != 0) 755 | return STATUS_FAIL; 756 | 757 | return STATUS_OK; 758 | } 759 | 760 | 761 | /** 762 | * sgp_get_driver_version() - Return the driver version 763 | * Return: Driver version string 764 | */ 765 | const char *sgp_get_driver_version() 766 | { 767 | return SGP_DRV_VERSION_STR; 768 | } 769 | 770 | 771 | /** 772 | * sgp_get_configured_address() - returns the configured I2C address 773 | * 774 | * Return: u8 I2C address 775 | */ 776 | u8 sgp_get_configured_address() { 777 | return SGP_I2C_ADDRESS; 778 | } 779 | 780 | 781 | /** 782 | * sgp_get_feature_set_version() - Retrieve the sensor's feature set version and 783 | * product type 784 | * 785 | * @feature_set_version: The feature set version 786 | * @product_type: The product type: 0 for sgp30, 1: sgpc3 787 | * 788 | * Return: STATUS_OK on success 789 | */ 790 | s16 sgp_get_feature_set_version(u16 *feature_set_version, u8 *product_type) { 791 | *feature_set_version = client_data.info.feature_set_version & 0x00FF; 792 | *product_type = (u8)((client_data.info.feature_set_version & 0xC0000) >> 14); 793 | return STATUS_OK; 794 | } 795 | 796 | 797 | /** 798 | * sgp_iaq_init() - reset the SGP's internal IAQ baselines 799 | * 800 | * Return: STATUS_OK on success. 801 | */ 802 | s16 sgp_iaq_init() { 803 | return sgp_run_profile_by_number(PROFILE_NUMBER_IAQ_INIT); 804 | } 805 | 806 | 807 | /** 808 | * sgp_probe() - check if SGP sensor is available and initialize it 809 | * 810 | * This call aleady initializes the IAQ baselines (sgp_iaq_init()) 811 | * 812 | * Return: STATUS_OK on success. 813 | */ 814 | s16 sgp_probe() { 815 | s16 err; 816 | const u64 *serial_buf = (const u64 *)client_data.word_buf; 817 | 818 | client_data.current_state = WAIT_STATE; 819 | 820 | /* Initialize I2C */ 821 | sensirion_i2c_init(); 822 | 823 | /* try to read the serial ID */ 824 | err = sgp_i2c_read_words_from_cmd(&sgp_cmd_get_serial_id, 825 | SGP_CMD_GET_SERIAL_ID_DURATION_US, 826 | client_data.word_buf, 827 | SGP_CMD_GET_SERIAL_ID_WORDS); 828 | if (err == STATUS_FAIL) 829 | return err; 830 | 831 | client_data.info.serial_id = be64_to_cpu(*serial_buf) >> 16; 832 | 833 | /* read the featureset version */ 834 | err = sgp_i2c_read_words_from_cmd(&sgp_cmd_get_featureset, 835 | SGP_CMD_GET_FEATURESET_DURATION_US, 836 | client_data.word_buf, 837 | SGP_CMD_GET_FEATURESET_WORDS); 838 | if (err == STATUS_FAIL) 839 | return STATUS_FAIL; 840 | 841 | err = sgp_detect_featureset_version(client_data.word_buf); 842 | if (err == STATUS_FAIL) 843 | return STATUS_FAIL; 844 | 845 | return sgp_iaq_init(); 846 | } 847 | 848 | 849 | #ifdef __cplusplus 850 | } // extern "C" 851 | #endif 852 | 853 | -------------------------------------------------------------------------------- /sgp30.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | 31 | #ifndef SGP30_H 32 | #define SGP30_H 33 | #include "sensirion_configuration.h" 34 | #include "sensirion_common.h" 35 | 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif 40 | 41 | extern s16 sgp_probe(void); 42 | s16 sgp_iaq_init(void); 43 | 44 | const char* sgp_get_driver_version(void); 45 | u8 sgp_get_configured_address(void); 46 | s16 sgp_get_feature_set_version(u16* feature_set_version, u8* product_type); 47 | 48 | s16 sgp_get_iaq_baseline(u32* baseline); 49 | s16 sgp_set_iaq_baseline(u32 baseline); 50 | 51 | s16 sgp_measure_iaq_blocking_read(u16* tvoc_ppb, u16* co2_eq_ppm); 52 | s16 sgp_measure_iaq(void); 53 | s16 sgp_read_iaq(u16* tvoc_ppb, u16* co2_eq_ppm); 54 | 55 | s16 sgp_measure_tvoc_blocking_read(u16* tvoc_ppb); 56 | s16 sgp_measure_tvoc(void); 57 | s16 sgp_read_tvoc(u16* tvoc_ppb); 58 | 59 | s16 sgp_measure_co2_eq_blocking_read(u16* co2_eq_ppm); 60 | s16 sgp_measure_co2_eq(void); 61 | s16 sgp_read_co2_eq(u16* co2_eq_ppm); 62 | 63 | s16 sgp_measure_signals_blocking_read(u16* scaled_ethanol_signal, 64 | u16* scaled_h2_signal); 65 | s16 sgp_measure_signals(void); 66 | s16 sgp_read_signals(u16* scaled_ethanol_signal, u16* scaled_h2_signal); 67 | 68 | s16 sgp_measure_test(u16* test_result); 69 | 70 | s16 sgp_set_absolute_humidity(u32 absolute_humidity); 71 | 72 | #ifdef __cplusplus 73 | } 74 | #endif 75 | 76 | #endif /* SGP30_H */ 77 | 78 | -------------------------------------------------------------------------------- /sgp30_featureset.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017, Sensirion AG 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | * list of conditions and the following disclaimer. 10 | * 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | * this list of conditions and the following disclaimer in the documentation 13 | * and/or other materials provided with the distribution. 14 | * 15 | * * Neither the name of Sensirion AG nor the names of its 16 | * contributors may be used to endorse or promote products derived from 17 | * this software without specific prior written permission. 18 | * 19 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #include "sensirion_common.h" 31 | #include "sgp_featureset.h" 32 | 33 | #ifdef __cplusplus 34 | extern "C" { 35 | #endif 36 | 37 | #define PROFILE_NUMBER_SIGNALS 10 38 | #define PROFILE_NUMBER_SET_AH 12 39 | 40 | const u8 PROFILE_NUMBER_MEASURE_SIGNALS = PROFILE_NUMBER_SIGNALS; 41 | const u8 PROFILE_NUMBER_SET_ABSOLUTE_HUMIDITY = PROFILE_NUMBER_SET_AH; 42 | 43 | static const struct sgp_signal ETHANOL_SIGNAL_FS9 = { 44 | .conversion_function = NULL, 45 | .name = "ethanol_signal", 46 | }; 47 | 48 | static const struct sgp_signal H2_SIGNAL_FS9 = { 49 | .conversion_function = NULL, 50 | .name = "h2_signal", 51 | }; 52 | 53 | static const struct sgp_signal TVOC_PPB_FS9 = { 54 | .conversion_function = NULL, 55 | .name = "tVOC", 56 | }; 57 | 58 | static const struct sgp_signal CO2_EQ_PPM = { 59 | .conversion_function = NULL, 60 | .name = "co2_eq", 61 | }; 62 | 63 | static const struct sgp_signal BASELINE_WORD1 = { 64 | .conversion_function = NULL, 65 | .name = "baseline1", 66 | }; 67 | 68 | static const struct sgp_signal BASELINE_WORD2 = { 69 | .conversion_function = NULL, 70 | .name = "baseline2", 71 | }; 72 | 73 | static const struct sgp_signal *SGP_PROFILE_IAQ_MEASURE_SIGNALS9[] = 74 | { &TVOC_PPB_FS9, &CO2_EQ_PPM }; 75 | 76 | static const struct sgp_signal *SGP_PROFILE_IAQ_GET_BASELINE_SIGNALS[] = 77 | { &BASELINE_WORD1, &BASELINE_WORD2 }; 78 | 79 | static const struct sgp_signal *SGP_PROFILE_MEASURE_SIGNALS_SIGNALS9[] = 80 | { ÐANOL_SIGNAL_FS9, &H2_SIGNAL_FS9 }; 81 | 82 | 83 | static const struct sgp_profile SGP_PROFILE_IAQ_INIT = { 84 | .number = PROFILE_NUMBER_IAQ_INIT, 85 | .duration_us = 10000, 86 | .signals = NULL, 87 | .number_of_signals = 0, 88 | .command = { .buf = {0x20, 0x03} }, 89 | .name = "iaq_init", 90 | }; 91 | 92 | static const struct sgp_profile SGP_PROFILE_IAQ_MEASURE9 = { 93 | .number = PROFILE_NUMBER_IAQ_MEASURE, 94 | .duration_us = 50000, 95 | .signals = SGP_PROFILE_IAQ_MEASURE_SIGNALS9, 96 | .number_of_signals = ARRAY_SIZE(SGP_PROFILE_IAQ_MEASURE_SIGNALS9), 97 | .command = { .buf = {0x20, 0x08} }, 98 | .name = "iaq_measure", 99 | }; 100 | 101 | static const struct sgp_profile SGP_PROFILE_IAQ_GET_BASELINE = { 102 | .number = PROFILE_NUMBER_IAQ_GET_BASELINE, 103 | .duration_us = 10000, 104 | .signals = SGP_PROFILE_IAQ_GET_BASELINE_SIGNALS, 105 | .number_of_signals = ARRAY_SIZE(SGP_PROFILE_IAQ_GET_BASELINE_SIGNALS), 106 | .command = { .buf = {0x20, 0x15} }, 107 | .name = "iaq_get_baseline", 108 | }; 109 | 110 | static const struct sgp_profile SGP_PROFILE_IAQ_SET_BASELINE = { 111 | .number = PROFILE_NUMBER_IAQ_SET_BASELINE, 112 | .duration_us = 1000, 113 | .signals = NULL, 114 | .number_of_signals = 0, 115 | .command = { .buf = {0x20, 0x1e} }, 116 | .name = "iaq_set_baseline", 117 | }; 118 | 119 | static const struct sgp_profile SGP_PROFILE_MEASURE_SIGNALS9 = { 120 | .number = PROFILE_NUMBER_SIGNALS, 121 | .duration_us = 200000, 122 | .signals = SGP_PROFILE_MEASURE_SIGNALS_SIGNALS9, 123 | .number_of_signals = ARRAY_SIZE(SGP_PROFILE_MEASURE_SIGNALS_SIGNALS9), 124 | .command = { .buf = {0x20, 0x50} }, 125 | .name = "measure_signals", 126 | }; 127 | 128 | static const struct sgp_profile SGP_PROFILE_SET_ABSOLUTE_HUMIDITY = { 129 | .number = PROFILE_NUMBER_SET_AH, 130 | .duration_us = 10000, 131 | .signals = NULL, 132 | .number_of_signals = 0, 133 | .command = { .buf = {0x20, 0x61} }, 134 | .name = "set_absolute_humidity", 135 | }; 136 | 137 | 138 | static const struct sgp_profile *sgp_profiles9[] = { 139 | &SGP_PROFILE_IAQ_INIT, 140 | &SGP_PROFILE_IAQ_MEASURE9, 141 | &SGP_PROFILE_IAQ_GET_BASELINE, 142 | &SGP_PROFILE_IAQ_SET_BASELINE, 143 | &SGP_PROFILE_MEASURE_SIGNALS9, 144 | }; 145 | 146 | static const struct sgp_profile *sgp_profiles32[] = { 147 | &SGP_PROFILE_IAQ_INIT, 148 | &SGP_PROFILE_IAQ_MEASURE9, 149 | &SGP_PROFILE_IAQ_GET_BASELINE, 150 | &SGP_PROFILE_IAQ_SET_BASELINE, 151 | &SGP_PROFILE_MEASURE_SIGNALS9, 152 | &SGP_PROFILE_SET_ABSOLUTE_HUMIDITY, 153 | }; 154 | 155 | static const u16 supported_featureset_versions_fs9[] = { 9 }; 156 | static const u16 supported_featureset_versions_fs32[] = { 0x20 }; 157 | 158 | 159 | const struct sgp_otp_featureset sgp_featureset9 = { 160 | .profiles = sgp_profiles9, 161 | .number_of_profiles = ARRAY_SIZE(sgp_profiles9), 162 | .supported_featureset_versions = (u16 *)supported_featureset_versions_fs9, 163 | .number_of_supported_featureset_versions = ARRAY_SIZE(supported_featureset_versions_fs9) 164 | }; 165 | 166 | const struct sgp_otp_featureset sgp_featureset32 = { 167 | .profiles = sgp_profiles32, 168 | .number_of_profiles = ARRAY_SIZE(sgp_profiles32), 169 | .supported_featureset_versions = (u16 *)supported_featureset_versions_fs32, 170 | .number_of_supported_featureset_versions = ARRAY_SIZE(supported_featureset_versions_fs32) 171 | }; 172 | 173 | /** 174 | * Supported featuresets 175 | * List featuresets in descending order (new -> old) to use the most recent 176 | * compatible featureset config, as multiple ones may apply for the same major 177 | * version. 178 | */ 179 | const struct sgp_otp_featureset *featuresets[] = { 180 | &sgp_featureset32, 181 | &sgp_featureset9, 182 | }; 183 | 184 | const struct sgp_otp_supported_featuresets sgp_supported_featuresets = { 185 | .featuresets = featuresets, 186 | .number_of_supported_featuresets = ARRAY_SIZE(featuresets) 187 | }; 188 | 189 | #ifdef __cplusplus 190 | } // extern "C" 191 | #endif 192 | -------------------------------------------------------------------------------- /sgp_featureset.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017, Sensirion AG 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | * * Neither the name of Sensirion AG nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | */ 30 | #ifndef SGP_FEATURESET_H 31 | #define SGP_FEATURESET_H 32 | 33 | #include "sensirion_configuration.h" 34 | 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | #define SGP_WORD_LEN 2 40 | #define SGP_COMMAND_LEN SGP_WORD_LEN 41 | 42 | /* maximal size of signal and profile names */ 43 | #define NAME_SIZE 32 44 | 45 | #define PROFILE_NUMBER_IAQ_INIT 0 46 | #define PROFILE_NUMBER_IAQ_MEASURE 1 47 | #define PROFILE_NUMBER_IAQ_GET_BASELINE 2 48 | #define PROFILE_NUMBER_IAQ_SET_BASELINE 3 49 | extern const u8 PROFILE_NUMBER_MEASURE_SIGNALS; 50 | extern const u8 PROFILE_NUMBER_SET_ABSOLUTE_HUMIDITY; 51 | 52 | typedef union { 53 | u16 words[SGP_COMMAND_LEN / SGP_WORD_LEN]; /* enforce u16 alignment */ 54 | u8 buf[SGP_COMMAND_LEN]; 55 | } sgp_command; 56 | 57 | struct sgp_signal { 58 | u16(*conversion_function)(u16); 59 | char name[NAME_SIZE]; 60 | }; 61 | 62 | struct sgp_profile { 63 | /* expected duration of measurement, i.e., when to return for data */ 64 | u32 duration_us; 65 | /* signals */ 66 | const struct sgp_signal** signals; 67 | u16 number_of_signals; 68 | u8 number; 69 | const sgp_command command; 70 | char name[NAME_SIZE]; 71 | }; 72 | 73 | struct sgp_otp_featureset { 74 | const struct sgp_profile** profiles; 75 | u16 number_of_profiles; 76 | const u16* supported_featureset_versions; 77 | u16 number_of_supported_featureset_versions; 78 | }; 79 | 80 | struct sgp_otp_supported_featuresets { 81 | const struct sgp_otp_featureset** featuresets; 82 | u16 number_of_supported_featuresets; 83 | }; 84 | 85 | extern const struct sgp_otp_supported_featuresets sgp_supported_featuresets; 86 | 87 | #endif /* SGP_FEATURESET_H */ 88 | 89 | #ifdef __cplusplus 90 | } // extern "C" 91 | #endif 92 | 93 | --------------------------------------------------------------------------------