Web IDE library compile issue (Adafruit CCS811)

Hi,

I’m preparing a training workshop for a group of colleagues on “IoT”, so we’re building a Photon based connected sensor and IFTTT interaction. I really want to use the CCS811 Gas sensor from Adafruit, but for setup simplicity, I have to use the Web IDE (I’m not going to setup 24 user laptops with the desktop IDE in a 1.5 hour workshop).

So even if I just include the Adafruit_CCS811 library, and the required Arduino.h, I get a compile error complaining about 2 functions - modf and log. Any idea how I can get past this with the Web IDE in short order? I don’t have time to get an updated library publish on Particle. I tried manually including math.h too, but that doesn’t help. Here’s my code:

#include <Arduino.h>
#include <math.h>
#include <Adafruit_CCS811.h>

void setup() {

}

void loop() {

}

And here’s the errors in Web IDE:
2018-03-08%2016_51_11-Particle%20Build

what happens when you comment out the Arduino libraries?

Compile error requiring Arduino.h

Have you tried the CCS811 library in the build IDE?

Yes that’s the one that won’t compile. If you create a new app and add that library and the do a cloud compile you’ll get the error message.

You need to load the CCS811_test.ino file example and it will compile just fine. Not just the .cpp and .H file.

Just tried that but still getting the same issue. Are you able to compile? Here’s a short tube of my madness :slight_smile:

For some reason i had to manually add the cpp a h files to the build ide to get it to compile.

Still no dice here. I see you have a photon though with 0.6.3 firmware… maybe there’s something with that. I’m on an electron 0.6.0.

Try these files. From my working files.

.cpp

#include "Adafruit_CCS811.h"

bool Adafruit_CCS811::begin(uint8_t addr)
{
	_i2caddr = addr;
	
	_i2c_init();

	SWReset();
	delay(100);
	
	//check that the HW id is correct
	if(this->read8(CCS811_HW_ID) != CCS811_HW_ID_CODE)
		return false;
	
	//try to start the app
	this->write(CCS811_BOOTLOADER_APP_START, NULL, 0);
	delay(100);
	
	//make sure there are no errors and we have entered application mode
	if(checkError()) return false;
	if(!_status.FW_MODE) return false;
	
	disableInterrupt();
	
	//default to read every second
	setDriveMode(CCS811_DRIVE_MODE_1SEC);
	
	return true;
}

void Adafruit_CCS811::setDriveMode(uint8_t mode)
{
	_meas_mode.DRIVE_MODE = mode;
	this->write8(CCS811_MEAS_MODE, _meas_mode.get());
}

void Adafruit_CCS811::enableInterrupt()
{
	_meas_mode.INT_DATARDY = 1;
	this->write8(CCS811_MEAS_MODE, _meas_mode.get());
}

void Adafruit_CCS811::disableInterrupt()
{
	_meas_mode.INT_DATARDY = 0;
	this->write8(CCS811_MEAS_MODE, _meas_mode.get());
}

bool Adafruit_CCS811::available()
{
	_status.set(read8(CCS811_STATUS));
	if(!_status.DATA_READY)
		return false;
	else return true;
}

uint8_t Adafruit_CCS811::readData()
{
	if(!available())
		return false;
	else{
		uint8_t buf[8];
		this->read(CCS811_ALG_RESULT_DATA, buf, 8);

		_eCO2 = ((uint16_t)buf[0] << 8) | ((uint16_t)buf[1]);
		_TVOC = ((uint16_t)buf[2] << 8) | ((uint16_t)buf[3]);
		
		if(_status.ERROR)
			return buf[5];
			
		else return 0;
	}
}

void Adafruit_CCS811::setEnvironmentalData(uint8_t humidity, double temperature)
{
	/* Humidity is stored as an unsigned 16 bits in 1/512%RH. The
	default value is 50% = 0x64, 0x00. As an example 48.5%
	humidity would be 0x61, 0x00.*/
	
	/* Temperature is stored as an unsigned 16 bits integer in 1/512
	degrees; there is an offset: 0 maps to -25�C. The default value is
	25�C = 0x64, 0x00. As an example 23.5% temperature would be
	0x61, 0x00.
	The internal algorithm uses these values (or default values if
	not set by the application) to compensate for changes in
	relative humidity and ambient temperature.*/
	
	uint8_t hum_perc = humidity << 1;
	
	float fractional = modf(temperature, &temperature);
	uint16_t temp_high = (((uint16_t)temperature + 25) << 9);
	uint16_t temp_low = ((uint16_t)(fractional / 0.001953125) & 0x1FF);
	
	uint16_t temp_conv = (temp_high | temp_low);

	uint8_t buf[] = {hum_perc, 0x00,
		(uint8_t)((temp_conv >> 8) & 0xFF), (uint8_t)(temp_conv & 0xFF)};
	
	this->write(CCS811_ENV_DATA, buf, 4);

}

