arduinoprojects

git clone https://git.tarina.org/arduinoprojects
Log | Files | Refs

SparkFunCCS811.cpp (16600B)


      1 /******************************************************************************
      2 SparkFunCCS811.cpp
      3 CCS811 Arduino library
      4 
      5 Marshall Taylor @ SparkFun Electronics
      6 Nathan Seidle @ SparkFun Electronics
      7 
      8 April 4, 2017
      9 
     10 https://github.com/sparkfun/CCS811_Air_Quality_Breakout
     11 https://github.com/sparkfun/SparkFun_CCS811_Arduino_Library
     12 
     13 Resources:
     14 Uses Wire.h for i2c operation
     15 
     16 Development environment specifics:
     17 Arduino IDE 1.8.1
     18 
     19 This code is released under the [MIT License](http://opensource.org/licenses/MIT).
     20 
     21 Please review the LICENSE.md file included with this example. If you have any questions 
     22 or concerns with licensing, please contact techsupport@sparkfun.com.
     23 
     24 Distributed as-is; no warranty is given.
     25 ******************************************************************************/
     26 
     27 //See SparkFunCCS811.h for additional topology notes.
     28 
     29 #include "SparkFunCCS811.h"
     30 #include "stdint.h"
     31 
     32 #include <Arduino.h>
     33 #include "Wire.h"
     34 #include <math.h>
     35 
     36 //****************************************************************************//
     37 //
     38 //  CCS811Core functions
     39 //
     40 //  Default <address> is 0x5B.
     41 //
     42 //****************************************************************************//
     43 CCS811Core::CCS811Core(uint8_t inputArg) : I2CAddress(inputArg)
     44 {
     45 }
     46 
     47 CCS811Core::CCS811_Status_e CCS811Core::beginCore(TwoWire &wirePort)
     48 {
     49 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS;
     50 
     51 	_i2cPort = &wirePort; //Pull in user's choice of I2C hardware
     52 
     53 	//Wire.begin(); //See issue 13 https://github.com/sparkfun/SparkFun_CCS811_Arduino_Library/issues/13
     54 
     55 #ifdef __AVR__
     56 #endif
     57 
     58 #ifdef __MK20DX256__
     59 #endif
     60 
     61 #if defined(ARDUINO_ARCH_ESP8266)
     62 	_i2cPort->setClockStretchLimit(200000); // was default 230 uS, now 200ms
     63 #endif
     64 
     65 	//Spin for a few ms
     66 	volatile uint8_t temp = 0;
     67 	for (uint16_t i = 0; i < 10000; i++)
     68 	{
     69 		temp++;
     70 	}
     71 
     72 	//Check the ID register to determine if the operation was a success.
     73 	uint8_t readCheck;
     74 	readCheck = 0;
     75 	returnError = readRegister(CSS811_HW_ID, &readCheck);
     76 
     77 	if (returnError != CCS811_Stat_SUCCESS)
     78 		return returnError;
     79 
     80 	if (readCheck != 0x81)
     81 	{
     82 		returnError = CCS811_Stat_ID_ERROR;
     83 	}
     84 
     85 	return returnError;
     86 }
     87 
     88 //****************************************************************************//
     89 //
     90 //  ReadRegister
     91 //
     92 //  Parameters:
     93 //    offset -- register to read
     94 //    *outputPointer -- Pass &variable (address of) to save read data to
     95 //
     96 //****************************************************************************//
     97 CCS811Core::CCS811_Status_e CCS811Core::readRegister(uint8_t offset, uint8_t *outputPointer)
     98 {
     99 	//Return value
    100 	uint8_t result = 1;
    101 	uint8_t numBytes = 1;
    102 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS;
    103 
    104 	_i2cPort->beginTransmission(I2CAddress);
    105 	_i2cPort->write(offset);
    106 	if (_i2cPort->endTransmission() != 0)
    107 	{
    108 		returnError = CCS811_Stat_I2C_ERROR;
    109 	}
    110 
    111 	_i2cPort->requestFrom(I2CAddress, numBytes);
    112 	*outputPointer = _i2cPort->read(); // receive a byte as a proper uint8_t
    113 
    114 	return returnError;
    115 }
    116 
    117 //****************************************************************************//
    118 //
    119 //  multiReadRegister
    120 //
    121 //  Parameters:
    122 //    offset -- register to read
    123 //    *outputPointer -- Pass &variable (base address of) to save read data to
    124 //    length -- number of bytes to read
    125 //
    126 //  Note:  Does not know if the target memory space is an array or not, or
    127 //    if there is the array is big enough.  if the variable passed is only
    128 //    two bytes long and 3 bytes are requested, this will over-write some
    129 //    other memory!
    130 //
    131 //****************************************************************************//
    132 CCS811Core::CCS811_Status_e CCS811Core::multiReadRegister(uint8_t offset, uint8_t *outputPointer, uint8_t length)
    133 {
    134 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS;
    135 
    136 	//define pointer that will point to the external space
    137 	uint8_t i = 0;
    138 	uint8_t c = 0;
    139 	//Set the address
    140 	_i2cPort->beginTransmission(I2CAddress);
    141 	_i2cPort->write(offset);
    142 	if (_i2cPort->endTransmission() != 0)
    143 	{
    144 		returnError = CCS811_Stat_I2C_ERROR;
    145 	}
    146 	else //OK, all worked, keep going
    147 	{
    148 		// request 6 bytes from slave device
    149 		_i2cPort->requestFrom(I2CAddress, length);
    150 		while ((_i2cPort->available()) && (i < length)) // slave may send less than requested
    151 		{
    152 			c = _i2cPort->read(); // receive a byte as character
    153 			*outputPointer = c;
    154 			outputPointer++;
    155 			i++;
    156 		}
    157 	}
    158 
    159 	return returnError;
    160 }
    161 
    162 //****************************************************************************//
    163 //
    164 //  writeRegister
    165 //
    166 //  Parameters:
    167 //    offset -- register to write
    168 //    dataToWrite -- 8 bit data to write to register
    169 //
    170 //****************************************************************************//
    171 CCS811Core::CCS811_Status_e CCS811Core::writeRegister(uint8_t offset, uint8_t dataToWrite)
    172 {
    173 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS;
    174 
    175 	_i2cPort->beginTransmission(I2CAddress);
    176 	_i2cPort->write(offset);
    177 	_i2cPort->write(dataToWrite);
    178 	if (_i2cPort->endTransmission() != 0)
    179 	{
    180 		returnError = CCS811_Stat_I2C_ERROR;
    181 	}
    182 	return returnError;
    183 }
    184 
    185 //****************************************************************************//
    186 //
    187 //  multiReadRegister
    188 //
    189 //  Parameters:
    190 //    offset -- register to read
    191 //    *inputPointer -- Pass &variable (base address of) to save read data to
    192 //    length -- number of bytes to read
    193 //
    194 //  Note:  Does not know if the target memory space is an array or not, or
    195 //    if there is the array is big enough.  if the variable passed is only
    196 //    two bytes long and 3 bytes are requested, this will over-write some
    197 //    other memory!
    198 //
    199 //****************************************************************************//
    200 CCS811Core::CCS811_Status_e CCS811Core::multiWriteRegister(uint8_t offset, uint8_t *inputPointer, uint8_t length)
    201 {
    202 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS;
    203 	//define pointer that will point to the external space
    204 	uint8_t i = 0;
    205 	//Set the address
    206 	_i2cPort->beginTransmission(I2CAddress);
    207 	_i2cPort->write(offset);
    208 	while (i < length) // send data bytes
    209 	{
    210 		_i2cPort->write(*inputPointer); // receive a byte as character
    211 		inputPointer++;
    212 		i++;
    213 	}
    214 	if (_i2cPort->endTransmission() != 0)
    215 	{
    216 		returnError = CCS811_Stat_I2C_ERROR;
    217 	}
    218 	return returnError;
    219 }
    220 
    221 //****************************************************************************//
    222 //
    223 //  Main user class -- wrapper for the core class + maths
    224 //
    225 //  Construct with same rules as the core ( uint8_t busType, uint8_t inputArg )
    226 //
    227 //****************************************************************************//
    228 CCS811::CCS811(uint8_t inputArg) : CCS811Core(inputArg)
    229 {
    230 	refResistance = 10000; //Unsupported feature. 
    231 	resistance = 0; //Unsupported feature. 
    232 	temperature = 0;
    233 	tVOC = 0;
    234 	CO2 = 0;
    235 }
    236 
    237 //****************************************************************************//
    238 //
    239 //  Begin
    240 //
    241 //  This starts the lower level begin, then applies settings
    242 //
    243 //****************************************************************************//
    244 bool CCS811::begin(TwoWire &wirePort)
    245 {
    246 	if (beginWithStatus(wirePort) == CCS811_Stat_SUCCESS)
    247 		return true;
    248 	return false;
    249 }
    250 
    251 //****************************************************************************//
    252 //
    253 //  Begin
    254 //
    255 //  This starts the lower level begin, then applies settings
    256 //
    257 //****************************************************************************//
    258 CCS811Core::CCS811_Status_e CCS811::beginWithStatus(TwoWire &wirePort)
    259 {
    260 	uint8_t data[4] = {0x11, 0xE5, 0x72, 0x8A};					   //Reset key
    261 	CCS811Core::CCS811_Status_e returnError = CCS811_Stat_SUCCESS; //Default error state
    262 
    263 	//restart the core
    264 	returnError = beginCore(wirePort);
    265 
    266 	if (returnError != CCS811_Stat_SUCCESS)
    267 		return returnError;
    268 
    269 	//Reset the device
    270 	multiWriteRegister(CSS811_SW_RESET, data, 4);
    271 
    272 	//Tclk = 1/16MHz = 0x0000000625
    273 	//0.001 s / tclk = 16000 counts
    274 	volatile uint8_t temp = 0;
    275 
    276 #ifdef ARDUINO_ARCH_ESP32
    277 	for (uint32_t i = 0; i < 80000; i++) //This waits > 1ms @ 80MHz clock
    278 	{
    279 		temp++;
    280 	}
    281 #elif __AVR__
    282 	for (uint16_t i = 0; i < 16000; i++) //This waits > 1ms @ 16MHz clock
    283 	{
    284 		temp++;
    285 	}
    286 #else
    287 	for (uint32_t i = 0; i < 200000; i++) //Spin for a good while
    288 	{
    289 		temp++;
    290 	}
    291 #endif
    292 
    293 	if (checkForStatusError() == true)
    294 		return CCS811_Stat_INTERNAL_ERROR;
    295 
    296 	if (appValid() == false)
    297 		return CCS811_Stat_INTERNAL_ERROR;
    298 
    299 	//Write 0 bytes to this register to start app
    300 	_i2cPort->beginTransmission(I2CAddress);
    301 	_i2cPort->write(CSS811_APP_START);
    302 	if (_i2cPort->endTransmission() != 0)
    303 	{
    304 		return CCS811_Stat_I2C_ERROR;
    305 	}
    306 
    307 	//Added from issue 6
    308 	// Without a delay here, the CCS811 and I2C can be put in a bad state.
    309 	// Seems to work with 50us delay, but make a bit longer to be sure.
    310 #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
    311 	delayMicroseconds(100);
    312 #endif
    313 
    314 	returnError = setDriveMode(1); //Read every second
    315 
    316 	return returnError;
    317 }
    318 
    319 //****************************************************************************//
    320 //
    321 //  Sensor functions
    322 //
    323 //****************************************************************************//
    324 //Updates the total voltatile organic compounds (TVOC) in parts per billion (PPB)
    325 //and the CO2 value
    326 //Returns nothing
    327 CCS811Core::CCS811_Status_e CCS811::readAlgorithmResults(void)
    328 {
    329 	uint8_t data[4];
    330 	CCS811Core::CCS811_Status_e returnError = multiReadRegister(CSS811_ALG_RESULT_DATA, data, 4);
    331 	if (returnError != CCS811_Stat_SUCCESS)
    332 		return returnError;
    333 	// Data ordered:
    334 	// co2MSB, co2LSB, tvocMSB, tvocLSB
    335 
    336 	CO2 = ((uint16_t)data[0] << 8) | data[1];
    337 	tVOC = ((uint16_t)data[2] << 8) | data[3];
    338 	return CCS811_Stat_SUCCESS;
    339 }
    340 
    341 //Checks to see if error bit is set
    342 bool CCS811::checkForStatusError(void)
    343 {
    344 	uint8_t value;
    345 	//return the status bit
    346 	readRegister(CSS811_STATUS, &value);
    347 	return (value & 1 << 0);
    348 }
    349 
    350 //Checks to see if DATA_READ flag is set in the status register
    351 bool CCS811::dataAvailable(void)
    352 {
    353 	uint8_t value;
    354 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_STATUS, &value);
    355 	if (returnError != CCS811_Stat_SUCCESS)
    356 	{
    357 		return 0;
    358 	}
    359 	else
    360 	{
    361 		return (value & 1 << 3);
    362 	}
    363 }
    364 
    365 //Checks to see if APP_VALID flag is set in the status register
    366 bool CCS811::appValid(void)
    367 {
    368 	uint8_t value;
    369 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_STATUS, &value);
    370 	if (returnError != CCS811_Stat_SUCCESS)
    371 	{
    372 		return 0;
    373 	}
    374 	else
    375 	{
    376 		return (value & 1 << 4);
    377 	}
    378 }
    379 
    380 uint8_t CCS811::getErrorRegister(void)
    381 {
    382 	uint8_t value;
    383 
    384 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_ERROR_ID, &value);
    385 	if (returnError != CCS811_Stat_SUCCESS)
    386 	{
    387 		return 0xFF;
    388 	}
    389 	else
    390 	{
    391 		return value; //Send all errors in the event of communication error
    392 	}
    393 }
    394 
    395 //Returns the baseline value
    396 //Used for telling sensor what 'clean' air is
    397 //You must put the sensor in clean air and record this value
    398 uint16_t CCS811::getBaseline(void)
    399 {
    400 	uint8_t data[2];
    401 	CCS811Core::CCS811_Status_e returnError = multiReadRegister(CSS811_BASELINE, data, 2);
    402 
    403 	unsigned int baseline = ((uint16_t)data[0] << 8) | data[1];
    404 	if (returnError != CCS811_Stat_SUCCESS)
    405 	{
    406 		return 0;
    407 	}
    408 	else
    409 	{
    410 		return (baseline);
    411 	}
    412 }
    413 
    414 CCS811Core::CCS811_Status_e CCS811::setBaseline(uint16_t input)
    415 {
    416 	uint8_t data[2];
    417 	data[0] = (input >> 8) & 0x00FF;
    418 	data[1] = input & 0x00FF;
    419 
    420 	CCS811Core::CCS811_Status_e returnError = multiWriteRegister(CSS811_BASELINE, data, 2);
    421 
    422 	return returnError;
    423 }
    424 
    425 //Enable the nINT signal
    426 CCS811Core::CCS811_Status_e CCS811::enableInterrupts(void)
    427 {
    428 	uint8_t value;
    429 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_MEAS_MODE, &value); //Read what's currently there
    430 	if (returnError != CCS811_Stat_SUCCESS)
    431 		return returnError;
    432 	value |= (1 << 3); //Set INTERRUPT bit
    433 	writeRegister(CSS811_MEAS_MODE, value);
    434 	return returnError;
    435 }
    436 
    437 //Disable the nINT signal
    438 CCS811Core::CCS811_Status_e CCS811::disableInterrupts(void)
    439 {
    440 	uint8_t value;
    441 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_MEAS_MODE, &value); //Read what's currently there
    442 	if (returnError != CCS811_Stat_SUCCESS)
    443 		return returnError;
    444 	value &= ~(1 << 3); //Clear INTERRUPT bit
    445 	returnError = writeRegister(CSS811_MEAS_MODE, value);
    446 	return returnError;
    447 }
    448 
    449 //Mode 0 = Idle
    450 //Mode 1 = read every 1s
    451 //Mode 2 = every 10s
    452 //Mode 3 = every 60s
    453 //Mode 4 = RAW mode
    454 CCS811Core::CCS811_Status_e CCS811::setDriveMode(uint8_t mode)
    455 {
    456 	if (mode > 4)
    457 		mode = 4; //sanitize input
    458 
    459 	uint8_t value;
    460 	CCS811Core::CCS811_Status_e returnError = readRegister(CSS811_MEAS_MODE, &value); //Read what's currently there
    461 	if (returnError != CCS811_Stat_SUCCESS)
    462 		return returnError;
    463 	value &= ~(0b00000111 << 4); //Clear DRIVE_MODE bits
    464 	value |= (mode << 4);		 //Mask in mode
    465 	returnError = writeRegister(CSS811_MEAS_MODE, value);
    466 	return returnError;
    467 }
    468 
    469 //Given a temp and humidity, write this data to the CSS811 for better compensation
    470 //This function expects the humidity and temp to come in as floats
    471 CCS811Core::CCS811_Status_e CCS811::setEnvironmentalData(float relativeHumidity, float temperature)
    472 {
    473 	//Check for invalid temperatures
    474 	if ((temperature < -25) || (temperature > 50))
    475 		return CCS811_Stat_GENERIC_ERROR;
    476 
    477 	//Check for invalid humidity
    478 	if ((relativeHumidity < 0) || (relativeHumidity > 100))
    479 		return CCS811_Stat_GENERIC_ERROR;
    480 
    481 	uint32_t rH = relativeHumidity * 1000; //42.348 becomes 42348
    482 	uint32_t temp = temperature * 1000;	//23.2 becomes 23200
    483 
    484 	byte envData[4];
    485 
    486 	//Split value into 7-bit integer and 9-bit fractional
    487 
    488 	//Incorrect way from datasheet.
    489 	//envData[0] = ((rH % 1000) / 100) > 7 ? (rH / 1000 + 1) << 1 : (rH / 1000) << 1;
    490 	//envData[1] = 0; //CCS811 only supports increments of 0.5 so bits 7-0 will always be zero
    491 	//if (((rH % 1000) / 100) > 2 && (((rH % 1000) / 100) < 8))
    492 	//{
    493 	//	envData[0] |= 1; //Set 9th bit of fractional to indicate 0.5%
    494 	//}
    495 
    496 	//Correct rounding. See issue 8: https://github.com/sparkfun/Qwiic_BME280_CCS811_Combo/issues/8
    497 	envData[0] = (rH + 250) / 500;
    498 	envData[1] = 0; //CCS811 only supports increments of 0.5 so bits 7-0 will always be zero
    499 
    500 	temp += 25000; //Add the 25C offset
    501 	//Split value into 7-bit integer and 9-bit fractional
    502 	//envData[2] = ((temp % 1000) / 100) > 7 ? (temp / 1000 + 1) << 1 : (temp / 1000) << 1;
    503 	//envData[3] = 0;
    504 	//if (((temp % 1000) / 100) > 2 && (((temp % 1000) / 100) < 8))
    505 	//{
    506 	//	envData[2] |= 1;  //Set 9th bit of fractional to indicate 0.5C
    507 	//}
    508 
    509 	//Correct rounding
    510 	envData[2] = (temp + 250) / 500;
    511 	envData[3] = 0;
    512 
    513 	CCS811Core::CCS811_Status_e returnError = multiWriteRegister(CSS811_ENV_DATA, envData, 4);
    514 	return returnError;
    515 }
    516 
    517 uint16_t CCS811::getTVOC(void)
    518 {
    519 	return tVOC;
    520 }
    521 
    522 uint16_t CCS811::getCO2(void)
    523 {
    524 	return CO2;
    525 }
    526 
    527 //****************************************************************************//
    528 //
    529 //	The CCS811 no longer supports temperature compensation from an NTC thermistor.
    530 //	NTC thermistor compensation will only work on boards purchased in 2017.
    531 //	List of unsupported functions:
    532 //		setRefResistance();
    533 //		readNTC();
    534 //		getResistance();
    535 //		getTemperature();	
    536 //
    537 //****************************************************************************//
    538 
    539 void CCS811::setRefResistance(float input)
    540 {
    541 	refResistance = input;
    542 }
    543 
    544 CCS811Core::CCS811_Status_e CCS811::readNTC(void)
    545 {
    546 	uint8_t data[4];
    547 	CCS811Core::CCS811_Status_e returnError = multiReadRegister(CSS811_NTC, data, 4);
    548 
    549 	vrefCounts = ((uint16_t)data[0] << 8) | data[1];
    550 	//Serial.print("vrefCounts: ");
    551 	//Serial.println(vrefCounts);
    552 	ntcCounts = ((uint16_t)data[2] << 8) | data[3];
    553 	//Serial.print("ntcCounts: ");
    554 	//Serial.println(ntcCounts);
    555 	//Serial.print("sum: ");
    556 	//Serial.println(ntcCounts + vrefCounts);
    557 	resistance = ((float)ntcCounts * refResistance / (float)vrefCounts);
    558 
    559 	//Code from Milan Malesevic and Zoran Stupic, 2011,
    560 	//Modified by Max Mayfield,
    561 	temperature = log((long)resistance);
    562 	temperature = 1 / (0.001129148 + (0.000234125 * temperature) + (0.0000000876741 * temperature * temperature * temperature));
    563 	temperature = temperature - 273.15; // Convert Kelvin to Celsius
    564 
    565 	return returnError;
    566 }
    567 
    568 float CCS811::getResistance(void)
    569 {
    570 	return resistance;
    571 }
    572 
    573 float CCS811::getTemperature(void)
    574 {
    575 	return temperature;
    576 }
    577 
    578 const char *CCS811::statusString(CCS811_Status_e stat)
    579 {
    580 	CCS811_Status_e val;
    581 	if (stat == CCS811_Stat_NUM)
    582 	{
    583 		val = stat;
    584 	}
    585 	else
    586 	{
    587 		val = stat;
    588 	}
    589 
    590 	switch (val)
    591 	{
    592 	case CCS811_Stat_SUCCESS:
    593 		return "All is well.";
    594 		break;
    595 	case CCS811_Stat_ID_ERROR:
    596 		return "ID Error";
    597 		break;
    598 	case CCS811_Stat_I2C_ERROR:
    599 		return "I2C Error";
    600 		break;
    601 	case CCS811_Stat_INTERNAL_ERROR:
    602 		return "Internal Error";
    603 		break;
    604 	case CCS811_Stat_GENERIC_ERROR:
    605 		return "Generic Error";
    606 		break;
    607 	default:
    608 		return "Unknown Status";
    609 		break;
    610 	}
    611 	return "None";
    612 }