• Email: info@sparkyswidgets.com

MinipH I2C pH Interface

MinipH I2C pH Interface

Project description

MinipH pH interface  is a very cost effective solution for adding pH sensing capability to any project. while it only uses I2C communications for digital output it also contains an analog output which can be used in conjunction with the I2C communications. This makes it an ideal addition to any electronic tool box, especially those that need water quality sensors!


Here is a MinipH pH interface in action, it has a wide input voltage range and can be used on either an Arduino or Raspberry pi without modification.


Simple but effective pH interface, great for every project that needs pH sensing

GitHub Reposidget for WordPress

SparkysWidgets / MinipHBFW

This is the most basic "Example" firmware for MinipH in Arduino

While working with Peter from ManyLabs/WireGarden, we wanted to begin to create a system of open source water quality sensors that can be cheaply deployed in schools, remote research, maker projects, basically anywhere there is a need! This project is the result of several months of research and prototyping on top of my current pH projects, I spent a lot of time incorporating a solid analog font end to interface with the pH probe which is often neglected even in commercial products.

By using a cheap, accurate and reliable I2C ADC we can both increase accuracy and decrease cost, and also create a product that people can source parts for globally at reasonable prices. while the the ADC is in a small package they are easy enough to handle even for hand soldering! This unit can even be hand assembled, and I have included a single sided variant of the design for those that want to create their own from scratch!

The front end consists mainly of the standard fixed gain/offset from our pH interface tutorial, but with great care taken in layout to ensure the best possible results from the probe itself(guard rings, current return path consideration, digital and analog separation, decoupling on each IC, etc...).

The result of this project, is a very high quality sensor interface at a very reasonable cost that is easy to use! You checkout my store to find MinipH.

Basic Usage

Very similar to the I2C breakout, it is easy to take readings, and witht he LeoPhi math easy to turn them into pH values!

The usage of the device is very straightforward and example code is provided, the gist of it is to simply ask the ADC for its value over standard I2C (both fast and slow modes) and assemble the bytes to recreate the 12bit number. Post processing is the standard pH calculations that can be found both here and on a few various other websites out there (although we all tend to calculate pH ever so slightly, I suspect the end results are all similar though)

I have already uploaded a decent example on how to implement ph readings from a MinipH board to my github, and I will also include it here as well!

As a side note the ADC does about ~28.8KSPS giving us more then enough data points and bandwidth to be able to oversample and decimate, although with the quality of the 12 bit reading it gives already it would really be an overkill at this point (I am sure someone will anyway!)

This is a simple example showing how to interface our mini I2C pH interface.
The usage for this design is very simple, as it uses the MCP3221 I2C ADC. Although actual
pH calculation is done offboard the analog section is very well laid out giving great results
at varying input voltages (see vRef for adjusting this from say 5v to 3.3v).
MinipH can operate from 2.7 to 5.5V to accommodate varying levels of system. Power VCC with 3.3v for a raspi!

ADC samples at ~28.8KSPS @12bit (4096 steps) and has 8 I2C address of from A0 to A7 (Default A5)
simply assemble the 2 BYTE registers from the standard I2C read for the raw reading.
conversion to pH shown in code.

Note: MinipH has an optional Vref(4.096V) that can be bypassed as well!

Sparky's Widgets 2012

//I2C Library
//We'll want to save calibration and configuration information in EEPROM
//EEPROM trigger check
#define Write_Check 0x1234

#define ADDRESS 0x4D // MCP3221 A5 in Dec 77 A0 = 72 A7 = 79)
// A0 = x48, A1 = x49, A2 = x4A, A3 = x4B,
// A4 = x4C, A5 = x4D, A6 = x4E, A7 = x4F

//Our parameter, for ease of use and eeprom access lets use a struct
struct parameters_T
unsigned int WriteCheck;
int pH7Cal, pH4Cal;
float pHStep;

float pH;
const float vRef = 4.096; //Our vRef into the ADC wont be exact
//Since you can run VCC lower than Vref its
//best to measure and adjust here
const float opampGain = 5.25; //what is our Op-Amps gain (stage 1)

