• Email: info@sparkyswidgets.com

DIY pH Meter

Project description

For Maker Faire 2013 I wanted to demonstrate the use of some of my widgets in a very simple way. One of the best ways I thought would be to create handheld pH meter similar to what is commercially available using my LeoPhi boards. I decided to take a few parts I had laying around and create a standalone handheld meter with a rechargeable battery. I don't think there is anything on the market like this, and I got a metric boatload of questions about these and how to make them, so with out any more delay. How to make a DIY handheld pH Meter!

I just wanted to add a couple updates and post my source code for running the OLED version, after quite a few requests I though it would be a good idea to push it up to github. The unit is still working strong, LeoPhi in standard operation mode uses just shy of 30mA and with the unit on constantly it can last for over 12 hours. I added a simple battery gauge and some other other improvements to make usage better. Overall this is a good project and something I think I will refine more and turn into a kit. Full code breakdown at bottom.

GitHub Reposidget for WordPress

SparkysWidgets / DIYpHMeterFW

Basic Firmware for a DIY pH meter using the LeoPhi hardware and a 4D Systems OLED color screen

Prototype_main_med

OLED Build Med

What's Inside

Not too much that is the key, I use my LeoPhi board to drive everything!

These builds are really simple, at the heart of it all is a LeoPhi driving everything, I also decided to use a boost converter and LiPo battery from Sparkfun to power it all. While I could use the battery by itself, the Vref of the ADC would always be changing, and I cant do a cool trick and monitor the Battery voltage to estimate charge. By adding in a boost converter I got a 5V supply and Vref, that way I can tap into the battery V+ with an ADC pin :) In the first build I went with a simple 4x20 Char LCD, it was basically just parts I had laying around. To round things out I added a power switch and one button for calibration as well.

HpHInsideMain

There really isn't a whole a lot to this as you can see, sorry for the rats nest :) Now to take a look at some of the parts in more detail!

LeoPhi

HpHLeoPhiCloseup

A close up of a LeoPhi Arduino pH sensor interface, the full main digital and analog ports are broken out and make creating DIY pH meters really easy!

At the heart of everything is a LeoPhi unit. I basically designed LeoPhi for this reason, to be able to build programmable meters with it. By breaking out the Key Arduino Digital and Analog "ports" hooking up LCDs and sensors to LeoPhi is extremely easy. Having 2 serial ports (Serial is USB, while Serial1 is on the headers) allows you to use the extra pins or have a wifi/xbee/BLE unit in there as well, but still able to charge and program via USB. A cool example because of the native CDC support via the Leonardo bootloader you can also hook LeoPhi right up through USB to a TP-703n router and still have access to the Serial1 port or pins, accessing LeoPhi via a RESTful interface at the same time! With some planning on the enclosure the RGB Led could be piped out for indication as well!

Battery Power

By using battery power we reduce the chances of ground loop errors and looping noise from other sensors!

HpHLiPo battery

I want to have a 5V rail for my main power supply, since this is only a 3.7v LiPo I will need a boost converter. While I could technically use this to power LeoPhi directly the Vref of the analog in will slowly decrease as the battery draws down. Also if we use a boost converter we can read the battery V+ pin directly with an ADC pin and get a rough Idea of charge! I got the Boost converter and a few these batteries from my friends over at SparkFun!

HpHBoostConverter

All I did was add in a standard  male header onto the battery in connections. This gives me another ground and I can pull the battery voltage right from here. There is a caveat to this, as the ADC pin that we read the voltage from will also have the side effect of powering the device in some of my tests I found it could even power the display. Since I wanted to be able to switch from a charge to run state I decided to use a DPDT switch and switch the V+ pin to the ADC as well.

HpHSelectorswitch

Although it is a bit of a rats nest, the switch simple changes the battery around from Charge to 5V out and vise versa, the other part disconnects the ADC when in charge mode, we don't want the boost converter running while its charging, I think the batteries would be very unhappy! The button is connect to an input and we do some software debouncing, its a simple state machine and use the button to change states (Cal7, Cal4, Info dump, and Run).

Prepare enclosure

Not much here but a messy bunch of cuts sorry to those that kick ass at packaging projects

To finish it off I simply made some rough cutouts for the LCD and one for the USB port. I use an iPhone wall charger to charge the batteries, you can also log data through the USB port too! I will leave most of the screen and enclosure parts to peoples imaginations, I expect to see some awesome builds so please let me know if you have one!!

