/* * File: sgp40.c * Description: Sensirion SGP40 sensor communication library * Author: David Zaitlik * Date: 2022-01-09 * * * Copyright (c) 2024 Veles Labs s.r.o. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ #include "sgp40.h" /* * Functions to be implemented by user */ /* I2C */ int8_t sgp40_i2c_transmit(uint8_t address, uint8_t *buffer, int len) __attribute__((weak)); int8_t sgp40_i2c_receive(uint8_t address, uint8_t *buffer, int len) __attribute__((weak)); /* CRC */ uint8_t sensirion_crc8_calculate(const uint8_t *data, uint16_t count) __attribute__((weak)); /* Interrupts */ int8_t sgp40_disable_interrupts(void) __attribute__((weak)); int8_t sgp40_enable_interrupts(void) __attribute__((weak)); /* delay function */ void delay_ms(int delay_ms) __attribute__((weak)); /* * Public functions */ int8_t sgp40_send_cmd(sgp40_cmd_t cmd) { uint8_t buffer[32]; int result; // start measurement buffer[0] = cmd >> 8; buffer[1] = cmd & 0x00ff; sgp40_disable_interrupts(); result = sgp40_i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 2); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } /* Sensirion sensors return NACK after last byte (so NACK at the end is ok) */ return SGP40_OK; } int8_t sgp40_measure_raw_signal(uint16_t * voc_ticks) { uint8_t buffer[32]; int result; /* Start measurement */ buffer[0] = 0x26; buffer[1] = 0x0F; buffer[2] = 0x80; buffer[3] = 0x00; buffer[4] = 0xA2; buffer[5] = 0x66; buffer[6] = 0x66; buffer[7] = 0x93; /* Returns NACK if CRC is wrong */ sgp40_disable_interrupts(); result = sgp40_i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 8); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } delay_ms(SGP40_MAX_MEAS_DURATION_MS); // 30ms sgp40_disable_interrupts(); result = sgp40_i2c_receive(SGP40_I2C_ADDRESS<<1, buffer, 3); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } *voc_ticks = (buffer[0] << 8) + buffer[1]; uint8_t voc_ticks_crc = buffer[2]; uint8_t crc_correct = sensirion_crc8_calculate(buffer, 2) == voc_ticks_crc; if (!crc_correct) { return SGP40_CRC8_ERROR; } return SGP40_OK; } int8_t sgp40_measure_raw_signal_compensated(uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature) { uint8_t buffer[32]; int result; uint16_t rh_ticks = (uint16_t)((uint32_t)relative_humidity * 65535 / 100); uint16_t t_ticks = (uint16_t)(((uint32_t)temperature/10 + 45) * 65535 / 175); buffer[0] = SGP40_MEASURE_RAW_SIGNAL >> 8; buffer[1] = SGP40_MEASURE_RAW_SIGNAL & 0x00ff; buffer[2] = (uint8_t)(rh_ticks >> 8); buffer[3] = (uint8_t)rh_ticks; buffer[4] = sensirion_crc8_calculate(buffer+2, 2); buffer[5] = (uint8_t)(t_ticks >> 8); buffer[6] = (uint8_t)t_ticks; buffer[7] = sensirion_crc8_calculate(buffer+5, 2); /* Returns NACK if CRC is wrong */ sgp40_disable_interrupts(); result = sgp40_i2c_transmit(SGP40_I2C_ADDRESS<<1, buffer, 8); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } delay_ms(SGP40_MAX_MEAS_DURATION_MS); // 30ms sgp40_disable_interrupts(); result = sgp40_i2c_receive(SGP40_I2C_ADDRESS<<1, buffer, 3); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } *voc_ticks = (buffer[0] << 8) + buffer[1]; uint8_t voc_ticks_crc = buffer[2]; uint8_t crc_correct = sensirion_crc8_calculate(buffer, 2) == voc_ticks_crc; if (!crc_correct) { return SGP40_CRC8_ERROR; } return SGP40_OK; } int8_t sgp40_execute_self_test ( uint8_t * test_result) { uint8_t buffer[16]; int8_t result; result = sgp40_send_cmd(SGP40_EXECUTE_SELF_TEST); if (result != 0) { return SGP40_ERROR; } delay_ms(350); sgp40_disable_interrupts(); result = sgp40_i2c_receive(SGP40_I2C_ADDRESS << 1, buffer, 3); sgp40_enable_interrupts(); if (result != 0) { return SGP40_ERROR; } *test_result = buffer[0]; uint8_t test_result_crc = buffer[2]; uint8_t crc_correct = sensirion_crc8_calculate(buffer, 2) == test_result_crc; if (!crc_correct) { return SGP40_CRC8_ERROR; } return SGP40_OK; } int8_t sgp40_get_serial_number(uint8_t serial[6]) { uint8_t buffer[16]; sgp40_disable_interrupts(); sgp40_send_cmd(SGP40_GET_SERIAL_NUMBER); sgp40_enable_interrupts(); delay_ms(5); sgp40_disable_interrupts(); sgp40_i2c_receive(SGP40_I2C_ADDRESS << 1, buffer, 9); sgp40_enable_interrupts(); serial[0] = buffer[0]; serial[1] = buffer[1]; uint8_t crc_ser01 = buffer[3]; serial[2] = buffer[4]; serial[3] = buffer[5]; uint8_t crc_ser23 = buffer[6]; serial[4] = buffer[7]; serial[5] = buffer[8]; uint8_t crc_ser45 = buffer[9]; uint8_t crc_correct = sensirion_crc8_calculate(buffer, 2) == crc_ser01; crc_correct &= sensirion_crc8_calculate(buffer + 3, 2) == crc_ser23; crc_correct &= sensirion_crc8_calculate(buffer + 6, 2) == crc_ser45; if (!crc_correct) { return SGP40_CRC8_ERROR; } return SGP40_OK; } int8_t sgp40_turn_heater_off(void) { return sgp40_send_cmd(SGP40_TURN_HEATER_OFF); } int8_t sgp40_soft_reset(void) { return sgp40_send_cmd(SGP40_SOFT_RESET); }