void setup(){
Wire.begin(); //initialize I2C
//Lets read our Info from the eeprom and setup our params,
//if we loose power or reset we'll still remember our settings!
eeprom_read_block(¶ms, (void *)0, sizeof(params));
//if its a first time setup or our magic number in eeprom is wrong reset to default
if (params.WriteCheck != Write_Check){

void loop(){
//This is our I2C ADC interface section
//We'll assign 2 BYTES variables to capture the LSB and MSB(or Hi Low in this case)
byte adc_high;
byte adc_low;
//We'll assemble the 2 in this variable
int adc_result;

Wire.requestFrom(ADDRESS, 2); //requests 2 bytes
while(Wire.available() < 2); //while two bytes to receive //Set em adc_high = Wire.read(); adc_low = Wire.read(); //now assemble them, remembering our byte maths a Union works well here as well adc_result = (adc_high * 256) + adc_low; //We have a our Raw pH reading fresh from the ADC now lets figure out what the pH is calcpH(adc_result); //Lets handle any commands here otherwise if we do prior to a fresh ADC reading //may end up calibrate to slightly older data (this really might not matter, handle as you will) if(Serial.available() ) { char c = Serial.read(); if(c == 'C') { //Which range? int calrange; calrange = Serial.parseInt(); if( calrange == 4 ) calibratepH4(adc_result); if( calrange == 7 ) calibratepH7(adc_result); } if(c == 'I') { //Lets read in our parameters and spit out the info! eeprom_read_block(¶ms, (void *)0, sizeof(params)); 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 probe slope: "); Serial.println(params.pHStep); } } //Spit out some debugging/Info to show what our pH and raws are Serial.print("pH: "); Serial.print(pH); Serial.print(" | "); Serial.println(adc_result); //You can delay or millis here depending on what tasks(others) you may have delay(1000); } //Lets read our raw reading while in pH7 calibration fluid and store it //We will store in raw int formats as this math works the same on pH step calcs void calibratepH7(int calnum) { params.pH7Cal = calnum; calcpHSlope(); //write these settings back to eeprom eeprom_write_block(¶ms, (void *)0, sizeof(params)); } //Lets read our raw reading while in pH4 calibration fluid and store it //We will store in raw int formats as this math works the same on pH step calcs //Temperature compensation can be added by providing the temp offset per degree //IIRC .009 per degree off 25c (temperature-25*.009 added pH@4calc) void calibratepH4(int calnum) { params.pH4Cal = calnum; calcpHSlope(); //write these settings back to eeprom eeprom_write_block(¶ms, (void *)0, sizeof(params)); } //This is really the heart of the calibration process, we want to capture the //probes "age" and compare it to the Ideal Probe, the easiest way to capture two readings, //at known point(4 and 7 for example) and calculate the slope. //If your slope is drifting too much from ideal (59.16) its time to clean or replace! void calcpHSlope () { //RefVoltage * our deltaRawpH / 12bit steps *mV in V / OP-Amp gain /pH step difference 7-4 params.pHStep = ((((vRef*(float)(params.pH7Cal - params.pH4Cal))/4096)*1000)/opampGain)/3; } //Now that we know our probe "age" we can calculate the proper pH Its really a matter of applying the math //We will find our millivolts based on ADV vref and reading, then we use the 7 calibration //to find out how many steps that is away from 7, then apply our calibrated slope to calculate real pH void calcpH(int raw) { float miliVolts = (((float)raw/4096)*vRef)*1000; float temp = ((((vRef*(float)params.pH7Cal)/4096)*1000)- miliVolts)/opampGain; pH = 7-(temp/params.pHStep); } //This just simply applies defaults to the params in case the need to be reset or //they have never been set before (!magicnum) void reset_Params(void) { //Restore to default set of parameters! params.WriteCheck = Write_Check; params.pH7Cal = 2048; //assume ideal probe and amp conditions 1/2 of 4096 params.pH4Cal = 1286; //using ideal probe slope we end up this many 12bit units away on the 4 scale params.pHStep = 59.16;//ideal probe slope eeprom_write_block(¶ms, (void *)0, sizeof(params)); //write these settings back to eeprom }[/sourcecode]

Happy Water Sensing!

Creative Commons License

MinipH I2C pH Interface by Sparky's Widgets is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.Based on a work at http://www.sparkyswidgets.com/portfolio-item/miniph-i2c-ph-interface/.


A simple to use and cost effective way to Interface a pH probe. Works great with various platforms and operates under a wide voltage range(2.7 to 5.5).


  • 12 bit I2C ADC
  • pH range from .1 to 14