HpH roughUSBcutout

It sure is ugly, but it works and for a prototype I built in under an hour I would say not bad (hey I need to get a CNC so I can make better enclosures) especially using a utility knife. Other than some more bad case work all it needs is to be buttoned up and charged!

How does it work

Not bad actually I am impressed with how easy it is to calibrate and use

Wow, its actually pretty easy to use and works great, I thought it would be handy but I find myself using it everywhere I can. One side effect is the battery drains fairly fast with the large 20x4 char LCD. At this point I wanted to start using OLEDs so I picked up an awesome color OLED from 4D systems. (man that screen is really gorgeous, the picture just don't do it justice!) but more on this later. The handheld pH meter prototype works good enough that I want to improve it.

Prototype_main_med

Better Screen, smaller case

I thought one way to improve the over all build is to shrink the case

I decided to shrink the case and try and get the form factor smaller so that a 96X96 pixel OLED would look just about right on the face on an enclosure. While at JB Saunders I found a little case that was perfect and decided to use it for this part of the build. One side effect was that the standoff made it very difficult to fit a LeoPhiV2 so I dug out an older V1 version and put it to work.

HpHOLEDBuildmed

The case turned out to be the perfect size really, any smaller and it would have been somewhat difficult to fit everything in. The rats nest inside is much like the earlier build just more compact so I won't bother showing all the other parts besides the case and screen are the same. Lets look at bit more at the OLED screen. This thing rocks, the colors are so nice and crisp its hard to show in photos just how great it looks. I decided to take advantage of this and add some color in when pH is out of range too much it'll show the pH value in red (Same with battery voltage) and yellow when a little out of range. using the screen like this you can convey a ton of info in such a small space. I will have to work more with 4D systems screens especially the OLEDs ones! Overall the 2nd build is great, battery lasts a very long time and you can get lots of info just by quick glances, some improvements I would make are to add BLE module that way one could read from phones or log data etc... Also a streamlined version in a single board to fit a polycase enclosure would be awesome!

HpHOLEDcloseup

Its hard to tell But man those color OLEDs are sexy!!!!

HpHOLEDDatViewAngle

The OLEDs also have amazing viewing angles too, I highly recommend anyone who is going to build projects with screens to look into OLEDs. And for stuff like this you can't beat the 4D systems ones that can work with arduino out of the box. I hope everyone finds this useful!

/*
 This is the basic OLED screen DIY pH meter firmware based on the LeoPhi hardware(either version, 2 is preferred).
 Some core features, keeping in mind that LeoPhi uses about 30mA during normal (non-conserving) operation usage can
 be limited even with 1000mAh battery. Battery indicator is shown in upper right corner and the different colors of the OLED
 are used to help give visual cues on both battery life and actual pH readings. This is a great example to build from when
 creating a DIY pH Meter. Just the little touches of color indication and rechargeable battery make the hassle of
 building a meter well worth it!

 All code is by Ryan except portions dealing with OLED (they com from the 4D systems demo firmware for Arduino)
 Sparky's Widgets 2012

http://www.sparkyswidgets.com/Projects/LeoPhi.aspx

 */

//#include <LiquidCrystal.h>


#include <avr/eeprom.h> 
#ifdef LOG_MESSAGES
  #define HWLOGGING Serial
#else
  #define HWLOGGING if (1) {} else Serial
#endif

#include "Goldelox_Serial_4DLib.h"
#include "GoldeloxBigDemo.h" 
#include "Goldelox_const4D.h"
#define DisplaySerial Serial1
Goldelox_Serial_4DLib Display(&DisplaySerial);
bool shouldClearDisplay = true;

// globals for this program
int fmediatests ;

const int PHIN      =      A0;
const int BATTV      =      A9;
const int GREENLED  =      10; //All Status LEDS on PWM, though they sink not source
const int BLUELED   =      11; 
const int REDLED    =       9; 

//LED fade effects
int brightness =      0;
int fadeAmount =      5;

//State machine base variables, do I really need two separate states, easier to code LED heartbeat and adc sample rate
long previousMillis = 0;
long adcMillis = 0;       
int statusInterval = 1000;           // interval at which to blink or send updates(milliseconds)
int adcReadInterval = 20;           // Our ADC read routine should be cycling at about ~50hz (20ms)

//EEPROM trigger check
#define Write_Check      0x1234 
#define VERSION 0x0002

const float vRef = 5.00; //Set our voltage reference (what is out Voltage at the Vin (+) of the ADC in this case an atmega32u4)
const float opampGain = 5.25; //what is our Op-Amps gain (stage 1)

const int buttonPin = 10;
const int buttonPullUp = 5;
int buttonState = 0;

//Rolling average this should act as a digital filter (IE smoothing)
const int numPasses = 200; //Larger number = slower response but greater smoothing effect

int passes[numPasses];    //Our storage array
int passIndex = 0;          //what pass are we on? this one
long total = 0;              //running total
int pHSmooth = 0;           //Our smoothed pHRaw number

//pH calc globals
float milliVolts, pH, battery; //using floats will transmit as 4 bytes over I2C

//Continuous reading flag
bool continousFlag,statusGFlag;

//Our parameter, for ease of use and eeprom access lets use a struct
struct parameters_T
{
  unsigned int WriteCheck;
  int pH7Cal, pH4Cal,pH10Cal;
  bool continous,statusLEDG;
  float pHStep;
} 
params;

enum statesEnum
{
  systemInitialize,
  normal,
  reset,
  calibrate7,
  calibrate4
};
statesEnum currentState;

void setup()  
{
  pinMode(GREENLED, OUTPUT);
  pinMode(REDLED, OUTPUT);
  pinMode(BLUELED, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(REDLED, HIGH);
  digitalWrite(BLUELED, HIGH);
  digitalWrite(GREENLED, HIGH);
  pinMode(PHIN, INPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT); 
  pinMode(buttonPullUp, OUTPUT);
  digitalWrite(buttonPullUp, HIGH);  
  //Serial1.begin(57600); //Enable basic serial commands in base version
  //Setup for OLED screen

  shouldClearDisplay = true;
  Display.TimeLimit4D   = 5000 ; // 2 second timeout on all commands
  Display.Callback4D = Callback ; // NULL ;
  DisplaySerial.begin(9600) ;
  Display.SSTimeout(0);
  //setting state
  currentState = systemInitialize;
  eeprom_read_block(&params, (void *)0, sizeof(params));
  continousFlag = params.continous;
  statusGFlag = params.statusLEDG;
  if (params.WriteCheck != Write_Check){
    reset_Params();
  }
  sendSerialStatusInfo('I');
  // initialize smoothing variables to 0: 
  for (int thisPass = 0; thisPass < numPasses; thisPass++)
    passes[thisPass] = 0;
}

void loop()
{
  //we made it past our setup routine change into our running(reading) state
  //Our smoothing portion
  //subtract the last pass
  unsigned long currentMillis = millis();

  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed.
  //if it is, the buttonState is HIGH:
  if (buttonState == HIGH) {     
    //We are using a pull up pin so our normal state is HIGH
  } 
  else {
    //Button hit detected, cycle state machine state
    if(currentState == systemInitialize)
    {
      Display.gfx_Cls() ;
      Display.txt_MoveCursor(1, 0) ;
      Display.txt_Height(1) ;
      Display.txt_Width(1) ;
      Display.putstr("Calibration");
      Display.txt_MoveCursor(2, 0) ;
      Display.putstr("Put Probe in");
      Display.txt_MoveCursor(3, 0) ;
      Display.putstr("pH 7 solution");
      Display.txt_MoveCursor(4, 0) ;
      Display.putstr("Press Button");
      Display.txt_MoveCursor(5, 0) ;
      Display.putstr("to Calibrate");
      currentState = calibrate7;
      delay(250);
    }
    else if(currentState == calibrate7)
    {
      Display.gfx_Cls() ;
      Display.txt_MoveCursor(1, 0) ;
      Display.txt_Height(1) ;
      Display.txt_Width(1) ;
      Display.putstr("Put Probe in");
      Display.txt_MoveCursor(2, 0) ;
      Display.putstr("pH 4 solution");
      Display.txt_MoveCursor(3, 0) ;
      Display.putstr("Press Button");
      Display.txt_MoveCursor(4, 0) ;
      Display.putstr("to Calibrate");
      currentState = calibrate4;
      params.pH7Cal = pHSmooth;
      eeprom_write_block(&params, (void *)0, sizeof(params));
      delay(250);
    }
    else if(currentState == calibrate4)
    {
      Display.gfx_Cls() ;
      Display.txt_MoveCursor(1, 0) ;
      Display.txt_Height(1) ;
      Display.txt_Width(1) ;
      Display.putstr("Calibrated");
      Display.txt_MoveCursor(2, 0) ;
      Display.putstr("Press Button");
      Display.txt_MoveCursor(3, 0) ;
      Display.putstr("to resume");
      currentState = reset;
      params.pH4Cal = pHSmooth;
      params.pHStep = ((((vRef*(float)(params.pH7Cal - params.pH4Cal))/1024)*1000)/opampGain)/3;
      eeprom_write_block(&params, (void *)0, sizeof(params));
      delay(250);
    }
    else if(currentState == reset)
    {
      display_pHscreen(true);
      currentState = normal;
      delay(250);
    }
    else if(currentState == normal)
    {
      currentState = systemInitialize;
      delay(250);
    }
  }

if(currentMillis - adcMillis > adcReadInterval)
  {
    // save the last time you blinked the LED 
    adcMillis = currentMillis;

    total = total - passes[passIndex];
    //grab our pHRaw this should pretty much always be updated due to our Oversample ISR
    //and place it in our passes array this mimics an analogRead on a pin
    digitalWrite(4, HIGH); 
    passes[passIndex] = smoothADCRead(PHIN);
    digitalWrite(4, LOW); //these will show our sampling fQ checking with scope!
    total = total + passes[passIndex];
    passIndex = passIndex + 1;
    //Now handle end of array and make our rolling average portion
    if(passIndex >= numPasses)
    {
    passIndex = 0;
    }
    
    pHSmooth = total/numPasses;

    //This is for our status LED heartbeats display however needed
    if(statusGFlag)
      {
        analogWrite(BLUELED, brightness);    
        // change the brightness for next time through the loop:
        brightness = brightness + fadeAmount;
        // reverse the direction of the fading at the ends of the fade: 
        if (brightness == 0 || brightness == 255) {
          fadeAmount = -fadeAmount ; 
        }
      }

  }


  if(Serial.available() ) 
  {
    String msgIN = "";
    int msgLength = 18;
    int msgCount = 0;
    char c;
    while(Serial.available() && msgCount < msgLength)
    {
     c = Serial.read();  
     msgIN += c;
     msgCount++;// just a little bit of string length protection
     }
     processMessage(msgIN);
  }

  calcpH();

  if(currentMillis - previousMillis > statusInterval)
  {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   
    battery = smoothADCRead(BATTV);

    if(continousFlag)
    {
     digitalWrite(REDLED,LOW);
     sendSerialStatusInfo('S');
    }
    digitalWrite(REDLED,HIGH); //we place here in the odd case if you exit C mode while LED is on
    if(currentState == normal || currentState == systemInitialize)
    {
      display_pHscreen(shouldClearDisplay); //we can pass down a clr screen flag if we need <--
      shouldClearDisplay = false; //we had our first run through which is auto clear scenario
    }
  }
}

void calcpH()
{
 float temp = ((((vRef*(float)params.pH7Cal)/1024)*1000)- calcMilliVolts(pHSmooth))/opampGain;
 pH = 7-(temp/params.pHStep);
}

float calcMilliVolts(int numToCalc)
{
 float calcMilliVolts = (((float)numToCalc/1024)*vRef)*1000; //pH smooth is our rolling average mine as well use it
 return calcMilliVolts;
}

int smoothADCRead(int whichPin)
{
  //lets just take a reading and drop it, this should eliminate any ADC multiplexer issues
  int throwAway = analogRead(whichPin);
  int smoothADCRead = analogRead(whichPin);
  return smoothADCRead;
}

void sendSerialStatusInfo(char charStatusInfo)
{
  if(charStatusInfo == 'S')
  {
    Serial.print("pHRaw: ");
    Serial.print(pHSmooth);
    Serial.print(" | ");
    Serial.print("pH: ");
    Serial.print(pH);
    Serial.print(" | ");
    Serial.print("Millivolts: ");
    Serial.print(calcMilliVolts(pHSmooth));
    Serial.print(" | ");
    Serial.print("Batt: ");
    Serial.println((battery/1024)*5);
  }
  if(charStatusInfo == 'I')
  {
   Serial.print("LeoPhi Info: Firmware Ver ");
   Serial.println(VERSION);
   Serial.print("pH 7 cal: ");
   Serial.print(params.pH7Cal);
   Serial.print(" | ");
   Serial.print("pH 4 cal: ");
   Serial.print(params.pH4Cal);
   Serial.print(" | ");
   Serial.print("pH 10 cal: ");
   Serial.print(params.pH10Cal);
   Serial.print(" | ");
   Serial.print("pH probe slope: ");
   Serial.println(params.pHStep);
  }
}

//FIXME: re factor, and don't use Strings bah, it works for now... no long inputs!
void processMessage(String msg)
{
  if(msg.startsWith("L"))
  {
    if (msg.substring(2,1) ==  "0") 
    {
     //Status led visual indication of a working unit on powerup 0 means off
     statusGFlag = false;
     digitalWrite(BLUELED, HIGH);
     Serial.println("Status led off");
     params.statusLEDG = statusGFlag;
     eeprom_write_block(&params, (void *)0, sizeof(params)); 
    }
    if (msg.substring(2,1) ==  "1") 
    {
     //Status led visual indication of a working unit on powerup 0 means off
     statusGFlag = true;
     Serial.println("Status led on");
     params.statusLEDG = statusGFlag;
     eeprom_write_block(&params, (void *)0, sizeof(params));  
    }
     
  }
  if(msg.startsWith("R"))
  {
    //take a pH reading
   calcpH();
   Serial.println(pH); 
  }
  if(msg.startsWith("C"))
  {
     continousFlag = true;
     Serial.println("Continuous Reading On");
     params.continous = continousFlag;
     eeprom_write_block(&params, (void *)0, sizeof(params));
  }
  if(msg.startsWith("E"))
  {
   //exit continuous reading mode
     continousFlag = false;
     Serial.println("Continuous Reading Off");
     params.continous = continousFlag;
     eeprom_write_block(&params, (void *)0, sizeof(params)); 
  }
  if(msg.startsWith("S"))
  {
   //Calibrate to pH7 solution, center on this for zero
   Serial.println("Calibrate 7");
   params.pH7Cal = pHSmooth;
   eeprom_write_block(&params, (void *)0, sizeof(params));
  }
  if(msg.startsWith("F"))
  {
   //calibrate to pH4 solution, recalculate our slope to account for probe
   Serial.println("Calibrate 4");
   params.pH4Cal = pHSmooth;
   //RefVoltage * our deltaRawpH / 10bit steps *mV in V / OP-Amp gain /pH step difference 7-4
   params.pHStep = ((((vRef*(float)(params.pH7Cal - params.pH4Cal))/1024)*1000)/opampGain)/3;
   eeprom_write_block(&params, (void *)0, sizeof(params));
  }
  if(msg.startsWith("T"))
  {
   //calibrate to pH10 solution, recalculate our slope to account for probe 
   Serial.println("Calibrate 10");
   params.pH10Cal = pHSmooth;
   //RefVoltage * our deltaRawpH / 10bit steps *mV in V / OP-Amp gain /pH step difference 10-7
   params.pHStep = ((((vRef*(float)(params.pH10Cal - params.pH7Cal))/1024)*1000)/opampGain)/3;
   eeprom_write_block(&params, (void *)0, sizeof(params));
  }
    if(msg.startsWith("I"))
  {
   //Lets read in our parameters and spit out the info! 
   eeprom_read_block(&params, (void *)0, sizeof(params));
   sendSerialStatusInfo('I');
  }
    if(msg.startsWith("X"))
  {
    //restore to default settings
    Serial.println("Reseting to default settings");
    reset_Params();
  }
}

void reset_Params(void)
{
  //Restore to default set of parameters!
  params.WriteCheck = Write_Check;
  params.statusLEDG = true;
  params.continous = false; //toggle continuous readings
  params.pH7Cal = 512; //assume ideal probe and amp conditions 1/2 of 4096
  params.pH4Cal = 382; //using ideal probe slope we end up this many 10bit units away on the 4 scale
  params.pH10Cal = 890;//using ideal probe slope we end up this many 10bit units away on the 10 scale
  params.pHStep = 59.16;//ideal probe slope
  eeprom_write_block(&params, (void *)0, sizeof(params)); //write these settings back to eeprom
}

////////////////////////Display Stuff//////////////////////////////

void SetThisBaudrate(int Newrate)
{
  int br ;
  DisplaySerial.flush() ;
  DisplaySerial.end() ;
  switch(Newrate)
  {
    case BAUD_110    : br = 110 ;
      break ;
    case BAUD_300    : br = 300 ;
      break ;
    case BAUD_600    : br = 600 ;
      break ;
    case BAUD_1200   : br = 1200 ;
      break ;
    case BAUD_2400   : br = 2400 ;
      break ;
    case BAUD_4800   : br = 4800 ;
      break ;
    case BAUD_9600   : br = 9600 ;
      break ;
    case BAUD_14400  : br = 14400 ;
      break ;
    case BAUD_19200  : br = 19200 ;
      break ;
    case BAUD_31250  : br = 31250 ;
      break ;
    case BAUD_38400  : br = 38400 ;
      break ;
    case BAUD_56000  : br = 56000 ;
      break ;
    case BAUD_57600  : br = 57600 ;
      break ;
    case BAUD_115200 : br = 115200 ;
      break ;
    case BAUD_128000 : br = 133928 ; // actual rate is not 128000 ;
      break ;
    case BAUD_256000 : br = 281250 ; // actual rate is not  256000 ;
      break ;
    case BAUD_300000 : br = 312500 ; // actual rate is not  300000 ;
      break ;
    case BAUD_375000 : br = 401785 ; // actual rate is not  375000 ;
      break ;
    case BAUD_500000 : br = 562500 ; // actual rate is not  500000 ;
      break ;
    case BAUD_600000 : br = 703125 ; // actual rate is not  600000 ;
      break ;
  }
  DisplaySerial.begin(br) ;
  delay(50) ; // Display sleeps for 100
  DisplaySerial.flush() ;
}

void setbaudWait(word  Newrate)
{
  DisplaySerial.print((char)(F_setbaudWait >> 8));
  DisplaySerial.print((char)(F_setbaudWait));
  DisplaySerial.print((char)(Newrate >> 8));
  DisplaySerial.print((char)(Newrate));
  SetThisBaudrate(Newrate); // change this systems baud rate to match new display rate, ACK is 100ms away
  Display.GetAck() ;
}

int trymount(void)
{
#define retries 15
  int i ;
  int j ;
  i = Display.media_Init() ;
  j = 0 ;
  if (!i)
  {
    Display.putstr("Pls insert uSD crd\n") ;
    while (   (!i)
          && (j < retries) )
    {
      Display.putstr(".") ;
      i = Display.media_Init() ;
      j++ ;
    }
    Display.putstr("\n") ;
  }
  if (j == retries)
    return FALSE ;
  else
    return TRUE ;
}

void display_pHscreen(bool shouldClear)
{
  if(shouldClear) Display.gfx_Cls() ;

  HWLOGGING.println(F("Text Tests")) ;

  //what mode are we in
  Display.putstr("pH meter") ;
  Display.gfx_Line(2,10,94,10,BLUE) ;
  
  //draw battery outline upper right corner
  Display.gfx_Line(82,2,92,2,GREEN) ;
  Display.gfx_Line(82,6,92,6,GREEN) ;
  Display.gfx_Line(80,3,80,6,GREEN) ;
  Display.gfx_Line(81,3,81,6,GREEN) ;
  Display.gfx_Line(92,2,92,7,GREEN) ;
  //now draw battery status
  if(battery >= 850)
  {
  Display.gfx_Line(82,3,92,3,BLUE) ;
  Display.gfx_Line(82,4,92,4,BLUE) ;
  Display.gfx_Line(82,4,92,4,BLUE) ;
  Display.gfx_Line(82,5,92,5,BLUE) ;
  }
  if(battery >= 800 && battery <= 849 )
  {
  Display.gfx_Line(82,3,92,3,GREEN) ;
  Display.gfx_Line(82,4,92,4,GREEN) ;
  Display.gfx_Line(82,4,92,4,GREEN) ;
  Display.gfx_Line(82,5,92,5,GREEN) ;
  }
  if(battery >= 750 && battery <= 799 )
  {
  Display.gfx_Line(82,3,82,6,BLACK) ;
  Display.gfx_Line(83,3,83,6,BLACK) ;
  Display.gfx_Line(85,3,92,3,GREEN) ;
  Display.gfx_Line(85,4,92,4,GREEN) ;
  Display.gfx_Line(85,4,92,4,GREEN) ;
  Display.gfx_Line(85,5,92,5,GREEN) ;
  }
  if(battery >= 700 && battery <= 749 )
  {
  Display.gfx_Line(82,3,82,6,BLACK) ;
  Display.gfx_Line(83,3,83,6,BLACK) ;
  Display.gfx_Line(84,3,84,6,BLACK) ;
  Display.gfx_Line(85,3,85,6,BLACK) ;
  Display.gfx_Line(86,3,92,3,GREEN) ;
  Display.gfx_Line(86,4,92,4,GREEN) ;
  Display.gfx_Line(86,4,92,4,GREEN) ;
  Display.gfx_Line(86,5,92,5,GREEN) ;
  }
  if(battery >= 625 && battery <= 699 )
  {
  Display.gfx_Line(82,3,82,6,BLACK) ;
  Display.gfx_Line(83,3,83,6,BLACK) ;
  Display.gfx_Line(84,3,84,6,BLACK) ;
  Display.gfx_Line(85,3,85,6,BLACK) ;
  Display.gfx_Line(86,3,86,6,BLACK) ;
  Display.gfx_Line(87,3,92,3,YELLOW) ;
  Display.gfx_Line(87,4,92,4,YELLOW) ;
  Display.gfx_Line(87,4,92,4,YELLOW) ;
  Display.gfx_Line(87,5,92,5,YELLOW) ;
  }
  if(battery <= 624 )
  {
  Display.gfx_Line(82,3,82,6,BLACK) ;
  Display.gfx_Line(83,3,83,6,BLACK) ;
  Display.gfx_Line(84,3,84,6,BLACK) ;
  Display.gfx_Line(85,3,85,6,BLACK) ;
  Display.gfx_Line(86,3,86,6,BLACK) ;
  Display.gfx_Line(87,3,87,6,BLACK) ;
  Display.gfx_Line(88,3,92,3,RED) ;
  Display.gfx_Line(88,4,92,4,RED) ;
  Display.gfx_Line(88,4,92,4,RED) ;
  Display.gfx_Line(88,5,92,5,RED) ;
  }
  //Probe slope status
  Display.txt_FGcolour(BLUE) ;
  Display.txt_MoveCursor(2, 0) ;
  //set up a temp buffer to convert steps to string
  char mVperpHBuff[6];
  Display.putstr(dtostrf(params.pHStep,5,2,mVperpHBuff)) ;
  Display.putstr("mV/pH");

  //Position and size for large clear pH number
  Display.txt_MoveCursor(4, 2) ;
  Display.txt_Height(3) ;
  Display.txt_Width(2) ;
  //Set color based on pH Red out of preferred range, green in range, yellow above range
  Display.txt_FGcolour(LIME) ;
  //set out ph putstr expects a 'string' lets use a std function to convert on the fly with temp buffer
  char pHscreenBuff[6];
  Display.putstr(dtostrf(pH,5,2,pHscreenBuff)) ;
  //Switch back to default size, example of 'superscript'
  //Display.txt_Height(1) ;
  //Display.txt_Width(1) ;
  //Display.putCH('z') ;

  HWLOGGING.print(F("Char height=")) ;
  HWLOGGING.print(Display.charheight('w') ) ;
  HWLOGGING.print(F(" Width= ")) ;
  HWLOGGING.print(Display.charwidth('w') ) ;
  HWLOGGING.println(F("")) ;
  //reset to defaults
  Display.txt_BGcolour(BLACK) ;
  Display.txt_FGcolour(LIME) ;
  Display.txt_FontID(SYSTEM) ;
  Display.txt_Height(1) ;
  Display.txt_Width(1) ;
  Display.txt_MoveCursor(0,0) ;
}

void Callback(int ErrCode, unsigned char ErrByte)
{
#ifdef LOG_MESSAGES
  const char *Error4DText[] = {"OK\0", "Timeout\0", "NAK\0", "Length\0", "Invalid\0"} ;
  HWLOGGING.print(F("Serial 4D Library reports error ")) ;
  HWLOGGING.print(Error4DText[ErrCode]) ;

  if (ErrCode == Err4D_NAK)
  {
    HWLOGGING.print(F(" returned data= ")) ;
    HWLOGGING.println(ErrByte) ;
  }
  else
    HWLOGGING.println(F("")) ;
  while(1) ; // you can return here, or you can loop
#else
// Pin 13 has an LED connected on most Arduino boards. Just give it a name
#define led 13
  while(1)
  {
    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(200);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
    delay(200);               // wait for a second
  }
#endif
}
DIYpHMeterOLED.inoview rawview file on GitHub

Overview

Details