I spent a chunk of time understanding the design of SparkysWidgets minipH breakout board. I posted what I learned here. My conclusion is – at least at this stage – nice design! I’ll use SparkysWidgets minipH in my current prototype.
The goal of this post is to get the pH readings of the miniPH as accurate as possible.
The pieces in the picture include:
- the minipH breakout board
- an Arduino Uno
- a breadboard
- jumper wires
- pH probe and calibration solution from Atlas Scientific. I had purchased the pH kit earlier and used it to run tests which I documented in this post.
While I consider the price on the high side, I am using the probe and calibration solutions from the kit because Atlas Scientific is known for quality products and I have familiarity and confidence in them based on my prior use.
The process to reach the goal will include:
- Understand results from running Sparky’s Widget’s minipH.ino.
- Calibrate the readings.
- Implement any changes to the setup and Arduino sketch that will improve the readings.
- Document final readings.
Initial Run of minipH.ino
It was very easy to wire the prototype. All I needed to do was solder headers onto the minipH breakout board so that the Vin, GND, and I2C (SDA and SCL) pins can be wired to the Arduino.
I ran the sketch when the pH probe was in each of the calibration solutions and took a screen shot of the serial monitor:
I was happy to find the results were reasonable since I hadn’t calibrated yet.
Evolution of minipH.ino
minipH.ino gives me the basics of what I need to calibrate the pH voltage step and to read the pH. I will evolve this sketch to:
- use a state machine to handle input. This will make the code easier to read and easier to change to different displays. The first display I will use is the serial monitor.
- calibrate based on a weighted average of several readings.
- determine if the probe needs to be replaced.
- read the pH. In this case there is no need to change what Sparky’s Widgets has done.
- show the info on what was used in calculating the pH voltage step. I will use what Sparky’s Widget has provided and add to it.
The sketch – minipH_bitknitting.ino – is located here
State Machine Input
I based input from a person using this system on the following state machine:
||Valid Keys or Code To Run
||<any other character>
||-> calibrate pH
||Display new slope
||-> read pH
||Display pH value
||-> show help
||Display amount deviation and $ noise in readings
||Display the params used in calculating the pH
||->print message, go to help
||Display invalid entry text
For a pH probe to match default (ideal) readings, it is assumed there is a voltage change of 59.16mV per pH unit (I’ll refer to the voltage change per pH unit as the pH voltage step. For example – in the ideal – a pH of 0 reads 59mV higher (0.414V) than a pH of 1 (0.355V). One thing that has been stamped in my mind from doing pH explorations is the importance of calibration. Each pH probe is going to measure the pH differently. A big factor is how the voltage measurements that come in through the ADC and then converted to a pH value vary from the ideal readings. Knowing this variance from the ideal readings allows the formula for calculating the pH to be adjusted and hence more accurate.
The sketch has two commands for calibration:
- 4 – assumes pH probe is in a pH 4 calibrated solution. Takes the reading and adjusts the pH voltage step to use this reading.
- 7 – assumes pH probe is in a pH 7 calibrated solution. Takes the reading and adjust the pH voltage step.
Here is the line of code that adjusts the pH voltage step:
params.pHStep = ((((vRef*(float)(params.pH7Cal – params.pH4Cal))/4096)*1000)/opampGain)/3;
Values to take note of in this equation include:
as shown in the image, an ADC takes in an analog input and puts it into a discrete step. In the case of a 12 bit ADC – which is used by the minipH – there are 4096 discrete steps – 2^12. In comparison, the ADC on the Arduino is 10 bit. This means there are 2^10 discrete steps = 1,024 discrete steps.
- vRef – as the shortened name implies, this is the voltage reference. It determines the mapping between the analog and digital output. For example, the vRef of the MCP1541 is a stable 4.096. When a reading comes in from the ADC, it’s discreet value will be between 0 and 4095. Which discreet value will be determined by VRef/4096. 4.096 is a great VRef for 12 bit ADCs since 4.096/4096 is 1mV. When the VRef is 4.93V – for example what I might see when using the USB port on my Mac as a power source, the division is 4.93/4096 = .001203613 – not as even a chunking of the analog into discreet parts. I’ll discuss this a bit more below.
- opampGain – recall in my earlier post the first op amp was used to amplify the voltages coming in from the pH probe. This is what the opampGain is. In the schematic for the minipH, the op amp gain is 5.68.
When I looked closer at the minipH breakout board, I noticed it did not include the MCP1541 as the schematic shows. The schematic seems to not be up to date with the actual minipH breakout board. OK. I can’t use the AREF of the Arduino, because AREF assumes the Arduino’s 10 bit ADC is being used. This means I will use the 5V out of the Arduino as the VRef to the ADC. Every time I have measured Arduino’s 5V out, it has been higher that 4.096, typically around 4.93. Yet constantly measuring and adjusting the vRef variable based on DMM readings was a non-starter for me. I could decide to use 4.93 – assuming this is “close enough” or somehow I could get the actual value of the Arduino’s 5V out. A Google search and I have found code from a very sharp person that indeed calculates the 5V out. How terrific is that? I may not be capable of figuring this code on my own, but happily – this person shared! See the readVcc() function in minipH_bitknitting.ino.
Output from the readVcc() function and my DMM got the same value – 4.94V for Arduino’s 5V out. mV results from a few ADC readings when the probe was in the pH 7 calibrating solution:
shows the difference when vRef is left at the default of 4.096 and not what is used when the MCP1541 is not use is about .5mV. I had this table that maps the pH value to the expected ADC reading (shift column) in my previous post:
Using the result from readVcc() gets a value that is closer to what I would expect to be more accurate.
Op Amp Gain
At first I was surprised to see the opAmpGain set to 5.25 in minipH.ino. After a closer check at the minipH schematic
the two resistors – R8 and R7 – used for the gain, I found R8 on the PCB to be 200K. So the OpAmpGain is indeed 5.25 and not 5.7 as calculated in the previous post.
I am using a weighted average of many readings when calculating the pH voltage step.
I decided to take multiple readings and weight them because given the characteristics of the readings to aid in eliminating noise. One spot where noise occurs when the pH probe takes the reading. The pH probe is made of glass which isn’t that great in creating an electrical circuit. This means it has a very high output impedance, typically around or more than 100MΩ. SpakysWidgets notes: “A typical probe has an impedance of anywhere between 50MΩ and 500MΩ, and since 100MΩ*1nA=.1v even having a single stray nano amp can throw our measurement off by almost 2 entire ph units.” Here is the chunk of code I added to use a weighted average of multiple readings when adjusting the pH voltage step:
unsigned long currentMillis = millis();
lastpHCalibrationMillis = currentMillis;
while (currentMillis – lastpHCalibrationMillis < pHReadCalibrationPeriod)
//get a pH reading (assumes pH probe is in a calibration solution)
adc_result = readADC();
float last_pH = params.pHStep;
//modify the mV between pH readngs by the current adc reading
if( pHCalibrating == 4 ) calibratepH4(adc_result);
if( pHCalibrating == 7 ) calibratepH7(adc_result);
//add the new calibration reading to the weighted average.. putting a weight of 70% on latest additions was decided as a starting place…
params.pHStep = .7 * params.pHStep + .3*last_pH;
Serial.print(“pH Slope: “);
currentMillis = millis();
//write the new pH voltage step unit to EEPROM so that it is stored in ‘permanent’ memory
eeprom_write_block(¶ms, (void *)0, sizeof(params)); //write these settings back to eeprom
Like tires on our cars, pH probes deteriorate to the point in which they must be replaced. Or perhaps the pH probe is not up to the job. But how off should the pH readings be from the ideal before I must use a different pH probe? Based on information provided in this post
Generally speaking, when an offset of more than 30 mv (at 7.0 pH) develops or more than 2 minutes is required for a probe to stabilize in a buffer solution a probe has reached end of life or needs reconditioning.
Based on this sentence, there are two things I should look at to answer if I need to replace my pH probe:
- the pH 7 reading of a calibrated solution should be 0mV. Is the reading > 30mV or <-30mV?
- are the pH readings within a 2 minute time below an acceptable noise threshold?
I don’t know what the acceptable noise threshold should be. I’m thinking it should be fairly relaxed to accommodate the inherent noise of the readings. I’ll start with 10%. See minipH_bitknitting.ino
Sending the ‘r’ or ‘R’ character to the arduino takes a pH reading. Check out the readpH() function to walk through how the pH is calculated based a reading of the ADC.
My Yippee! Moment came when it appears the minipH probe worked as advertised. It is a nice, simple design. I was surprised to find the VREF IC was not there. However, given the resolution of the readings, using the function to calculate the Vin seems to work fine.
I plan to evolve my knowledge of the minieC sensor using the same methods I used to understand the minipH sensor. By doing so, I am gaining a practical understanding of the components in the circuit. With perhaps op amps being the star of the show.
Thank you for reading this far. I hope you find many things to smile about.