diff --git a/fw/Core/Inc/main.h b/fw/Core/Inc/main.h
index b2bec4a..24af6bc 100644
--- a/fw/Core/Inc/main.h
+++ b/fw/Core/Inc/main.h
@@ -53,6 +53,7 @@ extern "C" {
#include "scd4x.h"
#include "sht4x.h"
#include "sps30.h"
+#include "sgp4x.h"
#include "modbus.h"
#include "config.h"
#include "rgbled.h"
diff --git a/fw/Core/Inc/sgp4x.h b/fw/Core/Inc/sgp4x.h
new file mode 100644
index 0000000..b90b9de
--- /dev/null
+++ b/fw/Core/Inc/sgp4x.h
@@ -0,0 +1,50 @@
+/*
+ * sgp4x.h
+ *
+ * Created on: Jan 9, 2022
+ * Author: david
+ */
+
+#ifndef INC_SGP4X_H_
+#define INC_SGP4X_H_
+
+
+#include "stdint.h"
+#include "stm32l0xx_ll_i2c.h"
+#include "stm32l0xx_ll_utils.h"
+#include "i2c.h"
+#include "crc8.h"
+
+/*
+ * Defines & macros
+ */
+
+#define SGP4X_I2C_ADDRESS 0x59
+#define SGP4X_MAX_MEAS_DURATION_MS 30
+
+/*
+ * Return values
+ */
+
+#define SGP4X_OK 0
+#define SGP4X_ERROR -1 // generic error
+#define SGP4X_CRC8_ERROR -2 // checksum failed
+
+typedef enum {
+ SGP4X_MEASURE_RAW_SIGNAL = 0x260F,
+ SGP4X_EXECUTE_SELF_TEST = 0x280E,
+ SGP4X_TURN_HEATER_OFF = 0x3615,
+ SGP4X_GET_SERIAL_NUMBER = 0x3682,
+ SGP4X_SOFT_RESET = 0x0006
+} sgp4x_cmd_t;
+
+int8_t sgp4x_send_cmd(sgp4x_cmd_t cmd);
+
+int8_t sgp4x_measure_raw_signal (uint16_t * voc_ticks);
+int8_t sgp4x_measure_raw_signal_compensated (uint16_t * voc_ticks, uint16_t relative_humidity, int16_t temperature);
+int8_t sgp4x_execute_self_test ( uint8_t * test_result);
+int8_t sgp4x_get_serial_number ( uint8_t serial[6]);
+int8_t sgp4x_turn_heater_off ( void );
+int8_t sgp4x_soft_reset ( void );
+
+#endif /* INC_SGP4X_H_ */
diff --git a/fw/Core/Src/sgp4x.c b/fw/Core/Src/sgp4x.c
new file mode 100644
index 0000000..422dc18
--- /dev/null
+++ b/fw/Core/Src/sgp4x.c
@@ -0,0 +1,155 @@
+/*
+ * sgp4x.c
+ *
+ * Created on: Jan 9, 2022
+ * Author: david
+ */
+
+#include "sgp4x.h"
+
+int8_t sgp4x_send_cmd(sgp4x_cmd_t cmd)
+{
+ uint8_t buffer[32];
+ int result;
+
+ // start measurement
+ buffer[0] = cmd >> 8;
+ buffer[1] = cmd & 0x00ff;
+ result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 2);
+ if (result == I2C_ERROR_TX_INCOMPLETE) {
+ return SGP4X_ERROR;
+ }
+
+ /* Sensirion sensors return NACK after last byte (so NACK at the end is ok) */
+ return SGP4X_OK;
+}
+
+int8_t sgp4x_measure_raw_signal (uint16_t * voc_ticks)
+{
+ uint8_t buffer[32];
+ int result;
+
+ // start measurement
+ buffer[0] = SGP4X_MEASURE_RAW_SIGNAL >> 8;
+ buffer[1] = SGP4X_MEASURE_RAW_SIGNAL & 0x00ff;
+ buffer[2] = 0x80;
+ buffer[3] = 0x00;
+ buffer[4] = 0xA2;
+ buffer[5] = 0x66;
+ buffer[6] = 0x66;
+ buffer[7] = 0x93;
+
+ result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 8);
+ if (result != I2C_OK) {
+ return SGP4X_ERROR;
+ }
+
+ LL_mDelay(SGP4X_MAX_MEAS_DURATION_MS); // 30ms
+
+ result = i2c_receive(SGP4X_I2C_ADDRESS<<1, buffer, 3);
+ if (result != I2C_OK)
+ {
+ return SGP4X_ERROR;
+ }
+
+ *voc_ticks = (buffer[0] << 8) + buffer[1];
+ uint8_t voc_ticks_crc = buffer[2];
+ uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc;
+ if (!crc_correct) {
+ return SGP4X_CRC8_ERROR;
+ }
+ return SGP4X_OK;
+
+}
+
+int8_t sgp4x_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 * (uint32_t)(65535/100));
+ uint16_t t_ticks = (uint16_t) ((uint32_t)(temperature + 45) * (uint32_t)(65535/175));
+
+ buffer[0] = SGP4X_MEASURE_RAW_SIGNAL >> 8;
+ buffer[1] = SGP4X_MEASURE_RAW_SIGNAL & 0x00ff;
+ buffer[2] = 0x80;
+ buffer[3] = 0x00;
+ buffer[4] = crc8_calculate(buffer+2, 2);
+ buffer[5] = 0x66;
+ buffer[6] = 0x66;
+ buffer[7] = crc8_calculate(buffer+5, 2);
+
+
+ result = i2c_transmit(SGP4X_I2C_ADDRESS<<1, buffer, 8);
+ if (result != I2C_OK) {
+ return SGP4X_ERROR;
+ }
+
+ LL_mDelay(SGP4X_MAX_MEAS_DURATION_MS); // 30ms
+
+ result = i2c_receive(SGP4X_I2C_ADDRESS<<1, buffer, 3);
+ if (result != I2C_OK)
+ {
+ return SGP4X_ERROR;
+ }
+
+ *voc_ticks = (buffer[0] << 8) + buffer[1];
+ uint8_t voc_ticks_crc = buffer[2];
+ uint8_t crc_correct = crc8_calculate(buffer, 2) == voc_ticks_crc;
+ if (!crc_correct) {
+ return SGP4X_CRC8_ERROR;
+ }
+ return SGP4X_OK;
+}
+
+int8_t sgp4x_execute_self_test ( uint8_t * test_result)
+{
+ uint8_t buffer[16];
+
+ scd4x_send_cmd(SGP4X_EXECUTE_SELF_TEST);
+ i2c_receive(SGP4X_I2C_ADDRESS << 1, buffer, 3);
+
+ test_result = buffer[0];
+ uint8_t test_result_crc = buffer[2];
+
+ uint8_t crc_correct = crc8_calculate(buffer, 2) == test_result_crc;
+ if (!crc_correct) {
+ return SGP4X_CRC8_ERROR;
+ }
+ return SGP4X_OK;
+}
+
+int8_t sgp4x_get_serial_number ( uint8_t serial[6])
+{
+ uint8_t buffer[16];
+
+ scd4x_send_cmd(SGP4X_GET_SERIAL_NUMBER);
+ i2c_receive(SGP4X_I2C_ADDRESS << 1, buffer, 9);
+
+ 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 = crc8_calculate(buffer, 2) == crc_ser01;
+ crc_correct &= crc8_calculate(buffer + 3, 2) == crc_ser23;
+ crc_correct &= crc8_calculate(buffer + 6, 2) == crc_ser45;
+ if (!crc_correct) {
+ return SGP4X_CRC8_ERROR;
+ }
+ return SGP4X_OK;
+}
+
+int8_t sgp4x_turn_heater_off ( void )
+{
+ return sgp4x_send_cmd(SGP4X_TURN_HEATER_OFF);
+}
+int8_t sgp4x_soft_reset ( void )
+{
+ return sgp4x_send_cmd(SGP4X_SOFT_RESET);
+}
diff --git a/fw/iaq_wired_sensor Debug.launch b/fw/iaq_wired_sensor Debug.launch
index 8aa4a98..191079c 100644
--- a/fw/iaq_wired_sensor Debug.launch
+++ b/fw/iaq_wired_sensor Debug.launch
@@ -58,7 +58,7 @@
-
+