| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361 |
- /*
- AirQuality.ino
-
- ATMEGA328P (Arduino UNO/Pro Trinket) only
- Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
- Copyright (c) 2018, olikraus@gmail.com
- All rights reserved.
- Redistribution and use in source and binary forms, with or without modification,
- are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright notice, this list
- of conditions and the following disclaimer.
-
- * Redistributions in binary form must reproduce the above copyright notice, this
- list of conditions and the following disclaimer in the documentation and/or other
- materials provided with the distribution.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
- http://shelvin.de/arduino-in-den-sleep_mode_pwr_down-schlaf-modus-setzen/
-
-
- battery symbol
- trinket 3.3V requires at least 3.5V (https://learn.adafruit.com/introducing-pro-trinket/pinouts)
- Assuming 3x AAA and a fresh cell with 1.5V: --> 4.5V
- There are 6 battery symbols
- symbol 0: < 3.5V
- symbol 1: 3.5 - 3.7
- symbol 2: 3.7 - 3.9
- symbol 3: 3.9 - 4.1
- symbol 4: 4.1 - 4-5
- symbol 5: >= 4.5V
- 1V difference --> 5 steps --> 1V / 5 --> 0.2V step
- R1 = 470K
- R2 = 100K
- Rsum = 570K
-
- analogRead will use the internal 1.1V reference signal
-
- Um = 100K * 4.5V / 570K = 0.7894 V --> *1024/1.1 --> 735 (analogRead)
- Um = 100K * 3.5V / 570K = 0.6140 V --> 571 (analogRead)
-
- 735 - 571 = 164 --> 33
-
- analogRead*1.1/1024 = Um, Um = 100 * Ubat/570
- analogRead*1.1/1024 = 10 * Ubat/57
- analogRead*1.1*57/10240 = Ubat
- Ubat = analogRead*1.1*57/10240 = analogRead*0.006123 = analogRead/163
-
-
- */
- #include <Arduino.h>
- #include <Wire.h>
- #include <U8g2lib.h>
- #include <Adafruit_SGP30.h>
- #include <SHTSensor.h> // adafruit sht31 lib v1.0.0 has a wrong 500 milliseconds delay, so use the SHTSensor.h lib
- #include <avr/sleep.h>
- //===================================================
- // Constants: Timing values for the sensor & display state machine
- // all values are "seconds"
- // define startup calibration time: 2h
- //#define STARTUP_TIME (60*60*2)
- // five minutes
- #define STARTUP_TIME (60*5)
- // define duration after which the display is disabled: 30 seconds
- #define DISPLAY_TIME ((30))
- // Datasheet, page 8:
- // For the first 15s after the “Init_air_quality” command the sensor
- // is in an initialization phase during which a “Measure_air_quality”
- // command returns fixed values
- // --> sensor warmup time
- #define SENSOR_WARMUP_TIME ((16))
- // SENSOR_MEASURE_TIME defines the duration how long the measurement
- // shell be aktive (after SENSOR_WARMUP_TIME)
- #define SENSOR_MEASURE_TIME (14)
- // This is the time, after which the gas sensor should do another measurement.
- // The sum of SENSOR_WARMUP_TIME and SENSOR_MEASURE_TIME must
- // be lesser than SENSOR_SAMPLE_TIME
- // every 7 minutes, so there are at least two measures per history entry
- #define SENSOR_SAMPLE_TIME (7*60)
- // This is the time after which a new history entry is generated
- // There are 96 entries in the history table (HIST_CNT)
- // 15*60 = 15 minutes --> 96 * 15 minutes --> 24h
- #define NEW_HISTORY_DELAY (15*60)
- //#define NEW_HISTORY_DELAY 2
- // changing the content of the display is done via "shakes"
- // shakes are detected by a tilt switsch.
- // This value defines the number of shake events to change the display page.
- #define NEW_DISPLAY_SHAKE_THRESHOLD 7
- // number of seconds, for which a new display page is fixed
- // this means, for this duration, the user can not change the display page
- #define NEW_DISPLAY_COOL_DOWN 3
- // Battery empty value
- // U = 100K * 3.5V / 570K = 0.6140 V --> 571 (analogRead)
- #define BATTERY_LOW 571
- // step width for each battery symbol
- #define BATTERY_STEP 33
- U8G2_SSD1306_128X64_NONAME_2_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
- SHTSensor sht; // temperature & humidity sensor
- Adafruit_SGP30 sgp; // air quality sensor
- U8G2LOG u8g2log;
- // setup the terminal (U8G2LOG) and connect to u8g2 for automatic refresh of the display
- // The size (width * height) depends on the selected font and the display
- #define U8LOG_WIDTH 24
- #define U8LOG_HEIGHT 5
- uint8_t u8log_buffer[U8LOG_WIDTH*U8LOG_HEIGHT];
- //===================================================
- // Constants: Font definitions for U8g2
- #define FONT_NARROW u8g2_font_mercutio_basic_nbp_tr
- #define FONT_SMALL u8g2_font_helvB08_tf
- #define FONT_MED_NUM u8g2_font_helvB14_tn
- //#define FONT_BIG u8g2_font_inr49_mn
- #define FONT_BIG u8g2_font_logisoso38_tn
- //===================================================
- // Constants: State values for the sensor & display coordination
- #define STATE_RESET 0
- #define STATE_STARTUP_DISP_ON 1
- #define STATE_STARTUP_DISP_OFF 2
- #define STATE_WARMUP_DISP_ON 11
- #define STATE_WARMUP_DISP_OFF 12
- #define STATE_MEASURE_DISP_ON 21
- #define STATE_MEASURE_DISP_OFF 22
- #define STATE_SENSOR_SLEEP_DISP_OFF 32
- //===================================================
- // Constants: Temperature boundaries
- #define TEMP_LOW -20
- #define TEMP_HIGH (TEMP_LOW+120)
- //===================================================
- // Constants: Number of different display pages
- #define DISPLAY_PAGE_CNT 3
- //===================================================
- // Constants: History
- #define HIST_CNT 96
- // history sample time: number of seconds between each history entry
- // --> see NEW_HISTORY_DELAY
- // 15 min = 15*60 seconds: 96 entries for 24h
- //===================================================
- // State variable for the sensor & display coordination
- uint8_t state = STATE_RESET; // assign STATE_xxx constants
- uint8_t is_display_enabled = 0; // modified by enable_display() and disable_display()
- //===================================================
- // Variables: Air quality sensor related varables
- uint8_t is_ethanol_read = 0;
- float temperature_raw;
- float humidity_raw;
- uint8_t temperature; /* with offset and multiplied by 2 */
- uint8_t humidity; /* *2 */
- uint16_t tvoc_raw;
- uint16_t tvoc_lp;
- uint16_t eco2_raw;
- uint16_t eco2_lp;
- uint16_t ethanol_raw = 0;
- int is_air_quality_available = 0;
- // Calibration values, values seem to be 0x8a27 and 0x08a98 for my sensor, so 0 will be used as not set
- uint16_t eco2_base = 0; // calibration value eCO2
- uint16_t tvoc_base = 0; // calibration value TVOC
- //===================================================
- uint16_t battery_raw;
- uint16_t battery_glyph;
- //===================================================
- // Variables: Timer for the state machine
- volatile uint32_t wdt_count = 0;
- volatile uint8_t wdt_sec = 0;
- volatile uint8_t wdt_min = 0;
- volatile uint8_t wdt_hour = 0;
- volatile uint16_t wdt_day = 0;
- volatile uint16_t startup_timer = 0;
- volatile uint16_t sensor_sample_timer = SENSOR_SAMPLE_TIME;
- volatile uint8_t is_sensor_sample_timer_alarm = 0; // if zero: wake up gas sensor
- volatile uint8_t display_timer = 0;
- volatile uint8_t sensor_warmup_timer = 0;
- volatile uint8_t sensor_measure_timer = 0;
- volatile uint8_t new_display_cool_down_timer = 0;
- volatile uint8_t is_wdt_irq = 0;
- volatile uint8_t is_new_history_entry = 0;
- volatile uint16_t new_history_timer = NEW_HISTORY_DELAY;
- //volatile uint16_t battery_level = 0; // in millivolt
- uint32_t millis_sensor; // Debugging: Duration of the sensour measurement
- uint32_t millis_display; // Debugging: Duration of the display refresh
- //===================================================
- // Variables: Shake detection
- volatile uint8_t is_shake = 0;
- volatile uint8_t shake_cnt = 0;
- volatile uint8_t shake_last_cnt = 0;
- //===================================================
- // Variables: Current visible display page
- uint8_t current_display_page = 0; // 0 .. DISPLAY_PAGE_CNT - 1
- //===================================================
- // Variables: History management
- uint8_t hist_start = 0;
- uint8_t hist_end = 1;
- uint8_t hist_last = 0;
- uint16_t hist_eco2_max[HIST_CNT];
- uint16_t hist_eco2_min[HIST_CNT];
- //uint8_t hist_temp_max[HIST_CNT];
- //uint8_t hist_temp_min[HIST_CNT];
- //uint8_t hist_rh_max[HIST_CNT];
- //uint8_t hist_rh_min[HIST_CNT];
- //===================================================
- void hist_append(void)
- {
- hist_last = hist_end;
-
- if ( hist_end == hist_start )
- { // history is full
- hist_start++;
- if ( hist_start >= HIST_CNT )
- hist_start = 0;
- }
-
-
- hist_end++;
- if ( hist_end >= HIST_CNT )
- {
- hist_end = 0;
- }
- }
- uint16_t maximum(uint16_t a, uint16_t b)
- {
- if ( a < b )
- return b;
- return a;
- }
- uint16_t minimum(uint16_t a, uint16_t b)
- {
- if ( a > b )
- return b;
- return a;
- }
- /* append current values to last history entry */
- void add_hist_minmax(void)
- {
-
- hist_eco2_max[hist_last] = maximum(hist_eco2_max[hist_last], eco2_raw);
- hist_eco2_min[hist_last] = minimum(hist_eco2_min[hist_last], eco2_raw);
- //hist_temp_max[hist_last] = maximum(hist_temp_max[hist_last], temperature);
- //hist_temp_min[hist_last] = minimum(hist_temp_min[hist_last], temperature);
- //hist_rh_max[hist_last] = maximum(hist_rh_max[hist_last], humidity);
- //hist_rh_min[hist_last] = minimum(hist_rh_min[hist_last], humidity);
- }
- void add_hist_new(void)
- {
- hist_append();
- hist_eco2_max[hist_last] = eco2_raw;
- hist_eco2_min[hist_last] = eco2_raw;
- //hist_temp_max[hist_last] = temperature;
- //hist_temp_min[hist_last] = temperature;
- //hist_rh_max[hist_last] = humidity;
- //hist_rh_min[hist_last] = humidity;
- }
- //===================================================
- ISR(WDT_vect)
- {
- is_wdt_irq = 1;
- wdt_count++;
-
-
- wdt_sec++;
- if ( wdt_sec >= 60 )
- {
- wdt_sec = 0;
- wdt_min++;
- if ( wdt_min >= 60 )
- {
- wdt_min = 0;
- wdt_hour++;
- if ( wdt_hour >= 24 )
- {
- wdt_hour = 0;
- wdt_day++;
- }
- }
- }
-
- if ( startup_timer > 0 )
- startup_timer--;
- if ( display_timer > 0 )
- display_timer--;
- if ( sensor_warmup_timer > 0 )
- sensor_warmup_timer--;
- if ( sensor_measure_timer > 0 )
- sensor_measure_timer--;
- if ( sensor_sample_timer > 0 )
- {
- sensor_sample_timer--;
- }
- else
- {
- sensor_sample_timer = SENSOR_SAMPLE_TIME;
- if ( is_sensor_sample_timer_alarm == 0 )
- is_sensor_sample_timer_alarm = 1;
- }
- if ( new_history_timer > 0 )
- {
- new_history_timer--;
- }
- else
- {
- new_history_timer = NEW_HISTORY_DELAY;
- if ( is_new_history_entry == 0 )
- is_new_history_entry = 1;
- }
- shake_last_cnt = shake_cnt;
- shake_cnt = 0;
- }
- void enableWDT()
- {
- MCUSR = 0; // clear all reset flags including the WDT flag
- WDTCSR = B00011000; // enable bit 4 (WDCE) and bit 3 (WDE) to change the prescalar
- WDTCSR = B01000110; // Enable watchdog IRQ and set prescaler to 128k --> 1 sec
- }
- void reducePower(void)
- {
- //ADCSRA = ADCSRA & B01111111; // ADC abschalten, ADEN bit7 zu 0
- ACSR = B10000000; // Analogen Comparator abschalten, ACD bit7 zu 1
- DIDR0 = DIDR0 | B00111111; // Digitale Eingangspuffer ausschalten, analoge Eingangs Pins 0-5 auf 1
- }
- // argument for attachInterrupt
- // This is called if something happens on the tilt switch
- void detectShake(void)
- {
- is_shake = 1; // used and cleared in is_display_on_event()
- if ( shake_cnt < 255 )
- shake_cnt++; // used and cleared in ISR(WDT_vect)
- }
- //===================================================
- void setup(void) {
- uint8_t i;
- reducePower();
- analogReference(INTERNAL); // use the internal 1.1V reference of the ATmega
-
- Wire.begin();
- u8g2.begin();
- u8g2.enableUTF8Print();
-
- u8g2.setFont(FONT_NARROW); // set the font for the terminal window
- u8g2log.begin(u8g2, U8LOG_WIDTH, U8LOG_HEIGHT, u8log_buffer);
- u8g2log.setLineHeightOffset(0); // set extra space between lines in pixel, this can be negative
- u8g2log.setRedrawMode(0); // 0: Update screen with newline, 1: Update screen for every char
-
- u8g2log.print(F("Air Quality\n"));
-
- for( i = 0; i < HIST_CNT; i++ )
- {
- hist_eco2_max[i] = 0;
- hist_eco2_min[i] = 0;
- }
-
-
- if (sht.init()) {
- u8g2log.print(F("SHT31 found at 0x44\n"));
- } else {
- u8g2log.print(F("SHT31 not found\n"));
- while (1);
- }
- sht.setAccuracy(SHTSensor::SHT_ACCURACY_HIGH);
- if (sgp.begin()) {
- u8g2log.print(F("SGP30 found\n"));
- } else {
- u8g2log.print(F("SGP30 not found\n"));
- while (1);
- }
-
- u8g2log.print(F("SGP #"));
- u8g2log.print(sgp.serialnumber[0], HEX);
- u8g2log.print(sgp.serialnumber[1], HEX);
- u8g2log.println(sgp.serialnumber[2], HEX);
-
- delay(1000);
-
- // tilt detection at pin 2
- //pinMode(2, INPUT_PULLUP);
- //attachInterrupt(digitalPinToInterrupt(2), detectShake, CHANGE);
-
- // tilt detection at pin 3
- pinMode(3, INPUT_PULLUP);
- attachInterrupt(digitalPinToInterrupt(3), detectShake, CHANGE);
- set_sleep_mode(SLEEP_MODE_PWR_DOWN);
- enableWDT();
- }
- //===================================================
- // read ethanol value from SGP30
- // bypass Adafruit lib, because this command is not included
- // not used at the moment
- uint16_t readEthanol(void)
- {
- uint8_t buf[6];
- uint8_t i;
- Wire.beginTransmission(0x58);
- Wire.write(0x20); // 0x2050: measure signals command
- Wire.write(0x50);
- Wire.endTransmission();
-
- delay(200); // max 200ms according to the datasheet
-
- if (Wire.requestFrom(0x58, 6) != 6)
- return 0;
- for (i=0; i<6; i++)
- buf[i] = Wire.read();
- // each data value has: msb, lsb and crc
- // ethanol value is the second data returned
- // crc is ignored
- return (((uint16_t)buf[3])<<8) + (uint16_t)buf[4];
- }
- //===================================================
- void readBatteryVoltageLevel(void)
- {
- battery_raw = analogRead(0);
- battery_glyph = 0;
- if ( battery_raw >= BATTERY_LOW )
- {
- battery_glyph = (battery_raw - BATTERY_LOW)/BATTERY_STEP;
- if ( battery_glyph > 5 )
- battery_glyph = 5;
- }
- battery_glyph += 48;
- }
- //===================================================
- /*
- Calculate absolute humidity [mg/m^3]
- Args: Temperature [°C], humidity [%RH]
- */
- uint32_t getAbsoluteHumidity(float temperature, float humidity)
- {
- // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15
- const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
- const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
- return absoluteHumidityScaled;
- }
- void startAirQuality(void)
- {
- }
- void readAirQuality(void)
- {
-
- if ( is_ethanol_read == 0 )
- {
- // normal air quality read
- sht.readSample();
- temperature_raw = sht.getTemperature();
- humidity_raw = sht.getHumidity();
-
- if ( temperature_raw <= (float)TEMP_LOW )
- {
- temperature = 0;
- }
- else if ( temperature_raw >= (float)TEMP_HIGH )
- {
- temperature = TEMP_HIGH - TEMP_LOW;
- temperature *= 2;
- }
- else
- {
- temperature = (uint8_t)((temperature_raw-(float)TEMP_LOW)*2.0);
- }
-
- humidity = (uint8_t)((humidity_raw)*2.0);
-
- sgp.setHumidity(getAbsoluteHumidity(temperature_raw, humidity_raw));
-
- is_air_quality_available = sgp.IAQmeasure();
-
- if ( is_air_quality_available )
- {
- tvoc_raw = sgp.TVOC;
- eco2_raw = sgp.eCO2;
- }
- else
- {
- tvoc_raw = 0;
- eco2_raw = 400;
- }
-
- if ( is_new_history_entry != 0 )
- {
- add_hist_new();
- is_new_history_entry = 0;
- }
- else
- {
- add_hist_minmax();
- }
- // ethanol should be read only, if the display is active:
- /* not used
- if ( is_display_enabled )
- {
- is_ethanol_read = 1;
- }
- */
-
- }
- else
- {
-
- // special ethanol read
- //ethanol_raw = readEthanol();
- // according to the datasheet, the baseline values are corrupted... so restore them if available
- //if ( eco2_base != 0 )
- // sgp.setIAQBaseline(eco2_base, tvoc_base); // Restore the baseline values
-
- is_ethanol_read = 0; // next: read normal air quality values
- }
- }
- uint16_t get_uint8(void *ptr, uint8_t pos)
- {
- return ((uint8_t *)ptr)[pos];
- }
- uint16_t get_uint16(void *ptr, uint8_t pos)
- {
- return ((uint16_t *)ptr)[pos];
- }
- void draw_temperature(uint16_t x)
- {
- uint8_t frac;
- frac = x & 1;
- x >>= 1;
- x += TEMP_LOW;
- x = x & 0x0ff;
- u8g2.print(x);
- u8g2.print('.');
- if ( frac )
- u8g2.print('5');
- else
- u8g2.print('0');
- }
- void draw_humidity(uint16_t x)
- {
- uint8_t frac;
- frac = x & 1;
- x >>= 1;
- u8g2.print(x);
- u8g2.print('.');
- if ( frac )
- u8g2.print('5');
- else
- u8g2.print('0');
- }
- void draw_16bit(uint16_t x)
- {
- u8g2.print(x);
- }
- void draw_graph( uint16_t (*get_val)(void *ptr, uint8_t pos), void *min_array, void *max_array, void (*draw_value)(uint16_t x))
- {
- uint8_t i, ii, x;
- uint16_t max = 0;
- uint16_t min = 0x0ffff;
- uint16_t delta;
- uint8_t ymin, ymax;
- i = hist_start;
- for(;;)
- {
- ii = i;
- i++;
- if ( i >= HIST_CNT )
- i = 0;
- if ( i == hist_end )
- break;
- if ( min > get_val(min_array, ii) )
- min = get_val(min_array, ii);
- if ( max < get_val(max_array, ii) )
- max = get_val(max_array, ii);
- }
- if ( min > max )
- {
- min = 0;
- max = 600;
- return;
- }
- if ( min + 30 >= max )
- max = min + 30;
-
- if ( max - min < 100 )
- {
- max += 19;
- max /= 20;
- max *= 20;
-
- min /= 20;
- min *= 20;
- }
- else
- {
- max += 199;
- max /= 200;
- max *= 200;
-
- min /= 200;
- min *= 200;
- }
-
- delta = max-min;
- i = hist_start;
- x = 127-HIST_CNT;
- u8g2.setFont(FONT_NARROW);
- u8g2.setDrawColor(1);
- for(;;)
- {
- ii = i;
- i++;
- if ( i >= HIST_CNT )
- i = 0;
- if ( i == hist_end )
- break;
- ymin = ((unsigned long)(get_val(min_array, ii) - min)*30UL)/delta;
- ymax = ((unsigned long)(get_val(max_array, ii) - min)*30UL)/delta;
- u8g2.drawVLine(x, 63-ymax, ymax-ymin+1);
- x++;
- }
- u8g2.setCursor(0, 46);
- draw_value(max);
- u8g2.setCursor(0, 63);
- draw_value(min);
- }
- //===================================================
- void draw_1_2_eco2_history(u8g2_uint_t x, u8g2_uint_t y)
- {
-
- draw_graph( get_uint16, hist_eco2_min, hist_eco2_max, draw_16bit);
- }
- void draw_1_4_temperature(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_MED_NUM);
-
- u8g2.setCursor(x, 28+y);
- draw_temperature(temperature);
- u8g2.setFont(FONT_SMALL);
-
- u8g2.setCursor(x, 10+y);
- u8g2.print(F("°C"));
- }
- void draw_1_4_humidity(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_MED_NUM);
- u8g2.setCursor(x, 28+y);
- draw_humidity(humidity);
-
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, 10+y);
- u8g2.print(F("%RH"));
- }
- void draw_gray_out(u8g2_uint_t x, u8g2_uint_t y)
- {
- if ( state == STATE_WARMUP_DISP_ON )
- {
- uint8_t xx, yy;
- u8g2.setDrawColor(0);
- for( yy = y+14; yy < y+28; yy+=1 )
- {
- for( xx = 0; xx < 52; xx += 2 )
- {
- u8g2.drawPixel(xx+x+(yy&1), yy);
- }
- }
- u8g2.setDrawColor(1);
- }
- }
- void draw_1_4_eco2(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+10);
- u8g2.print(F("ppm CO"));
- u8g2.setCursor(x+40, y+15);
- u8g2.print(F("²"));
- u8g2.setFont(FONT_MED_NUM);
- u8g2.setCursor(x, y+28);
- u8g2.print(eco2_raw);
-
- draw_gray_out(x, y);
- }
- void draw_1_4_tvoc(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+10);
- u8g2.print(F("ppb TVOC"));
- u8g2.setFont(FONT_MED_NUM);
- u8g2.setCursor(x, y+28);
- u8g2.print(tvoc_raw);
- draw_gray_out(x, y);
- }
- void draw_1_4_base(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+11);
- u8g2.print(F("Sec "));
- u8g2.print(wdt_count);
- u8g2.setCursor(x, y+21);
- u8g2.print(F("B-C "));
- u8g2.print(eco2_base, HEX);
- u8g2.setCursor(x, y+31);
- u8g2.print(F("B-T "));
- u8g2.print(tvoc_base, HEX);
- }
- void draw_1_4_delay(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+11);
- u8g2.print(F("Sens "));
- u8g2.print(millis_sensor);
- u8g2.setCursor(x, y+21);
- u8g2.print(F("Disp "));
- u8g2.print(millis_display);
- }
- void draw_1_4_uptime(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+11);
- u8g2.print(F("Day "));
- u8g2.print(wdt_day);
- u8g2.setCursor(x, y+21);
- u8g2.print(F("Hour "));
- u8g2.print(wdt_hour);
- u8g2.setCursor(x, y+31);
- u8g2.print(F("Min "));
- u8g2.print(wdt_min);
- }
- void draw_1_4_system(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(FONT_SMALL);
- u8g2.setCursor(x, y+11);
- u8g2.print(F("ETH "));
- u8g2.print(ethanol_raw, HEX);
- u8g2.setCursor(x, y+21);
- u8g2.print(F("St "));
- u8g2.print(state);
- u8g2.print(F(" Sh "));
- u8g2.print(shake_last_cnt);
- u8g2.setCursor(x, y+31);
- u8g2.print(F("Bat "));
- u8g2.print(battery_raw);
- }
- void draw_1_4_battery(u8g2_uint_t x, u8g2_uint_t y)
- {
- u8g2.setFont(u8g2_font_battery19_tn);
- u8g2.drawGlyph(x, y+20, battery_glyph);
- }
- void draw_1_4_emoticon(u8g2_uint_t x, u8g2_uint_t y)
- {
- uint8_t tvoc_idx, eco2_idx, emo_idx;
- if ( tvoc_raw <= 75 )
- tvoc_idx = 1;
- else if ( tvoc_raw <= 150 )
- tvoc_idx = 2;
- else if ( tvoc_raw <= 300 )
- tvoc_idx = 3;
- else if ( tvoc_raw <= 500 )
- tvoc_idx = 4;
- else if ( tvoc_raw <= 1000 )
- tvoc_idx = 5;
- else if ( tvoc_raw <= 1500 )
- tvoc_idx = 6;
- else if ( tvoc_raw <= 3000 )
- tvoc_idx = 7;
- else if ( tvoc_raw <= 5000 )
- tvoc_idx = 8;
- else
- tvoc_idx = 9;
- if ( eco2_raw <= 500 )
- eco2_idx = 1;
- else if ( eco2_raw <= 600 )
- eco2_idx = 2;
- else if ( eco2_raw <= 800 )
- eco2_idx = 3;
- else if ( eco2_raw <= 1000 )
- eco2_idx = 4;
- else if ( eco2_raw <= 1200 )
- eco2_idx = 5;
- else if ( eco2_raw <= 1400 )
- eco2_idx = 6;
- else if ( eco2_raw <= 5000 )
- eco2_idx = 7;
- else if ( eco2_raw <= 10000 )
- eco2_idx = 8;
- else
- eco2_idx = 9;
- emo_idx = tvoc_idx;
- if ( emo_idx < eco2_idx )
- emo_idx = eco2_idx;
- u8g2.setFont(u8g2_font_emoticons21_tr);
- u8g2.drawGlyph(x+20, y+21, 32+emo_idx);
-
- }
- //===================================================
- void draw_all_numbers(void)
- {
- u8g2.setFontMode(1);
- u8g2.firstPage();
- do {
- u8g2.drawHLine(0, 32, 128);
- u8g2.drawVLine(62, 0, 64);
-
-
- draw_1_4_temperature(1, 0);
- draw_1_4_humidity(66, 0);
- if ( is_air_quality_available )
- {
- draw_1_4_eco2(1, 32+3);
- draw_1_4_tvoc(66, 32+3);
- }
-
- } while ( u8g2.nextPage() );
- }
- void draw_with_emo(void)
- {
- u8g2.setFontMode(1);
- u8g2.firstPage();
- do {
- //u8g2.drawHLine(0, 32, 128);
- //u8g2.drawVLine(62, 0, 64);
- u8g2.drawHLine(0, 32, 128);
- u8g2.drawVLine(62-14, 0, 32);
- u8g2.drawVLine(98, 0, 32);
- u8g2.drawVLine(62-8, 32, 32);
- u8g2.drawVLine(128-12, 32, 32);
-
- draw_1_4_temperature(1, 0);
- draw_1_4_humidity(66-14, 0);
- draw_1_4_emoticon(127-20-21, 4);
- //draw_1_4_wdt_count(66, 0);
- if ( is_air_quality_available )
- {
- draw_1_4_eco2(1, 32+3);
- //draw_1_4_emoticon(66, 32+6);
- draw_1_4_tvoc(66-8, 32+3);
- }
-
- draw_1_4_battery(128-8, 32+7);
- } while ( u8g2.nextPage() );
- }
- void draw_system(void)
- {
- u8g2.setFontMode(1);
- u8g2.firstPage();
- do {
- u8g2.drawHLine(0, 32, 128);
- u8g2.drawVLine(62, 0, 64);
-
-
- draw_1_4_uptime(1, 0);
- //draw_1_4_delay(1,0);
- //draw_1_4_temperature(1, 0);
- draw_1_4_eco2(66, 0);
- //draw_1_4_humidity(66, 0);
- //draw_1_4_wdt_count(66, 0);
- draw_1_4_base(0, 32);
- draw_1_4_system(66, 32);
-
- } while ( u8g2.nextPage() );
- }
- void draw_with_history(void)
- {
- u8g2.setFontMode(1);
- u8g2.firstPage();
- do {
- u8g2.drawHLine(0, 32, 128);
- u8g2.drawVLine(62-20, 0, 32);
- u8g2.drawVLine(98, 0, 32);
-
-
- draw_1_4_temperature(1, 0);
- if ( is_air_quality_available )
- {
- draw_1_4_eco2(66-20, 0);
- draw_1_4_emoticon(127-20-21, 4);
- }
-
- draw_1_2_eco2_history(0, 32);
-
-
-
- } while ( u8g2.nextPage() );
- }
- //===================================================
- uint8_t is_display_on_event(void)
- {
- if ( is_shake > 0 )
- {
- is_shake = 0;
- return 1;
- }
- if ( shake_last_cnt >= 1 )
- return 1;
- return 0;
- }
- void handle_new_display_page(void)
- {
- if ( new_display_cool_down_timer > 0 )
- {
- new_display_cool_down_timer--;
- }
- else
- {
- if ( shake_last_cnt > NEW_DISPLAY_SHAKE_THRESHOLD )
- {
- new_display_cool_down_timer = NEW_DISPLAY_COOL_DOWN;
- current_display_page++;
- if ( current_display_page >= DISPLAY_PAGE_CNT )
- current_display_page = 0;
- }
- }
- }
- void disable_display(void)
- {
- u8g2.clear();
- u8g2.setPowerSave(1);
- is_display_enabled = 0;
- }
- void enable_display(void)
- {
- is_display_enabled = 1;
- u8g2.setPowerSave(0);
- }
- void disable_sensors(void)
- {
- /* both sensors will go to idle/sleep mode when the I2C soft reset is sent */
- Wire.beginTransmission(0);
- Wire.write(6);
- Wire.endTransmission(true); // true: send full stop on I2C
- delay(5);
-
- }
- void enable_sensors(void)
- {
- sht.init();
- sgp.IAQinit();
- delay(5);
- }
- //===================================================
- void next_state(void)
- {
- uint32_t start;
- switch(state)
- {
- case STATE_RESET:
- enable_display();
- startup_timer = STARTUP_TIME;
- state = STATE_STARTUP_DISP_ON;
- display_timer = DISPLAY_TIME;
- break;
-
- // - - - Start Up - - -
-
- case STATE_STARTUP_DISP_ON:
- start = millis();
- sgp.getIAQBaseline(&eco2_base, &tvoc_base); // always store the calibration values during measure
- readAirQuality();
- millis_sensor = millis() - start;
-
- if ( is_display_on_event() )
- {
- // display is already enabled, but the timer is reseted
- display_timer = DISPLAY_TIME;
- }
-
- if ( display_timer == 0 )
- {
- disable_display();
- state = STATE_STARTUP_DISP_OFF;
- }
- else if ( startup_timer == 0 )
- {
- sensor_measure_timer = SENSOR_MEASURE_TIME;
- state = STATE_MEASURE_DISP_ON;
- }
- break;
-
- case STATE_STARTUP_DISP_OFF:
- start = millis();
- sgp.getIAQBaseline(&eco2_base, &tvoc_base); // always store the calibration values during measure
- readAirQuality();
- millis_sensor = millis() - start;
-
- if ( is_display_on_event() )
- {
- enable_display();
- display_timer = DISPLAY_TIME;
- new_display_cool_down_timer = NEW_DISPLAY_COOL_DOWN; // ensure, that the display page is visible for a while
- state = STATE_STARTUP_DISP_ON;
- }
- else if ( startup_timer == 0 )
- {
- sensor_measure_timer = SENSOR_MEASURE_TIME;
- state = STATE_MEASURE_DISP_OFF;
- }
- break;
-
- // - - - Sensor Warm Up - - -
-
- case STATE_WARMUP_DISP_ON: // DONE
- if ( is_display_on_event() )
- {
- // display is already enabled, but the timer is reseted
- display_timer = DISPLAY_TIME;
- }
- if ( display_timer == 0 )
- {
- disable_display();
- state = STATE_WARMUP_DISP_OFF;
- }
- else if ( sensor_warmup_timer == 0 )
- {
- // set the baseline at the end of the warmup
- // this is described in the "SGP30 Driver Integration"
- if ( eco2_base != 0 )
- sgp.setIAQBaseline(eco2_base, tvoc_base); // Restore the baseline values
-
- sensor_measure_timer = SENSOR_MEASURE_TIME;
- state = STATE_MEASURE_DISP_ON;
- }
- break;
-
- case STATE_WARMUP_DISP_OFF: // DONE
- if ( is_display_on_event() )
- {
- enable_display();
- display_timer = DISPLAY_TIME;
- new_display_cool_down_timer = NEW_DISPLAY_COOL_DOWN; // ensure, that the display page is visible for a while
- state = STATE_WARMUP_DISP_ON;
- }
- else if ( sensor_warmup_timer == 0 )
- {
- // set the baseline at the end of the warmup
- // this is described in the "SGP30 Driver Integration"
- if ( eco2_base != 0 )
- sgp.setIAQBaseline(eco2_base, tvoc_base); // Restore the baseline values
-
- sensor_measure_timer = SENSOR_MEASURE_TIME;
- state = STATE_MEASURE_DISP_OFF;
- }
- break;
- // - - - Sensor Measure - - -
- case STATE_MEASURE_DISP_ON: // DONE
- start = millis();
- sgp.getIAQBaseline(&eco2_base, &tvoc_base); // always store the calibration values during measure
- readAirQuality();
- millis_sensor = millis() - start;
-
- if ( is_display_on_event() )
- {
- // display is already enabled, but the timer is reseted
- display_timer = DISPLAY_TIME;
- }
-
- if ( display_timer == 0 )
- {
- disable_display();
- state = STATE_MEASURE_DISP_OFF;
- }
- else if ( sensor_measure_timer == 0 )
- {
- // ignored: Measurement is continued until the display goes off.
- }
- break;
-
- case STATE_MEASURE_DISP_OFF: // DONE
- start = millis();
- sgp.getIAQBaseline(&eco2_base, &tvoc_base); // always store the calibration values during measure
- readAirQuality();
- millis_sensor = millis() - start;
-
- if ( is_display_on_event() )
- {
- enable_display();
- display_timer = DISPLAY_TIME;
- new_display_cool_down_timer = NEW_DISPLAY_COOL_DOWN; // ensure, that the display page is visible for a while
- state = STATE_MEASURE_DISP_ON;
- }
- else if ( sensor_measure_timer == 0 )
- {
- disable_sensors();
- state = STATE_SENSOR_SLEEP_DISP_OFF;
- }
- break;
- // - - - Sensor Sleep - - -
- case STATE_SENSOR_SLEEP_DISP_OFF: // DONE
- if ( is_display_on_event() )
- {
- enable_display();
- display_timer = DISPLAY_TIME;
- new_display_cool_down_timer = NEW_DISPLAY_COOL_DOWN; // ensure, that the display page is visible for a while
-
- enable_sensors();
-
- sensor_warmup_timer = SENSOR_WARMUP_TIME;
- state = STATE_WARMUP_DISP_ON;
- }
- else if ( is_sensor_sample_timer_alarm != 0 )
- {
- is_sensor_sample_timer_alarm = 0;
-
- enable_sensors();
-
- sensor_warmup_timer = SENSOR_WARMUP_TIME;
- state = STATE_WARMUP_DISP_OFF;
- }
- break;
-
- default:
- state = STATE_RESET;
- break;
- }
- }
- //===================================================
- void loop(void) {
- uint8_t i;
- u8g2_uint_t g;
- uint32_t start;
- if ( is_wdt_irq )
- {
- is_wdt_irq = 0;
- next_state();
- readBatteryVoltageLevel();
- handle_new_display_page();
- if ( is_display_enabled ) // "is_display_enabled" will be calculated in next_state()
- {
- start = millis();
- if ( current_display_page == 0 )
- draw_with_emo();
- else if ( current_display_page == 1 )
- draw_with_history();
- else if ( current_display_page == 2 )
- draw_system();
- millis_display = millis() - start;
- }
- else
- {
- millis_display = 0;
- }
- }
- set_sleep_mode(SLEEP_MODE_PWR_DOWN);
- //noInterrupts();
- sleep_enable();
- //interrupts();
- sleep_mode();
- sleep_disable();
- }
|