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 }