Published: 2016-09-25 | Categories: [»] Engineeringand[»] Chemistry.

Two months ago I presented the barebones of a [»] conductometric circuit to monitor the concentration of salts by their electrical conductivity. In this post, I present a major upgrade of the circuit with the following advantages:

- It is now fully compatible with the Arduino Uno R3 board (demo code at the end of the post).

- It has a self-tuning extended range to read from less than 5 mg/l to more than 25 g/l NaCl solutions.

- It has a simplified probe construction that is now based on a gold-plated PCB.

- The circuit now uses a reference signal for more accurate measurements.

Figure 1 shows a picture of the shield with the new probe. It is based on SMT components and a bill of material as well as the Gerber files are available at the end of the post so that you can reproduce the circuit.

Figure 1 - Conductivity shield for Arduino Uno with its gold-plated probe

The overall principle of the board did not change from the first version as it still uses a sine wave oscillator to generate the input signal, convert the current flowing through the probe using a current-to-voltage transformer and then extracts the RMS value of this current with a measuring bandwidth of max ~10 Hz. The differences in the upgraded version come from a reference channel which takes the raw sine wave, amplifies it and extract its RMS value as well as a high-dynamic range current-to-voltage converter. The current-to-voltage circuit has three different resistors (1.8 kΩ, 18 kΩ and 180 kΩ) to switch the ranges of conductivity using a dynamic, electronic, switch. Although this does not offer a high number of digits for the reading (it still uses the 10 bits ADC from the Arduino), it allows reading from 5 mg/l NaCl to about 3 g/l NaCl. To increase even further the concentration range towards the more concentrated solutions, one last electronic switch allows to divide the amplitude of the input sine wave by 10. This was required (instead of using a 180 Ω resistor in the current-to-voltage stage) because the maximum allowable current with the selected operational amplifier is about 5 mA. While this resistor set was satisfying for the tests I performed, I would now consider using slightly different values and add one more resistor to the current-to-voltage converter to increase a bit more the more diluted range. I will modify this later.

The upgraded circuit principle is shown in Figure 1.

Figure 2 - Upgraded circuit concept

When comparing to the former version, I also changed the frequency from ~12 kHz to about ~7 kHz now. This is based on an analysis of the probe response at various NaCl concentrations, as shown in Figure 3. For low concentrations, the 10-20 kHz range is still fine, but the response drops pretty quickly at higher concentrations. The optimal separation seems to occurs around 7 kHz which is therefore the motivation for the new carrier frequency.

Figure 3 - Probe transfer function at various NaCl concentrations

The circuit was finally tested on dilute NaCl solutions using a methodology similar to the one used in my previous post. A mother solution of 100 g/l NaCl was prepared and successively diluted until it reached 0.4 mg/l (19 dilutions of 1:2 ratio were required). The conductivity (raw units) was measured for each solution and a power-law model was then fitted on the data. Finally, the conductivity of a calibrated 5,000 µS/cm solution was measured to have a translation between the raw units of the circuit and the µS/cm values.

The conductivity plot for the various NaCl concentrations is shown in Figure 4. It shall be read with care because only one single calibrated solution was used. Also, the low-end values are probably erroneous because of the conductivity of the demineralized water used which may have influenced the results (the solutions were not diluted using zero conductivity water but with water having a conductivity around 12 µS/cm). Nonetheless, the results were compared to NaCl conductivity figures found in the literature giving 2% error at 5 g/l, 14% error at 10 g/l and 22% error at 20 g/l. The interesting part is when I compare the experimental values to the model because I get a 4% difference at 6 g/l, a 17% difference at 12,5 g/l and a 29% difference at 25 g/l which is quite similar to the error I have with the literature. It is therefore possible that the model I used is not as good as it should and that the experimental values are more accurate than what you can see on Figure 4. I did not find values to compare for the more diluted solutions but it can be expected that they should be accurate as well. For instance, tap water gave around 800 µS/cm which is well in line with the authorized value from the water supply company here.

Figure 4 - Conducitivity of dilute NaCl solutions

Before concluding, I would now like to show how powerful the circuit presented here can be.