//calculate temperature based on the NTC register
double Adafruit_CCS811::calculateTemperature()
{
	uint8_t buf[4];
	this->read(CCS811_NTC, buf, 4);

	uint32_t vref = ((uint32_t)buf[0] << 8) | buf[1];
	uint32_t vntc = ((uint32_t)buf[2] << 8) | buf[3];
	
	//from ams ccs811 app note
	uint32_t rntc = vntc * CCS811_REF_RESISTOR / vref;
	
	double ntc_temp;
	ntc_temp = log((double)rntc / CCS811_REF_RESISTOR); // 1
	ntc_temp /= 3380; // 2
	ntc_temp += 1.0 / (25 + 273.15); // 3
	ntc_temp = 1.0 / ntc_temp; // 4
	ntc_temp -= 273.15; // 5
	return ntc_temp - _tempOffset;

}

void Adafruit_CCS811::setThresholds(uint16_t low_med, uint16_t med_high, uint8_t hysteresis)
{
	uint8_t buf[] = {(uint8_t)((low_med >> 8) & 0xF), (uint8_t)(low_med & 0xF),
	(uint8_t)((med_high >> 8) & 0xF), (uint8_t)(med_high & 0xF), hysteresis};
	
	this->write(CCS811_THRESHOLDS, buf, 5);
}

void Adafruit_CCS811::SWReset()
{
	//reset sequence from the datasheet
	uint8_t seq[] = {0x11, 0xE5, 0x72, 0x8A};
	this->write(CCS811_SW_RESET, seq, 4);
}

bool Adafruit_CCS811::checkError()
{
	_status.set(read8(CCS811_STATUS));
	return _status.ERROR;
}

void Adafruit_CCS811::write8(byte reg, byte value)
{
	this->write(reg, &value, 1);
}

uint8_t Adafruit_CCS811::read8(byte reg)
{
	uint8_t ret;
	this->read(reg, &ret, 1);
	
	return ret;
}

void Adafruit_CCS811::_i2c_init()
{
	Wire.begin();
}

void Adafruit_CCS811::read(uint8_t reg, uint8_t *buf, uint8_t num)
{
	uint8_t value;
	uint8_t pos = 0;
	
	//on arduino we need to read in 32 byte chunks
	while(pos < num){
		
		uint8_t read_now = min(32, num - pos);
		Wire.beginTransmission((uint8_t)_i2caddr);
		Wire.write((uint8_t)reg + pos);
		Wire.endTransmission();
		Wire.requestFrom((uint8_t)_i2caddr, read_now);
		
		for(int i=0; i<read_now; i++){
			buf[pos] = Wire.read();
			pos++;
		}
	}
}

void Adafruit_CCS811::write(uint8_t reg, uint8_t *buf, uint8_t num)
{
	Wire.beginTransmission((uint8_t)_i2caddr);
	Wire.write((uint8_t)reg);
	Wire.write((uint8_t *)buf, num);
	Wire.endTransmission();
}

.h

#ifndef LIB_ADAFRUIT_CCS811_H
#define LIB_ADAFRUIT_CCS811_H

#if (ARDUINO >= 100)
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

#include <Wire.h>

/*=========================================================================
    I2C ADDRESS/BITS
    -----------------------------------------------------------------------*/
    #define CCS811_ADDRESS                (0x5A)
/*=========================================================================*/

/*=========================================================================
    REGISTERS
    -----------------------------------------------------------------------*/
    enum
    {
        CCS811_STATUS = 0x00,
        CCS811_MEAS_MODE = 0x01,
        CCS811_ALG_RESULT_DATA = 0x02,
        CCS811_RAW_DATA = 0x03,
        CCS811_ENV_DATA = 0x05,
        CCS811_NTC = 0x06,
        CCS811_THRESHOLDS = 0x10,
        CCS811_BASELINE = 0x11,
        CCS811_HW_ID = 0x20,
        CCS811_HW_VERSION = 0x21,
        CCS811_FW_BOOT_VERSION = 0x23,
        CCS811_FW_APP_VERSION = 0x24,
        CCS811_ERROR_ID = 0xE0,
        CCS811_SW_RESET = 0xFF,
    };
	
	//bootloader registers
	enum
	{
		CCS811_BOOTLOADER_APP_ERASE = 0xF1,
		CCS811_BOOTLOADER_APP_DATA = 0xF2,
		CCS811_BOOTLOADER_APP_VERIFY = 0xF3,
		CCS811_BOOTLOADER_APP_START = 0xF4
	};
	
	enum
	{
		CCS811_DRIVE_MODE_IDLE = 0x00,
		CCS811_DRIVE_MODE_1SEC = 0x01,
		CCS811_DRIVE_MODE_10SEC = 0x02,
		CCS811_DRIVE_MODE_60SEC = 0x03,
		CCS811_DRIVE_MODE_250MS = 0x04,
	};

