diff --git a/Inc/i2c.h b/Inc/i2c.h new file mode 100644 --- /dev/null +++ b/Inc/i2c.h @@ -0,0 +1,19 @@ +/* + * i2c.h + * + * Created on: Mar 7, 2017 + * Author: Matthew Reed + */ + +#ifndef INC_I2C_H_ +#define INC_I2C_H_ + +#include "system.h" + +extern I2C_HandleTypeDef i2c_handle; + +void i2c_init(void); +bool i2c_send(uint16_t address, uint8_t* buffer, uint16_t length); +bool i2c_receive(uint16_t address, uint8_t* buffer, uint16_t length); + +#endif /* INC_I2C_H_ */ diff --git a/Inc/sgp30.h b/Inc/sgp30.h new file mode 100644 --- /dev/null +++ b/Inc/sgp30.h @@ -0,0 +1,30 @@ +#ifndef INC_SGP30_H_ +#define INC_SGP30_H_ + +#include +#include "stm32f0xx_hal.h" +#include "i2c.h" + + +// the i2c address +#define SGP30_I2CADDR_DEFAULT 0x58 ///< SGP30 has only one I2C address + +// commands and constants +#define SGP30_FEATURESET 0x0020 ///< The required set for this library +#define SGP30_CRC8_POLYNOMIAL 0x31 ///< Seed for SGP30's CRC polynomial +#define SGP30_CRC8_INIT 0xFF ///< Init value for CRC +#define SGP30_WORD_LEN 2 ///< 2 bytes per word + + +bool sgp30_init(void); +bool sgp30_IAQinit(void); +bool sgp30_IAQmeasure(void); + +bool sgp30_getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base); +bool sgp30_setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base); +bool sgp30_setHumidity(uint32_t absolute_humidity); + +bool sgp30_readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delay, uint16_t *readdata, uint8_t readlen); +uint8_t sgp30_generateCRC(uint8_t data[], uint8_t datalen); + +#endif /* INC_SGP30_H_ */ diff --git a/Src/i2c.c b/Src/i2c.c new file mode 100644 --- /dev/null +++ b/Src/i2c.c @@ -0,0 +1,76 @@ +/* + * i2c.c + * + * Created on: Mar 7, 2017 + * Author: Matthew Reed + */ + +#include "i2c.h" + +I2C_HandleTypeDef i2c_handle; + +void i2c_init(void) +{ + + i2c_handle.Instance = I2C1; + i2c_handle.Init.Timing = 0x2000090E; + i2c_handle.Init.OwnAddress1 = 0; + i2c_handle.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; + i2c_handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; + i2c_handle.Init.OwnAddress2 = 0; + i2c_handle.Init.OwnAddress2Masks = I2C_OA2_NOMASK; + i2c_handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; + i2c_handle.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; + HAL_I2C_Init(&i2c_handle); + + //Configure Analog filter + HAL_I2CEx_ConfigAnalogFilter(&i2c_handle, I2C_ANALOGFILTER_ENABLE); + +} + +bool i2c_send(uint16_t address, uint8_t* buffer, uint16_t length) +{ + bool result = true; + + HAL_I2C_Master_Transmit(&i2c_handle, address, buffer, length, 10000); + +// while(HAL_I2C_Master_Transmit(&i2c_handle, address, buffer, length, 10000) != HAL_OK) +// { +// +// if (HAL_I2C_GetError(&i2c_handle) != HAL_I2C_ERROR_AF) +// { +// result = false; +// } +// } + + return result; +} + +bool i2c_receive(uint16_t address, uint8_t* buffer, uint16_t length) +{ + bool result = true; + + HAL_I2C_Master_Receive(&i2c_handle, address, buffer, length, 10000); + +// while(HAL_I2C_Master_Receive(&i2c_handle, address, buffer, length, 10000) != HAL_OK) +// { +// if (HAL_I2C_GetError(&i2c_handle) != HAL_I2C_ERROR_AF) +// { +// result = false; +// } +// } + + return result; +} + +void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *I2cHandle) +{ +} + +void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *I2cHandle) +{ +} + +void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *I2cHandle) +{ +} diff --git a/Src/sgp30.c b/Src/sgp30.c new file mode 100644 --- /dev/null +++ b/Src/sgp30.c @@ -0,0 +1,214 @@ +#include "sgp30.h" + + /** + * The last measurement of the IAQ-calculated Total Volatile Organic Compounds in ppb. This value is set when you call {@link IAQmeasure()} + */ + uint16_t TVOC; + + /** + * The last measurement of the IAQ-calculated equivalent CO2 in ppm. This value is set when you call {@link IAQmeasure()} + */ + uint16_t eCO2; + + /** + * The 48-bit serial number, this value is set when you call {@link begin()} + */ + uint16_t serialnumber[3]; + + uint8_t _i2caddr; + +/**************************************************************************/ +/*! + @brief Setups the hardware and detects a valid SGP30. Initializes I2C + then reads the serialnumber and checks that we are talking to an SGP30 + @param theWire Optional pointer to I2C interface, otherwise use Wire + @returns True if SGP30 found on I2C, False if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_init() +{ + _i2caddr = SGP30_I2CADDR_DEFAULT; + + i2c_init(); + + uint8_t command[2]; + command[0] = 0x36; + command[1] = 0x82; + if (! sgp30_readWordFromCommand(command, 2, 10, serialnumber, 3)) + return false; + + uint16_t featureset; + command[0] = 0x20; + command[1] = 0x2F; + if (! sgp30_readWordFromCommand(command, 2, 10, &featureset, 1)) + return false; + //Serial.print("Featureset 0x"); Serial.println(featureset, HEX); + if (featureset != SGP30_FEATURESET) + return false; + if (! sgp30_IAQinit()) + return false; + + return true; +} + +uint16_t sgp30_get_tvoc(void) +{ + return TVOC; +} + +uint16_t sgp30_get_eC02(void) +{ + return eCO2; +} + +/**************************************************************************/ +/*! + @brief Commands the sensor to begin the IAQ algorithm. Must be called after startup. + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_IAQinit(void) +{ + uint8_t command[2]; + command[0] = 0x20; + command[1] = 0x03; + return sgp30_readWordFromCommand(command, 2, 10, 0, 0); +} + +/**************************************************************************/ +/*! + @brief Commands the sensor to take a single eCO2/VOC measurement. Places results in {@link TVOC} and {@link eCO2} + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_IAQmeasure(void) +{ + uint8_t command[2]; + command[0] = 0x20; + command[1] = 0x08; + uint16_t reply[2]; + if (! sgp30_readWordFromCommand(command, 2, 12, reply, 2)) + return false; + TVOC = reply[1]; + eCO2 = reply[0]; + return true; +} + +/**************************************************************************/ +/*! + @brief Request baseline calibration values for both CO2 and TVOC IAQ calculations. Places results in parameter memory locaitons. + @param eco2_base A pointer to a uint16_t which we will save the calibration value to + @param tvoc_base A pointer to a uint16_t which we will save the calibration value to + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_getIAQBaseline(uint16_t *eco2_base, uint16_t *tvoc_base) +{ + uint8_t command[2]; + command[0] = 0x20; + command[1] = 0x15; + uint16_t reply[2]; + if (! sgp30_readWordFromCommand(command, 2, 10, reply, 2)) + return false; + *eco2_base = reply[0]; + *tvoc_base = reply[1]; + return true; +} + +/**************************************************************************/ +/*! + @brief Assign baseline calibration values for both CO2 and TVOC IAQ calculations. + @param eco2_base A uint16_t which we will save the calibration value from + @param tvoc_base A uint16_t which we will save the calibration value from + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_setIAQBaseline(uint16_t eco2_base, uint16_t tvoc_base) +{ + uint8_t command[8]; + command[0] = 0x20; + command[1] = 0x1e; + command[2] = tvoc_base >> 8; + command[3] = tvoc_base & 0xFF; + command[4] = sgp30_generateCRC(command+2, 2); + command[5] = eco2_base >> 8; + command[6] = eco2_base & 0xFF; + command[7] = sgp30_generateCRC(command+5, 2); + + return sgp30_readWordFromCommand(command, 8, 10, 0, 0); +} + +/**************************************************************************/ +/*! + @brief Set the absolute humidity value [mg/m^3] for compensation to increase precision of TVOC and eCO2. + @param absolute_humidity A uint32_t [mg/m^3] which we will be used for compensation. If the absolute humidity is set to zero, humidity compensation will be disabled. + @returns True if command completed successfully, false if something went wrong! +*/ +/**************************************************************************/ +bool sgp30_setHumidity(uint32_t absolute_humidity) +{ + if (absolute_humidity > 256000) { + return false; + } + + uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24); + uint8_t command[5]; + command[0] = 0x20; + command[1] = 0x61; + command[2] = ah_scaled >> 8; + command[3] = ah_scaled & 0xFF; + command[4] = sgp30_generateCRC(command+2, 2); + + return sgp30_readWordFromCommand(command, 5, 10, 0, 0); +} + +/**************************************************************************/ +/*! + @brief I2C low level interfacing +*/ +/**************************************************************************/ + + +bool sgp30_readWordFromCommand(uint8_t command[], uint8_t commandLength, uint16_t delayms, uint16_t *readdata, uint8_t readlen) +{ + i2c_send(_i2caddr, command, commandLength); + + HAL_Delay(delayms); + + if (readlen == 0) + return true; + + uint8_t replylen = readlen * (SGP30_WORD_LEN +1); + uint8_t replybuffer[replylen]; + i2c_receive(_i2caddr, replybuffer, replylen); + + for (uint8_t i=0; i