Hi,
Appreciate the responses and I apologize for leaving out detail.
The schematic is here:
The code is included below. I did not include it initially since it runs as expected when the entire thing is powered through the USB port on the Arduino. There is no logic in setup() to differentiate between USB power and battery power.
Kind regards,
Eelco
/*****************************************************************************
* ej's o2 oled analyzer - v0.21
* http://ejlabs.net/arduino-oled-nitrox-analyzer
*
* License
* -------
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*****************************************************************************/
// prototypes:
unsigned int EEPROMReadInt(int p_address);
int calibrate(int x);
// end prototypes
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_ADS1X15.h>
#include <EEPROM.h>
#include <RunningAverage.h>
// #include <splash.h>
#define EE_DEBUG true // true for printing various debugging messages to Serial, false otherwise
int ee_debug_cnt = 0; // used to control some messaging in debug mode
#define RA_SIZE 20
RunningAverage RA(RA_SIZE);
// ADS1115 creation
Adafruit_ADS1115 ads;
// OLED definitions and declaration for new Adafruit_SSD1306 constructors.
// from: https://github.com/adafruit/Adafruit_SSD1306/blob/master/examples/ssd1306_128x64_i2c/ssd1306_128x64_i2c.ino
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// // OLED definitions and declarations for Adafruit_SH110X contructors
// // Uncomment the initialize the I2C address , uncomment only one,
// // If you get a totally blank screen try the other
// #define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
// e.g. the one with GM12864-77 written on it
// //#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED's
// #define SCREEN_WIDTH 128 // OLED display width, in pixels
// #define SCREEN_HEIGHT 64 // OLED display height, in pixels
// #define OLED_RESET -1 // QT-PY / XIAO
// Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Define pins used
const int buttonPin = 2; // push button
const int buzzerPin = 9; // buzzer
const int ledPin = 13; // led
// Define button press durations in seconds
const int cal_holdTime = 2; // 2 sec button hold to calibration
const int mod_holdTime = 4; // 4 sec hold to po2 mod change
const int max_holdtime = 6; // 6 sec hold to reset max o2 result
// Define variables
double calibrationv; // Calibration return value?
float multiplier; // Read-out multiplier to convert mV into percent
long millis_held; // How long the button was held (milliseconds)
long secs_held; // How long the button was held (seconds)
long prev_secs_held; // How long the button was held in the previous check
byte previous = HIGH;
unsigned long firstTime; // how long since the button was first pressed
int active = 0;
double result_max = 0;
//
// Calculate MOD (Maximum Operating Depth)
// Inputs:
// percentage: current O2 percentage in gas
// ppo2: desired max ppo2 (default to 1.4 bar)
//
float max_po1 = 1.30; // lowest ppo2 pressure for MOD calculcation (in bar)
const float max_po2 = 1.60;
float cal_mod (float percentage, float ppo2 = 1.40) {
return 10 * ( (ppo2/(percentage/100)) - 1 );
}
//
// make beep for x time (seconds, default 1s)
void beep(int x=1) {
//digitalWrite(ledPin, HIGH); // led blink disable for battery save
for(int i=0; i<x; i++) {
tone(buzzerPin, 2800, 100);
delay(200);
}
//digitalWrite(ledPin, LOW);
noTone(buzzerPin);
}
//
// Read O2 sensor and update RunningAverage
// Input:
// adc: ADS channel, default 0
//
void read_sensor(int adc=0) {
int16_t millivolts = 0;
millivolts = ads.readADC_Differential_0_1();
RA.addValue(millivolts);
}
//
// Write to EEPROM
// Inputs:
// p_address: EEPROM address to write to
// p_value: value to store
//
void EEPROMWriteInt(int p_address, int p_value)
{
byte lowByte = ((p_value >> 0) & 0xFF);
byte highByte = ((p_value >> 8) & 0xFF);
EEPROM.write(p_address, lowByte);
EEPROM.write(p_address + 1, highByte);
}
//
// Read from EEPROM
// Input:
// p_address: address to read from
//
unsigned int EEPROMReadInt(int p_address)
{
byte lowByte = EEPROM.read(p_address);
byte highByte = EEPROM.read(p_address + 1);
return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}
//
// Sensor calibration routine
// Inputs:
// x: EEPROM address
//
int calibrate(int x) {
// Clear display and print calibrating message
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,0);
display.setTextSize(2);
display.print(F("Calibrate"));
display.display();
// Clear RunningAverage buffer, get new readout and save to EEPROM;
double result;
for(int cx=0; cx<= RA_SIZE; cx++) {
read_sensor(0);
}
result = RA.getAverage();
result = abs(result); // absolute value so works irrespective of the polarity of the sensor connection
EEPROMWriteInt(x, result); // write to eeprom
// Beep when done
beep(1);
// Wait 1 second
delay(1000);
active = 0;
return result;
}
//
// Main gas analysis routine, including menu
// Inputs:
// x:
// cal: calibration reading for ambient air
//
void analysing(int x, int cal) {
double currentmv=0;
double result;
double mv = 0.0;
if(EE_DEBUG && ee_debug_cnt == 0){ Serial.println(F("Main analysing fn")); }
read_sensor(0);
currentmv = RA.getAverage(); // read (average) current sensor value
currentmv = abs(currentmv); // absolute value so works irrespective of sensor polarity
// Calculate current O2 % based on readout
result = (currentmv / cal) * 20.9;
if (result > 99.9) result = 99.9;
mv = currentmv * multiplier;
// Clear display and print current O2 %
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,34);
if (mv < 0.02 || result <= 0) {
// Error for too-low sensor readout value
display.setTextSize(2);
display.println(F("Sensor"));
display.print(F("Error!"));
} else {
// display.setCursor(0,32);
display.setTextSize(4);
display.print(result,1);
display.println(F("%"));
// Increment result_max is readout still increasing
if (result >= result_max) {
result_max = result;
}
display.setTextSize(1);
display.setCursor(0,0);
display.setTextColor(BLACK, WHITE);
display.print(F("Max "));
display.print(result_max,1);
display.print(F("% "));
// Print sensor mV reading as well (to check sensor health)
display.setCursor(70,0);
display.print(mv,2);
display.print(F("mV "));
if (active % 4) {
display.setCursor(120,0);
display.setTextColor(WHITE);
display.print(F("."));
}
// Print MOD based on current O2 %
display.setTextColor(WHITE);
display.setCursor(0,8);
display.print(F("ppO2 "));
display.print(max_po1,1);
display.print(F(" / "));
display.print(max_po2,1);
display.print(F(" MOD"));
display.setTextSize(2);
display.setCursor(0,16);
display.print(cal_mod(result,max_po1),1);
display.print(F("/"));
display.print(cal_mod(result,max_po2),1);
display.print(F("m "));
// menu: switch between modes based on duration of button press
if (secs_held < 5 && active > 16) {
display.setTextSize(2);
display.setCursor(0,0);
display.setTextColor(BLACK, WHITE);
if (secs_held >= cal_holdTime && secs_held < mod_holdTime) {
display.print(F(" CAL "));
}
if (secs_held >= mod_holdTime && secs_held < max_holdtime) {
display.print(F(" PO2 "));
}
if (secs_held >= max_holdtime && secs_held < 10) {
display.print(F(" MAX "));
}
}
}
display.display();
if(EE_DEBUG){ ee_debug_cnt++; }
} // end analysing()
//
// Lock screen
// Inputs:
// pause: timeout to (un-)lock, in ms, defautl 5000ms
//
void lock_screen(long pause = 5000) {
// beep(1);
display.setTextSize(1);
display.setCursor(0,0);
display.setTextColor(0xFFFF, 0);
display.print(F(" "));
display.setTextColor(BLACK, WHITE);
display.setCursor(0,0);
display.print(F("======= LOCK ======="));
display.display();
for (int i = 0; i < pause; ++i) {
while (digitalRead(buttonPin) == HIGH) {
}
}
active = 0;
} // end lock_screen()
//
// Cycle between Max pPO2 values and confirm selected value on-screen
//
void po2_change() {
if (max_po1 == 1.30) max_po1 = 1.40;
else if (max_po1 == 1.40) max_po1 = 1.50;
else if (max_po1 == 1.50) max_po1 = 1.60;
else if (max_po1 == 1.60) max_po1 = 1.30;
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,0);
display.setTextSize(2);
display.println(F("pO2 set"));
display.print(max_po1);
display.display();
// beep(1);
delay(1000);
active = 0;
} // end po2_change()
void max_clear() {
result_max = 0;
display.clearDisplay();
display.setTextColor(WHITE);
display.setCursor(0,0);
display.setTextSize(2);
display.println(F("Max result"));
display.print(F("cleared"));
display.display();
// beep(1);
delay(1000);
active = 0;
}
//
// Setup logic at startup
void setup(void) {
Serial.begin(9600);
if(EE_DEBUG){ Serial.println(F("Begin setup")); }
delay(500); // wait for screen
/* power saving stuff for battery power */
// Disable ADC
// ADCSRA = 0;
// Disable the analog comparator by setting the ACD bit
// (bit 7) of the ACSR register to one.
// ACSR = B10000000;
// Disable digital input buffers on all analog input pins
// DIDR0 = DIDR0 | B00111111;
// Initialize screen
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
// if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
// Serial.println(F("SSD1306 allocation failed"));
// for(;;); // loop forever
// }
if(EE_DEBUG) {
Serial.println(F("Display initialized"));
display.display(); // splash screen
delay(200); // show for 0.2s
}
// ADS1115 set gain and initialize
ads.setGain(GAIN_TWO);
multiplier = 0.0625F;
ads.begin(); // ads1115 start
pinMode(buttonPin,INPUT_PULLUP);
// Clear RunningAverage
RA.clear();
for(int cx=0; cx<= RA_SIZE; cx++) {
read_sensor(0);
}
// Calibrate sensor: start by reading last from EEPROM
calibrationv = EEPROMReadInt(0);
if (calibrationv < 100) {
calibrationv = calibrate(0);
}
// Beep when done
beep(1);
if(EE_DEBUG){ Serial.println(F("End setup")); }
} // end setup()
//
// Main loop while active
void loop(void) {
if(EE_DEBUG && ee_debug_cnt==0){ Serial.println(F("Starting main loop()")); }
// Read button and measure duration held pressed
int current = digitalRead(buttonPin);
if (current == LOW && previous == HIGH && (millis() - firstTime) > 200) {
firstTime = millis();
active = 17;
}
millis_held = (millis() - firstTime);
secs_held = millis_held / 1000;
// Menu logic
if (millis_held > 2) {
if (current == HIGH && previous == LOW) {
if (secs_held <= 0) {
lock_screen();
}
if (secs_held >= cal_holdTime && secs_held < mod_holdTime) {
calibrationv = calibrate(0);
}
if (secs_held >= mod_holdTime && secs_held < max_holdtime) {
po2_change();
}
if (secs_held >= max_holdtime && secs_held < 10) {
max_clear();
}
}
}
previous = current;
prev_secs_held = secs_held;
analysing(0,calibrationv);
delay(200);
active++;
if(EE_DEBUG){ ee_debug_cnt++; }
} // end main loop()