/*=========================================================================*/

#define CCS811_HW_ID_CODE			0x81

#define CCS811_REF_RESISTOR			100000

class Adafruit_CCS811 {
	public:
		//constructors
		Adafruit_CCS811(void) {};
		~Adafruit_CCS811(void) {};
		
		bool begin(uint8_t addr = CCS811_ADDRESS);

		void setEnvironmentalData(uint8_t humidity, double temperature);

		//calculate temperature based on the NTC register
		double calculateTemperature();
		
		void setThresholds(uint16_t low_med, uint16_t med_high, uint8_t hysteresis = 50);

		void SWReset();
		
		void setDriveMode(uint8_t mode);
		void enableInterrupt();
		void disableInterrupt();
		
		uint16_t getTVOC() { return _TVOC; }
		uint16_t geteCO2() { return _eCO2; }
		
		void setTempOffset(float offset) { _tempOffset = offset; }
		
		//check if data is available to be read
		bool available();
		uint8_t readData();
		
		bool checkError();

	private:
		uint8_t _i2caddr;
		float _tempOffset;
		
		uint16_t _TVOC;
		uint16_t _eCO2;
		
		void      write8(byte reg, byte value);
		void      write16(byte reg, uint16_t value);
        uint8_t   read8(byte reg);
		
		void read(uint8_t reg, uint8_t *buf, uint8_t num);
		void write(uint8_t reg, uint8_t *buf, uint8_t num);
		void _i2c_init();
		
/*=========================================================================
	REGISTER BITFIELDS
    -----------------------------------------------------------------------*/
		// The status register
        struct status {
           
            /* 0: no error
            *  1: error has occurred
            */ 
            uint8_t ERROR: 1;

            // reserved : 2

            /* 0: no samples are ready
            *  1: samples are ready
            */ 
            uint8_t DATA_READY: 1;
            uint8_t APP_VALID: 1;

			// reserved : 2

            /* 0: boot mode, new firmware can be loaded
            *  1: application mode, can take measurements
            */
            uint8_t FW_MODE: 1;

            void set(uint8_t data){
            	ERROR = data & 0x01;
            	DATA_READY = (data >> 3) & 0x01;
            	APP_VALID = (data >> 4) & 0x01;
            	FW_MODE = (data >> 7) & 0x01;
            }
        };
        status _status;

        //measurement and conditions register
        struct meas_mode {
        	// reserved : 2

        	/* 0: interrupt mode operates normally
            *  1: Interrupt mode (if enabled) only asserts the nINT signal (driven low) if the new
				ALG_RESULT_DATA crosses one of the thresholds set in the THRESHOLDS register
				by more than the hysteresis value (also in the THRESHOLDS register)
            */ 
        	uint8_t INT_THRESH: 1;

        	/* 0: int disabled
            *  1: The nINT signal is asserted (driven low) when a new sample is ready in
				ALG_RESULT_DATA. The nINT signal will stop being driven low when
				ALG_RESULT_DATA is read on the I²C interface.
            */ 
        	uint8_t INT_DATARDY: 1;

        	uint8_t DRIVE_MODE: 3;

        	uint8_t get(){
        		return (INT_THRESH << 2) | (INT_DATARDY << 3) | (DRIVE_MODE << 4);
        	}
        };
        meas_mode _meas_mode;
		
        struct error_id {
        	/* The CCS811 received an I²C write request addressed to this station but with
			invalid register address ID */
        	uint8_t WRITE_REG_INVALID: 1;

        	/* The CCS811 received an I²C read request to a mailbox ID that is invalid */
        	uint8_t READ_REG_INVALID: 1;

        	/* The CCS811 received an I²C request to write an unsupported mode to
			MEAS_MODE */        	
        	uint8_t MEASMODE_INVALID: 1;

        	/* The sensor resistance measurement has reached or exceeded the maximum
			range */
        	uint8_t MAX_RESISTANCE: 1;