At the moment, we essentially used the conductometer to measure values of diluted NaCl solutions to analyse sea water, tap water, demineralized water content and so on. But conductometry is far more than that. One of the great usage of conductivity measurements is to track the end-point of titration or to detect the output of a chromatographic column. I will illustrate here the former case (titration). Figure 5 shows an oxalic acid solution (C2H2O4.2H2O) being titrated with a potassium carbonate solution (K2CO3). This is a classical acid-base titration (although usually we would use the oxalic acid as titrant because it is classified as a standard titrant since it is very stable in time, at contrario of the potassium carbonate – but this is not important here).

Figure 5 - Conductometric identification of a titration equivalence point

On Figure 5, the data point has been taken every 1 ml of titrant injected in the solution. The data spread into two different areas. On the left, there is oxalic acid left in the solution but the conductivity drops as it becomes neutralized progressively by the potassium carbonate. On the right, all the oxalic acid has been neutralized and the conductivity start increasing again as we add more potassium carbonate to the solution. By fitting lines in both domains and looking at the intersection, we can deduce with great precision the point at which the equivalence happens. This is known as the titration end-point and it allows to find the precise concentration of the oxalic acid solution if the titrant (potassium carbonate solution) concentration is known with precision.

If you have ever had chemistry lab, you probably have done titration but with a coloured indicator such as phenolphtaleine. If so, you may remember how tedious titrations are. Nobody ever read the indicator colour the same and it is very easy to miss the titration end-point. Also, coloured indicators are often biased because you need to add more titrant than required for the equivalence to make the colour change. All of this is solved with the conductometric titration because it relies on a quantitative measurement that does not require the appreciation of an operator and it is also unbiased as the apex occurs at the exact equivalence point and no excess titrant is required.

But this is enough for the perspectives and I will try to discuss this more in depth in a new post. Let us now dig into the construction of the circuit itself.

You may download the Gerber files of the circuit [∞] here, the bill of material [∞] here and the Gerber files of the probe [∞] here. All resistors are 1% and capacitors are 5%. When ordering the PCBs, be sure to ask for a gold-plating on the probe PCB. This will help the probe to last longer. Also, think about cleaning the probe after your experiments using demineralized water and wipe it off with a clean tissue after. I would also recommend insulating the soldered wire connections using a large amount of epoxy glue to prevent unwanted metallic electrolytic couples to develop in the solution.

Once you have the conductivity shield ready, you will need to assemble it to an Arduino Uno R3 board and the Adafruit LCD Shield. You can use a different output shield to visualize the output or you can also discard this last shield if you are only interested in recording the data from the serial port of the Arduino. One nice improvement in that case would be to connect the Arduino stack to a WiFi shield or the XBee wireless shield to broadcast the data over the air. Please note that you will need to power it using an external 12V power supply as it will not work using the 5V USB connector.

You will now have to program the Arduino with the driving program. The code is not very complex and most of it is in the loop function:

#include <Wire.h> #include <Adafruit_RGBLCDShield.h> #include <utility/Adafruit_MCP23017.h> Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield(); const float g_fCalibration = 6.188118812f; unsigned char g_ucMode = 1; void setModePins(void) { lcd.setCursor(0, 1); switch(g_ucMode) { case 0: digitalWrite(7, 1); digitalWrite(6, 1); digitalWrite(5, 0); digitalWrite(4, 0); lcd.print("Low Amp. Mode "); break; // mode 1 is also default mode case 1: default: digitalWrite(7, 0); digitalWrite(6, 1); digitalWrite(5, 0); digitalWrite(4, 0); lcd.print("1.8k ref "); break; case 2: digitalWrite(7, 0); digitalWrite(6, 0); digitalWrite(5, 1); digitalWrite(4, 0); lcd.print("18k ref "); break; case 3: digitalWrite(7, 0); digitalWrite(6, 0); digitalWrite(5, 0); digitalWrite(4, 1); lcd.print("180k ref "); break; } } float readVoltage(void) { return ((float)analogRead(0)) / 1023.0f; } float scaleVoltage(float fVoltage) { switch(g_ucMode) { case 0: return fVoltage * 9.33333f; case 1: return fVoltage; case 2: return fVoltage * 0.1f; case 3: return fVoltage * 0.01f; } return 0.0f; } void setup() { Serial.begin(19200); lcd.begin(16, 2); lcd.setBacklight(0x7); pinMode(7, OUTPUT); pinMode(6, OUTPUT); pinMode(5, OUTPUT); pinMode(4, OUTPUT); setModePins(); analogReference(EXTERNAL); } void printFloatDigit(float fValue) { char ret = ' '; if(fValue >= 9.0f) ret = '9'; else if(fValue >= 8.0f) ret = '8'; else if(fValue >= 7.0f) ret = '7'; else if(fValue >= 6.0f) ret = '6'; else if(fValue >= 5.0f) ret = '5'; else if(fValue >= 4.0f) ret = '4'; else if(fValue >= 3.0f) ret = '3'; else if(fValue >= 2.0f) ret = '2'; else if(fValue >= 1.0f) ret = '1'; else ret = '0'; lcd.print(ret); Serial.print(ret); } void printFloat(float fValue, float fStd, int iDigits=16) { // horrible hack fValue = fValue + fStd * 0.5; bool bDecimalPrinted = false; float fDivider = 1; if(fValue >= 10000.0f) fDivider = 10000.0f; else if(fValue >= 1000.0f) fDivider = 1000.0f; else if(fValue >= 100.0f) fDivider = 100.0f; else if(fValue >= 10.0f) fDivider = 10.0f; else if(fValue >= 1.0f) fDivider = 1.0f; else { fDivider = 0.1f; lcd.print('0'); Serial.print('0'); } while(fValue >= fStd && iDigits > 0) { if(fValue < 1.0f && !bDecimalPrinted) { bDecimalPrinted = true; lcd.print('.'); Serial.print('.'); } printFloatDigit(fValue / fDivider); while(fValue >= fDivider) fValue -= fDivider; fDivider *= 0.1f; iDigits --; } Serial.print('\n'); if(g_fCalibration != 0) { lcd.print(" mS/cm"); Serial.print(" mS/cm"); iDigits -= 6; } while(iDigits > 0) { lcd.print(' '); iDigits --; } } void loop() { static float fAnalogAccumulator = 0; static float fAnalogAccumulatorStd = 0; static float fAnalogAccumulatorCounter = 0; static unsigned long ulNextAction = 0; unsigned long ulTime = millis(); float fVoltage = readVoltage(); fAnalogAccumulator += fVoltage; fAnalogAccumulatorStd += fVoltage * fVoltage; fAnalogAccumulatorCounter ++; if(ulTime < ulNextAction) return; ulNextAction = ulTime + 250; float fCurrentVoltage = fAnalogAccumulator / fAnalogAccumulatorCounter; float fCurrentVoltageStd = sqrt((fAnalogAccumulatorStd / fAnalogAccumulatorCounter) - fCurrentVoltage * fCurrentVoltage); fAnalogAccumulator = 0; fAnalogAccumulatorStd = 0; fAnalogAccumulatorCounter = 0; if(fCurrentVoltage >= 0.95f) { if(g_ucMode > 0) { g_ucMode --; setModePins(); } else if(fCurrentVoltage >= 0.99f) { lcd.setCursor(0,0); lcd.print("Signal too high "); } return; } if(fCurrentVoltage <= 0.08f) { if(g_ucMode < 3) { g_ucMode ++; setModePins(); } else if(fCurrentVoltage < 0.01f) { lcd.setCursor(0,0); lcd.print("Signal too low "); } return; } fCurrentVoltage = scaleVoltage(fCurrentVoltage); fCurrentVoltageStd = scaleVoltage(fCurrentVoltageStd); lcd.setCursor(0, 0); if(g_fCalibration != 0) printFloat(g_fCalibration * fCurrentVoltage, g_fCalibration * scaleVoltage(0.001f)); else printFloat(fCurrentVoltage, scaleVoltage(0.001f)); }

The program will accumulate analog readings for 250 ms where some processing will then occur. If the analog value is too low (or too high), the current-to-voltage resistor is changed to switch the reading range. The value is then printed on both the LCD shield and the serial port. You can also change the const value at the beginning of the program to change the conductivity calibration if you have a standard solution to test the board. If you want to plot the raw units, just set it to zero.

And that is all! I hope you will enjoy this circuit and stay tuned for updates.

[⇈] Top of Page

You may also like:

[»] DIY Conductometry

[»] Measuring Turbidity

[»] In-line Absorption and Fluorescence Sensor

[»] Sine Wave Oscillator with Fewer Op-Amp

[»] 1 Amp Power LED driver with Modulation Input