kopia lustrzana https://github.com/pimoroni/pimoroni-pico
				
				
				
			
		
			
				
	
	
		
			243 wiersze
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
			
		
		
	
	
			243 wiersze
		
	
	
		
			8.1 KiB
		
	
	
	
		
			C++
		
	
	
| /******************************************************************************
 | |
| sgp30.cpp
 | |
| Code based on "Sensirion_Gas_Sensors_Datasheet_SGP30.pdf" on sensirion.com.
 | |
| Code written by Simon Reap, March 17, 2021
 | |
| https://github.com/simon3270/pico-pimoroni
 | |
| 
 | |
| This code is released under the [MIT License](http://opensource.org/licenses/MIT).
 | |
| Please review the LICENSE file included with this example.
 | |
| Distributed as-is; no warranty is given.
 | |
| ******************************************************************************/
 | |
| 
 | |
| #include "sgp30.hpp"
 | |
| 
 | |
| namespace pimoroni {
 | |
| 
 | |
|   bool SGP30::init() {
 | |
|     soft_reset();
 | |
| 
 | |
|     if(!retrieve_unique_id()) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     // Retrieve and check Feature Set
 | |
|     uint16_t featureset;
 | |
|     if(!read_reg_1_word(GET_FEATURE_SET_VERSION, 10, &featureset))
 | |
|       return false;
 | |
|     if((featureset & 0xF0) != SGP30_REQ_FEATURES)
 | |
|       return false;
 | |
| 
 | |
|     // Start the measurement process
 | |
|     // - parameter true = wait for readings to initialise
 | |
|     //             false = return immediately
 | |
|     // As usual, function returns true if the request succeeded
 | |
|     // if (!start_measurement(true))
 | |
|     //   return false;
 | |
| 
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   i2c_inst_t* SGP30::get_i2c() const {
 | |
|     return i2c->get_i2c();
 | |
|   }
 | |
| 
 | |
|   int SGP30::get_sda() const {
 | |
|     return i2c->get_sda();
 | |
|   }
 | |
| 
 | |
|   int SGP30::get_scl() const {
 | |
|     return i2c->get_scl();
 | |
|   }
 | |
| 
 | |
|   // Get the unique ID from the Chip. Will fail if no chip attached
 | |
|   bool SGP30::retrieve_unique_id() {
 | |
|     // return the Chip ID, in three separate 16-bit values
 | |
|     return read_reg_3_words(GET_SERIAL_ID, 10, serial_number, serial_number + 1, serial_number + 2);
 | |
|   }
 | |
| 
 | |
|   // get the previously-retreved Chip ID as the lower 48 bits of a 64-bit uint
 | |
|   uint64_t SGP30::get_unique_id() {
 | |
|     return (((uint64_t)serial_number[0]) << 32) \
 | |
|       + (((uint64_t)serial_number[1]) << 16) \
 | |
|       + serial_number[2];
 | |
|   }
 | |
| 
 | |
|   // Write a soft reset - writes globally, so all devices on this
 | |
|   // I2C bus receive the request
 | |
|   bool SGP30::soft_reset() {
 | |
|     return write_global(SOFT_RESET, 10);
 | |
|   }
 | |
| 
 | |
|   // Start the measurement process.
 | |
|   // If the parameter is true, wait for the readings to be valid
 | |
|   // If false, return immediately
 | |
|   bool SGP30::start_measurement(bool wait_for_setup) {
 | |
|     // First kick off the "measurement" phase
 | |
|     bool rc = write_reg(INIT_AIR_QUALITY, 10);
 | |
| 
 | |
|     // Optionally wait up to 20 seconds for the measurement process to initiate
 | |
|     if(wait_for_setup) {
 | |
|       // It takes 15 seconds to start the measurement process but allow 20.
 | |
|       // Ignore the first 2 readings completely.
 | |
|       uint16_t eCO2, TVOC;
 | |
|       uint8_t sec_count = 0;
 | |
|       while(sec_count < 20) {
 | |
|         sleep_ms(988); // Will sleep last 12ms of 1sec in get_air_quality()
 | |
|         get_air_quality(&eCO2, &TVOC);
 | |
|         if((sec_count >= 2) && (eCO2 != 400 || TVOC != 0)) {
 | |
|           // startup process finished
 | |
|           break;
 | |
|         }
 | |
|         sec_count++;
 | |
|       }
 | |
|     }
 | |
|     return rc;
 | |
|   }
 | |
| 
 | |
|   // get the air quality values - will be 400 and 0 respectively for the
 | |
|   // first 15 seconds after starting measurement
 | |
|   bool SGP30::get_air_quality(uint16_t * eCO2, uint16_t * TVOC) {
 | |
|     return read_reg_2_words(MEASURE_AIR_QUALITY, 12, eCO2, TVOC);
 | |
|   }
 | |
| 
 | |
|   // Get the raw readings - not useful in real-world settings
 | |
|   bool SGP30::get_air_quality_raw(uint16_t * rawH2, uint16_t * rawEthanol) {
 | |
|     return read_reg_2_words(MEASURE_RAW_SIGNALS, 25, rawH2, rawEthanol);
 | |
|   }
 | |
| 
 | |
|   // Get the baseline compensation values for eCO2 and VOC
 | |
|   bool SGP30::get_baseline(uint16_t *eco2, uint16_t *tvoc) {
 | |
|     return read_reg_2_words(GET_BASELINE, 10, eco2, tvoc);
 | |
|   }
 | |
| 
 | |
|   // Write the baseline compensation values for eCO2 and VOC
 | |
|   void SGP30::set_baseline(uint16_t eco2, uint16_t tvoc) {
 | |
|     write_reg_2_words(SET_BASELINE, 10, eco2, tvoc);
 | |
|   }
 | |
| 
 | |
|   // Set the absolute humidity, e.g. from an SHTxx chip
 | |
|   bool SGP30::set_humidity(uint32_t absolute_humidity) {
 | |
|     if(absolute_humidity > 256000) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     uint16_t ah_scaled = (uint16_t)(((uint64_t)absolute_humidity * 256 * 16777) >> 24);
 | |
| 
 | |
|     return write_reg_1_word(SET_HUMIDITY, 10, ah_scaled);
 | |
|   }
 | |
| 
 | |
|   // Write a single byte globally (not to a specifc I2c address)
 | |
|   bool SGP30::write_global(uint16_t reg, uint16_t delay_ms) {
 | |
|     uint8_t buffer[1] = { (uint8_t)(reg & 0xFF)};
 | |
|     i2c->write_blocking(0, buffer, 1, false);
 | |
|     sleep_ms(delay_ms);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write just the register to the i2c address, no parameter
 | |
|   bool SGP30::write_reg(uint16_t reg, uint16_t delay_ms) {
 | |
|     uint8_t buffer[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF)};
 | |
|     i2c->write_blocking(address, buffer, 2, false);
 | |
|     sleep_ms(delay_ms);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write one 16-bit word (+CRC)
 | |
|   bool SGP30::write_reg_1_word(uint16_t reg, uint16_t delay_ms, uint16_t value) {
 | |
|     uint8_t buffer[5] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF),
 | |
|                           (uint8_t)((value >> 8) & 0xFF), (uint8_t)(value & 0xFF), calculate_crc(value)};
 | |
|     i2c->write_blocking(address, buffer, 5, false);
 | |
|     sleep_ms(delay_ms);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write two 16-bit words (+CRC)
 | |
|   bool SGP30::write_reg_2_words(uint16_t reg, uint16_t delay_ms, uint16_t value1, uint16_t value2) {
 | |
|     uint8_t buffer[8] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF),
 | |
|                           (uint8_t)((value1 >> 8) & 0xFF), (uint8_t)(value1 & 0xFF), calculate_crc(value1),
 | |
|                           (uint8_t)((value2 >> 8) & 0xFF), (uint8_t)(value2 & 0xFF), calculate_crc(value2)};
 | |
|     i2c->write_blocking(address, buffer, 8, false);
 | |
|     sleep_ms(delay_ms);
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write register and read one 16-bit word in response
 | |
|   bool SGP30::read_reg_1_word(uint16_t reg, uint16_t delay_ms, uint16_t *value) {
 | |
|     uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
 | |
|     uint8_t buffer[3];
 | |
|     i2c->write_blocking(address, regbuf, 2, true);
 | |
|     sleep_ms(delay_ms);
 | |
|     i2c->read_blocking(address, buffer, 3, false);
 | |
|     if(buffer[2] != calculate_crc(buffer[0], buffer[1]))
 | |
|       return false;
 | |
|     *value = (buffer[0] << 8) + buffer[1];
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write register and read two 16-bit words
 | |
|   bool SGP30::read_reg_2_words(uint16_t reg, uint16_t delay_ms, uint16_t *value1, uint16_t *value2) {
 | |
|     uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
 | |
|     uint8_t buffer[6];
 | |
|     i2c->write_blocking(address, regbuf, 2, true);
 | |
|     sleep_ms(delay_ms);
 | |
|     i2c->read_blocking(address, buffer, 6, false);
 | |
|     if((buffer[2] != calculate_crc(buffer[0], buffer[1])) || (buffer[5] != calculate_crc(buffer[3], buffer[4]))) {
 | |
|       return false;
 | |
|     }
 | |
|     *value1 = (buffer[0] << 8) + buffer[1];
 | |
|     *value2 = (buffer[3] << 8) + buffer[4];
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // Write register and read three 16-bit words
 | |
|   bool SGP30::read_reg_3_words(uint16_t reg, uint16_t delay_ms, uint16_t *value1, uint16_t *value2, uint16_t *value3) {
 | |
|     uint8_t regbuf[2] = { (uint8_t)((reg >> 8) & 0xFF), (uint8_t)(reg & 0xFF) };
 | |
|     uint8_t buffer[9];
 | |
|     i2c->write_blocking(address, regbuf, 2, true);
 | |
|     sleep_ms(delay_ms);
 | |
|     i2c->read_blocking(address, buffer, 9, false);
 | |
|     if(buffer[2] != calculate_crc(buffer[0], buffer[1])) {
 | |
|       return false;
 | |
|     }
 | |
|     if (buffer[5] != calculate_crc(buffer[3], buffer[4])) {
 | |
|       return false;
 | |
|     }
 | |
|     if (buffer[8] != calculate_crc(buffer[6], buffer[7])) {
 | |
|       return false;
 | |
|     }
 | |
|     *value1 = (buffer[0] << 8) + buffer[1];
 | |
|     *value2 = (buffer[3] << 8) + buffer[4];
 | |
|     *value3 = (buffer[6] << 8) + buffer[7];
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   // calculate the CRC for a single 16-bit word
 | |
|   uint8_t SGP30::calculate_crc(uint16_t value) {
 | |
|     return calculate_crc((value >> 8) && 0xFF, value & 0xFF);
 | |
|   }
 | |
| 
 | |
|   // calculate the CRC for two 8-bit bytes
 | |
|   uint8_t SGP30::calculate_crc(uint8_t value1, uint8_t value2) {
 | |
|     // calculates 8-Bit checksum with given polynomial
 | |
|     uint8_t crc = SGP30_CRC_BASE;
 | |
| 
 | |
|     crc ^= value1;
 | |
|     for(uint8_t b = 0; b < 8; b++) {
 | |
|       if(crc & 0x80)
 | |
|         crc = (crc << 1) ^ SGP30_CRC_SEED;
 | |
|       else
 | |
|         crc <<= 1;
 | |
|     }
 | |
|     crc ^= value2;
 | |
|     for(uint8_t b = 0; b < 8; b++) {
 | |
|       if(crc & 0x80)
 | |
|         crc = (crc << 1) ^ SGP30_CRC_SEED;
 | |
|       else
 | |
|         crc <<= 1;
 | |
|     }
 | |
| 
 | |
|     return crc;
 | |
|   }
 | |
| 
 | |
| }
 |