        	/* The Heater current in the CCS811 is not in range */
        	uint8_t HEATER_FAULT: 1;

        	/*  The Heater voltage is not being applied correctly */
        	uint8_t HEATER_SUPPLY: 1;

        	void set(uint8_t data){
        		WRITE_REG_INVALID = data & 0x01;
        		READ_REG_INVALID = (data & 0x02) >> 1;
        		MEASMODE_INVALID = (data & 0x04) >> 2;
        		MAX_RESISTANCE = (data & 0x08) >> 3;
        		HEATER_FAULT = (data & 0x10) >> 4;
        		HEATER_SUPPLY = (data & 0x20) >> 5;
        	}
        };
        error_id _error_id;

/*=========================================================================*/
};

#endif

Woot! Well I did not realize you could create additional files from the WEB ide! Lord I feel dumb. I still had to comment out the lines that have the modf and log functions in them, but I don’t use the temp from this CCS811 sensor anyways so it’s working now!

Thanks RWB for the persistent help!

Here is my main .ino code:

/***************************************************************************
  This is a library for the CCS811 air

  This sketch reads the sensor

  Designed specifically to work with the Adafruit CCS811 breakout
  ----> http://www.adafruit.com/products/3566

  These sensors use I2C to communicate. The device's I2C address is 0x5A

  Adafruit invests time and resources providing this open source code,
  please support Adafruit andopen-source hardware by purchasing products
  from Adafruit!

  Written by Dean Miller for Adafruit Industries.
  BSD license, all text above must be included in any redistribution
 ***************************************************************************/
//SYSTEM_THREAD(ENABLED);

#include "Adafruit_CCS811.h"
#include "math.h"


Adafruit_CCS811 ccs;






void setup() {
  Serial.begin(9600);
  delay(5000);
  Serial.println("CCS811 test");

  if(!ccs.begin()){
    Serial.println("Failed to start sensor! Please check your wiring.");
    while(1);
  }

 

void loop() {




 

  if(ccs.available()){

    if(!ccs.readData()){

      Serial.print("CO2: ");
      Serial.print(ccs.geteCO2());
      Serial.println(" ppm");
      Serial.print("TVOC ");
      Serial.print(ccs.getTVOC());
      Serial.println("ppb");
      Serial.println("");

    }
    else{
      Serial.println("ERROR!");
      while(1);
    }
}

  delay(1000);
}

Woot! Well I did not realize you could create additional files from the WEB ide! Lord I feel dumb. I still had to comment out the lines that have the modf and log functions in them, but I don’t use the temp from this CCS811 sensor anyways so it’s working now!

Thanks

With my main .INO code above you should not have to comment out the modf and log functions and the temp function should still work.

The issues you are seeing come from the fact that 0.6.1 was the first system version that boosted the Arduino compatibility considerably.
When you set 0.6.1+ as your build target (it’s always a good idea to go for the latest stable vesion when seeing problems) the samples will build as expected - without need for any extra messing.

I think this is a more viable solution than the copy/paste/editing of library files.

I may be adding a compile error output when using this library with a system pre 0.6.1.


Update:
Done. Now you get a clearer error message when targeting an incompatible version

Beautiful! Thanks all for the help on this one!

Sorry, one more ?

I normally use the windows or mac Firmware manager, and when I download it, it’s pulling down a 0.6.0 firmware only (filename of the firmware manager even reflects 0.6.0 on the getting started guide: https://docs.particle.io/guide/tools-and-features/firmware-manager/photon/#getting-started

Firmware mgr link on the guide:
https://binaries.particle.io/updater/particle_firmware_manager-v0.6.0-windows.exe

Should we not use this tool and do the updates only via the CLI?

For some reason unclear to me the Firmware Manager isn’t maintained anymore.
But when you are flashing a Photon OTA with a firmware greater than the system installed on your device the device will undergo an auto-update (multiple restart cycles) to accomodate the new application firmware.
So for Photons there is no need to update the system before being able to upload a more recent target application.

But if you want to explicitly update first, I’d go with CLI particle update (but make sure to have the most recent CLI version installed).

When I go to the CCS811 library and pick to use the test.ino example code and choose 0.6.3 firmware the library will not load and you get the error message as pictured below.

Huh? Have to check again. I’m sure I tested that.


Update: You are correct. For some reason SYSTEM_VERSION does not seem to do what it used to do.
I have to investigate more.


Update: @RWB, now it should work. The version check failed due to a missing #include <Particle.h> in the library header file. Thanks for the test :+1:

1 Like