Skip to content

Instantly share code, notes, and snippets.

@bboyho
Created December 8, 2022 12:26
Show Gist options
  • Save bboyho/b6257ea719f893a8370df066de7bed7a to your computer and use it in GitHub Desktop.
Save bboyho/b6257ea719f893a8370df066de7bed7a to your computer and use it in GitHub Desktop.
/******************************************************************************
WRITTEN BY: Ho Yun "Bobby" Chan
@ SparkFun Electronics
DATE: 12/8/2022
GITHUB REPO: https://github.com/sparkfun/MicroMod_Environmental_Sensor_Function_Board
DEVELOPMENT ENVIRONMENT SPECIFICS:
Firmware developed using Arduino IDE v1.8.19
========== DESCRIPTION==========
This example code combines example codes from the SHTC3, STC31, and SGP40 libraries.
Most of the steps to obtain the measurements are the same as the example code.
Generic object names were renamed (e.g. mySensor => mySGP40 and mySTC3x).
Example 1: Basic Relative Humidity and Temperature Readings w/ SHTC3; Written by Owen Lyke
Example 2: PHT (SHTC3) Compensated CO2 Readings w/ STC31; Written by Paul Clark and based on earlier code by Nathan Seidle
Example 1: Basic VOC Index w/ SGP40; Written by Paul Clark
Open a Serial Monitor at 115200 baud to view the readings!
Note: You may need to wait about ~5 minutes after starting up the code before VOC index
has any values.
========== HARDWARE CONNECTIONS ==========
MicroMod Artemis Processor Board => MicroMod Main Board => MicroMod Environmental Function Board (with SHTC3, STC31, and SGP40)
Feel like supporting open source hardware?
Buy a board from SparkFun!
MicroMod MicroMod Artemis Processor | https://www.sparkfun.com/products/16401
MicroMod Main Board - Single | https://www.sparkfun.com/products/18575
MicroMod Environmental Function Board | https://www.sparkfun.com/products/18632
Qwiic Micro OLED Breakout | https://www.sparkfun.com/products/14532
You can also get the sensors individually.
Qwiic SHTC3 | https://www.sparkfun.com/products/16467
Qwiic STC31 | https://www.sparkfun.com/products/18385
Qwiic SGP40 | https://www.sparkfun.com/products/17729
LICENSE: This code is released under the MIT License (http://opensource.org/licenses/MIT)
******************************************************************************/
#include <Wire.h>
// digital I/O pins
//const byte STAT1 = 19; //defined as a LED_BUILTIN = 19 on Artemis Processor Board, use this if you want to manually define the LED
const byte STAT1 = LED_BUILTIN;
boolean STAT1_blink = HIGH;
#include "SparkFun_SHTC3.h" //Click here to get the library: http://librarymanager/All#SparkFun_SHTC3
SHTC3 mySHTC3; // Create an object of the SHTC3 class
float RH = 0.00; // Variable to keep track of SHTC3 temperature compensation for the STC31
int32_t RHint_1 = 0;
int32_t RHint_2 = 0;
float temperature = 0.00; // Variable to keep track of SHTC3 relative humidity compensation for the STC31float temp_C = 0.00; // Variable to keep track of SHTC3 relative humidity compensation for the STC31
float temp_C = 0.00; // Variable to keep track of SHTC3 relative humidity compensation for the STC31float temp_C = 0.00; // Variable to keep track of SHTC3 relative humidity compensation for the STC31
int32_t temp_Cint_1 = 0;
int32_t temp_Cint_2 = 0;
int32_t temp_Cint_3 = 0;
float temp_F = 0.00; // Variable to keep track of SHTC3 relative humidity compensation for the STC31
int32_t temp_Fint_1 = 0;
int32_t temp_Fint_2 = 0;
int32_t temp_Fint_3 = 0;
#include "SparkFun_STC3x_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_STC3x
STC3x mySTC3x; // Create an object of the mySTC3x class
float CO2_value = 0.00; // Variable to keep track of STC3 CO2
int32_t CO2_value_int_1 = 0;
int32_t CO2_value_int_2 = 0;
int32_t CO2_value_int_3 = 0;
#include "SparkFun_SGP40_Arduino_Library.h" // Click here to get the library: http://librarymanager/All#SparkFun_SGP40
SGP40 mySGP40; //Create an object of the SGP40 class
int32_t VOCindex = 100; // Variable to keep track of SGP40 VOC index
#include <SFE_MicroOLED.h> // Include the SFE_MicroOLED library
#define PIN_RESET 7
#define DC_JUMPER 1
MicroOLED oled(PIN_RESET, DC_JUMPER); // I2C declaration
///////////////////////////////
// Display Mode Page Control //
///////////////////////////////
// This enum defines all of our display modes and their order.
enum t_displayModes {
DISPLAY_RH_TEMP,
DISPLAY_CO2,
DISPLAY_VOC_INDEX,
DISPLAY_CUBE
};
const int NUM_DISPLAY_MODES = 4; // Number of values defined in enum above
volatile int displayMode = NUM_DISPLAY_MODES - 1; // Keeps track of current display page
const unsigned long DISPLAY_UPDATE_RATE = 5000; // Cycle display every 5 seconds
unsigned long lastDisplayUpdate = 0; // Stores time of last display update
unsigned long lastLEDUpdate = 0; // Stores time of last LED update
unsigned long lastSensorUpdate = 0; // Stores time of last sensor update
unsigned long currentMillis = 0; // Stores time of last display update
//boolean activity = true; //used to keep track of movement or button press
//const unsigned long noActivityMillis = NUM_DISPLAY_MODES * (DISPLAY_UPDATE_RATE + 1000); //used to compare amount of time when no activity to turn of screen
//unsigned long lastActivityMillis = 0;
float percentage = 0; //store percent for progress bar
int progressWidth = 0; // Width of progress bar depends on the [% * (64 pixels wide)]
int progressY = 0; //location of the progress bar at the botton of the microOLED
int SCREEN_WIDTH = oled.getLCDWidth();
int SCREEN_HEIGHT = oled.getLCDHeight();
float d = 3;
float px[] = {
-d, d, d, -d, -d, d, d, -d
};
float py[] = {
-d, -d, d, d, -d, -d, d, d
};
float pz[] = {
-d, -d, -d, -d, d, d, d, d
};
float p2x[] = {
0, 0, 0, 0, 0, 0, 0, 0
};
float p2y[] = {
0, 0, 0, 0, 0, 0, 0, 0
};
float r[] = {
0, 0, 0
};
#define SHAPE_SIZE 600
// Define how fast the cube rotates. Smaller numbers are faster.
// This is the number of ms between draws.
//#define ROTATION_SPEED 0
void setup() {
Serial.begin(115200);
//while (!Serial) ; // Wait for Serial Monitor/Plotter to open for Processors with Native USB (i.e. SAMD51)
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
Serial.println(F("Initializing Combined Example w/ SGP40, SHTC3, and STC31."));
Wire.begin();
//mySTC3x.enableDebugging(); // Uncomment this line to get helpful debug messages on Serial
//mySGP40.enableDebugging(); // Uncomment this line to print useful debug messages to Serial
if (mySHTC3.begin() != SHTC3_Status_Nominal)
{
Serial.println(F("SHTC3 not detected. Please check wiring. Freezing..."));
while (1)
; // Do nothing more
}
if (mySTC3x.begin() == false)
{
Serial.println(F("STC3x not detected. Please check wiring. Freezing..."));
while (1)
; // Do nothing more
}
if (mySGP40.begin() == false)
{
Serial.println(F("SGP40 not detected. Check connections. Freezing..."));
while (1)
; // Do nothing more
}
//We need to tell the STC3x what binary gas and full range we are using
//Possible values are:
// STC3X_BINARY_GAS_CO2_N2_100 : Set binary gas to CO2 in N2. Range: 0 to 100 vol%
// STC3X_BINARY_GAS_CO2_AIR_100 : Set binary gas to CO2 in Air. Range: 0 to 100 vol%
// STC3X_BINARY_GAS_CO2_N2_25 : Set binary gas to CO2 in N2. Range: 0 to 25 vol%
// STC3X_BINARY_GAS_CO2_AIR_25 : Set binary gas to CO2 in Air. Range: 0 to 25 vol%
if (mySTC3x.setBinaryGas(STC3X_BINARY_GAS_CO2_AIR_25) == false)
{
Serial.println(F("Could not set the binary gas! Freezing..."));
while (1)
; // Do nothing more
}
//We can compensate for temperature and relative humidity using the readings from the SHTC3
if (mySHTC3.update() != SHTC3_Status_Nominal) // Request a measurement
{
Serial.println(F("Could not read the RH and T from the SHTC3! Freezing..."));
while (1)
; // Do nothing more
}
//In case the ‘Set temperature command’ has been used prior to the measurement command,
//the temperature value given out by the STC31 will be that one of the ‘Set temperature command’.
//When the ‘Set temperature command’ has not been used, the internal temperature value can be read out.
temperature = mySHTC3.toDegC(); // "toDegC" returns the temperature as a floating point number in deg C
Serial.print(F("Setting STC3x temperature to "));
Serial.print(temperature, 2);
Serial.print(F("C was "));
if (mySTC3x.setTemperature(temperature) == false)
Serial.print(F("not "));
Serial.println(F("successful"));
RH = mySHTC3.toPercent(); // "toPercent" returns the percent humidity as a floating point number
Serial.print(F("Setting STC3x RH to "));
Serial.print(RH, 2);
Serial.print(F("% was "));
if (mySTC3x.setRelativeHumidity(RH) == false)
Serial.print(F("not "));
Serial.println(F("successful"));
//If we have a pressure sensor available, we can compensate for ambient pressure too.
//As an example, let's set the pressure to 840 mbar (== SF Headquarters)
uint16_t pressure = 840;
Serial.print(F("Setting STC3x pressure to "));
Serial.print(pressure);
Serial.print(F("mbar was "));
if (mySTC3x.setPressure(pressure) == false)
Serial.print(F("not "));
Serial.println(F("successful"));
Serial.println(F("Note: Relative humidity and temperature compensation for the STC31 will be updated frequently in the main loop() function."));
Serial.println(F("Note: The SGP40 takes a few seconds to warm up. Try waiting about 10 seconds before reading the measurements. "));
Serial.println("");
oled.begin(); // Initialize the OLED
oled.clear(ALL); // Clear the display's internal memory
oled.display(); // Display what's in the buffer (splashscreen)
delay(1000); // Delay 1000 ms
oled.clear(PAGE); // Clear the buffer.
//Quick test to check how many characters can go across screen
//oled.clear(ALL); // Clear the display's internal memory
//oled.setCursor(0, 0); // Set cursor to top-left
//oled.setFontType(0); // Smallest font
//oled.print(F("123456789;")); // Print, max is 10 characters across, 5x7-pixel characters
//oled.display(); // Display what's in the buffer (splashscreen)
//delay(1000); // Delay 1000 ms
} //end setup()
void loop() {
//get time based on how long the Arduino has been running
currentMillis = millis();
if (currentMillis >= lastSensorUpdate + 1100) //read sensors after about every 1100 milliseconds
{
Serial.print("Time:");
Serial.println(currentMillis);
//==============================
//==========READ SHTC3==========
//==============================
//minimum update rate = ~100Hz
SHTC3_Status_TypeDef result = mySHTC3.update(); // Call "update()" to command a measurement, wait for measurement to complete, and update the RH and T members of the object
RH = mySHTC3.toPercent(); // "toPercent" returns the percent humidity as a floating point number
Serial.print(F("% RH = "));
Serial.print(RH);
Serial.println(F("%"));
Serial.print(F("Temperature = "));
temp_F = mySHTC3.toDegF();
Serial.print(temp_F); // "toDegF" return the temperature as a floating point number in deg F
Serial.print(F("°F, "));
temperature = mySHTC3.toDegC(); // "toDegC" returns the temperature as a floating point number in deg C
temp_C = temperature;
Serial.print(temp_C);
Serial.print(F("°C"));
if (mySHTC3.lastStatus == SHTC3_Status_Nominal) // You can also assess the status of the last command by checking the ".lastStatus" member of the object
{
Serial.println(""); //Sample data good, no need to output a message
}
else {
Serial.print(F(", Update failed, error: ")); //notify user if there is an error
errorDecoder(mySHTC3.lastStatus);
Serial.println("");
}
//==============================
//==========READ STC31==========
//==============================
//minimum update rate = 1Hz
if (mySTC3x.setRelativeHumidity(RH) == false)
Serial.print(F("Unable to set STC31 Relative Humidity with SHTC3."));
if (mySTC3x.setTemperature(temperature) == false)
Serial.println(F("Unable to set STC31 Temperature with SHTC3."));
Serial.print(F("CO2(%): "));
if (mySTC3x.measureGasConcentration()) // measureGasConcentration will return true when fresh data is available
{
CO2_value = mySTC3x.getCO2();
Serial.println(CO2_value, 2);
}
else
{
Serial.print(CO2_value, 2);
Serial.println(", (old STC3 sample reading, STC31 was not able to get fresh data yet)"); //output this note to indicate when we are not able to obtain a new measurement
}
//==============================
//==========READ SGP40==========
//==============================
//minimum update rate = 1Hz
//VOCindex = mySGP40.getVOCindex(); //Get the VOC Index using the default RH (50%) and T (25C)
VOCindex = mySGP40.getVOCindex(RH, temperature); //Get the VOC Index using the RH and Temperature
Serial.print(F("VOC Index is: "));
Serial.println(VOCindex);
Serial.println("");
lastSensorUpdate = currentMillis;
}
// Another method of updating display:
// The display will cycle itself every DISPLAY_UPDATE_RATE seconds
if ( (currentMillis >= (lastDisplayUpdate + DISPLAY_UPDATE_RATE + 1000)) )
{
// Increment displayMode, next time through a new page will be displayed:
displayMode = (displayMode + 1) % NUM_DISPLAY_MODES;
// Update lastDisplayTime, so we don't come back for DISPLAY_UPDATE_RATE seconds
lastDisplayUpdate = currentMillis;
}
// display
oled.clear(PAGE); // Clear the display
updateDisplay();
displayProgressBar(); // Draws a progress bar at the bottom of the screen
oled.display();
if (currentMillis >= lastLEDUpdate + 1000) {
STAT1_blink = !STAT1_blink;
digitalWrite(STAT1, STAT1_blink); //Blink stat LED
lastLEDUpdate = currentMillis;
}
//================================
//=========SPACE & DELAY==========
//================================
//delay is calculated using millis instead of using a delay() function
//Serial.println("");// Uncomment this line to add some space between readings for the Serial Monitor
//delay(1000); //Wait 1 second - the Sensirion VOC and CO2 algorithms expects a sample rate of 1Hz
}//end loop()
void errorDecoder(SHTC3_Status_TypeDef message) // The errorDecoder function prints "SHTC3_Status_TypeDef" results in a human-friendly way
{
switch (message)
{
case SHTC3_Status_Nominal : Serial.print("Nominal"); break;
case SHTC3_Status_Error : Serial.print("Error"); break;
case SHTC3_Status_CRC_Fail : Serial.print("CRC Fail"); break;
default : Serial.print("Unknown return code"); break;
}
}
void updateDisplay() {
switch (displayMode)
{
case DISPLAY_RH_TEMP:
displayRH_TEMP();
break;
case DISPLAY_CO2:
displayCO2();
break;
case DISPLAY_VOC_INDEX:
displayVOC_INDEX();
break;
case DISPLAY_CUBE:
drawCube();
break;
}
}
void displayRH_TEMP() {
oled.setFontType(0); // Smallest font
oled.setCursor(18, 0); // Set cursor to top-middle
oled.print(F("STC31")); // Print
oled.setFontType(1); // medium font
oled.setCursor(0, 8); // Set cursor to top-ish
oled.print(F(" C"));
oled.setCursor(0, 8); // Set cursor to top-ish
//oled.print(String(temp_C, 2)); // Print temp, assuming that it is within room temp in tens <= floats doesn't convert too well with the Artemis Processor Board
temp_Cint_1 = temp_C * 100;
temp_Cint_1 = temp_Cint_1 / 100;
oled.print(String(temp_Cint_1)); // Print temp, assuming that it is within room temp in tens
oled.print(".");
temp_Cint_2 = temp_C * 10;
temp_Cint_2 = temp_Cint_2 % 10;
oled.print(String(temp_Cint_2)); // Print temp, assuming that it is within room temp in tens
temp_Cint_3 = temp_C * 100;
temp_Cint_3 = temp_Cint_3 % 10;
oled.print(String(temp_Cint_3)); // Print temp, assuming that it is within room temp in tens
oled.setCursor(0, 21); // Set cursor to middle-ish
oled.print(F(" F"));
oled.setCursor(0, 21); // Set cursor to middle-ish
//oled.print(String(temp_F, 2));// Print temp, assuming that it is within room temp in tens <= floats doesn't convert too well with the Artemis Processor Board
temp_Fint_1 = temp_F * 100;
temp_Fint_1 = temp_Fint_1 / 100;
oled.print(String(temp_Fint_1)); // Print temp, assuming that it is within room temp in tens
oled.print(".");
temp_Fint_2 = temp_F * 10;
temp_Fint_2 = temp_Fint_2 % 10;
oled.print(String(temp_Fint_2)); // Print temp, assuming that it is within room temp in tens
temp_Fint_3 = temp_F * 100;
temp_Fint_3 = temp_Fint_3 % 10;
oled.print(String(temp_Fint_3)); // Print temp, assuming that it is within room temp in tens
oled.circle(50, 9, 1); //"degree" sign after output values
oled.circle(50, 22, 1); //"degree" sign after output values
oled.setCursor(0, 34); // Set cursor to bottom-ish
oled.print(F(" %RH"));
oled.setCursor(0, 34); // Set cursor to bottom-ish
//oled.print(String(RH, 1)); // Print humid, assuming that it is within room temp in tens <= floats doesn't convert too well with the Artemis Processor Board
RHint_1 = RH * 100;
RHint_1 = RHint_1 / 100;
oled.print(String(RHint_1)); // Print humid, assuming that it is within room temp in tens
oled.print(".");
RHint_2 = RH * 10;
RHint_2 = RHint_2 % 10;
oled.print(String(RHint_2)); // Print temp, assuming that it is within room temp in tens
}
void displayCO2() {
oled.setFontType(0); // Smallest font
oled.setCursor(18, 0); // Set cursor to middle-ish
oled.print(F("SHTC3")); // Print
oled.setFontType(1); // medium font
oled.setCursor(22, 8); // Set cursor to middle-ish
oled.print(F("CO"));
oled.setFontType(0); // small font
oled.setCursor(38, 12); // Set cursor to middle-ish
oled.print(F("2"));
oled.setFontType(2); // medium font
oled.setCursor(11, 21); // Set cursor to middle-ish
//oled.print(String(CO2_value, 2)); // Print CO2, assuming that it is within room temp in thousandths <= floats doesn't convert too well with the Artemis Processor Board
CO2_value_int_1 = CO2_value * 100;
CO2_value_int_1 = CO2_value_int_1 / 100;
oled.print(String(CO2_value_int_1)); // Print CO2, assuming that it is within room temp in thousandths
oled.print(".");
CO2_value_int_2 = CO2_value * 10;
CO2_value_int_2 = CO2_value_int_2 % 10;
oled.print(String(CO2_value_int_2)); // Print temp, assuming that it is within room temp in tens
CO2_value_int_3 = CO2_value * 100;
CO2_value_int_3 = CO2_value_int_3 % 10;
oled.print(String(CO2_value_int_3)); // Print temp, assuming that it is within room temp in tens
}
void displayVOC_INDEX() {
oled.setFontType(0); // Smallest font
oled.setCursor(18, 0); // Set cursor to top-left
oled.print(F("SGP40")); // Print
oled.setFontType(0); // Smallest font
oled.setCursor(6, 8); // Set cursor to middle-ish
oled.print(F("VOC Index"));
oled.setFontType(2); // medium font
if (VOCindex < 10) {
oled.setCursor(26, 21); // Set cursor to middle-ish
}
else if (VOCindex >= 10 && VOCindex < 100) {
oled.setCursor(21, 21); // Set cursor to middle-ish
}
else if (VOCindex >= 100 && VOCindex < 1000) {
oled.setCursor(16, 21); // Set cursor to middle-ish
}
else if (VOCindex >= 1000) {
oled.setCursor(11, 21); // Set cursor to middle-ish
}
oled.print(String(VOCindex));// Print tVOC, assuming that it is within room temp in tens
}
void drawCube() {
r[0] = r[0] + 10 * PI / 180.0; // Add a degree
r[1] = r[1] + 10 * PI / 180.0; // Add a degree
r[2] = r[2] + 10 * PI / 180.0; // Add a degree
if (r[0] >= 360.0 * PI / 180.0) r[0] = 0;
if (r[1] >= 360.0 * PI / 180.0) r[1] = 0;
if (r[2] >= 360.0 * PI / 180.0) r[2] = 0;
for (int i = 0; i < 8; i++)
{
float px2 = px[i];
float py2 = cos(r[0]) * py[i] - sin(r[0]) * pz[i];
float pz2 = sin(r[0]) * py[i] + cos(r[0]) * pz[i];
float px3 = cos(r[1]) * px2 + sin(r[1]) * pz2;
float py3 = py2;
float pz3 = -sin(r[1]) * px2 + cos(r[1]) * pz2;
float ax = cos(r[2]) * px3 - sin(r[2]) * py3;
float ay = sin(r[2]) * px3 + cos(r[2]) * py3;
float az = pz3 - 150;
p2x[i] = SCREEN_WIDTH / 2 + ax * SHAPE_SIZE / az;
p2y[i] = SCREEN_HEIGHT / 2 + ay * SHAPE_SIZE / az;
}
for (int i = 0; i < 3; i++)
{
oled.line(p2x[i], p2y[i], p2x[i + 1], p2y[i + 1]);
oled.line(p2x[i + 4], p2y[i + 4], p2x[i + 5], p2y[i + 5]);
oled.line(p2x[i], p2y[i], p2x[i + 4], p2y[i + 4]);
}
oled.line(p2x[3], p2y[3], p2x[0], p2y[0]);
oled.line(p2x[7], p2y[7], p2x[4], p2y[4]);
oled.line(p2x[3], p2y[3], p2x[7], p2y[7]);
}
// This function draws a line at the very bottom of the screen showing how long
// it'll be before the screen updates.
// Based on Jim's micro OLED code used in the Photon SIK KIT => [ https://github.com/sparkfun/Inventors_Kit_For_Photon_Experiments/blob/master/11-OLEDApps/Code/02-WeatherForecast/WeatherApp.ino ]
void displayProgressBar() {
// Use lastDisplayUpdate's time, the time Arduino has been running
// (since we do not have an RTC, Internet, or GPS), and the total time
// per page (DISPLAY_UPDATE_RATE) to calculate what portion
// of the display bar needs to be drawn.
percentage = (float)(currentMillis - lastDisplayUpdate) / (float)DISPLAY_UPDATE_RATE;
//for debugging progress bar
//Serial.println(currentMillis);
//Serial.println(lastDisplayUpdate);
//Serial.println(DISPLAY_UPDATE_RATE);
//Serial.println(percentage);
//Serial.println(oled.getLCDWidth());
// Mutliply that value by the total lcd width to get the pixel length of our line
progressWidth = percentage * oled.getLCDWidth();
// the y-position of our line should be at the very bottom of the screen:
progressY = oled.getLCDHeight() - 1;
// First, draw a blank line to clear any old lines out:
oled.line(0, progressY, oled.getLCDWidth(), progressY, BLACK, NORM);
// Next, draw our line:
oled.line(0, progressY, progressWidth, progressY);
//oled.display(); // Update the display, this is already called after we exit this function
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment