Show by Label

Thursday, September 29, 2016

Building a 6 Digit Milli-Volt Meter

There are a large number of updates in the software, scroll down to see them.


As I mentioned in one of my other posts about the Kelvin-Varley Divider, I wanted to have a higher resolution voltmeter than I currently have. I happened to stumble on a nice set of Youtube videos from Louis Scully from Scullcom Electronics. He has described a set of very nice instruments, one of them is a 6.5 digit milli-Volt meter.

One usual caveat! The links I provide may at times no longer work, so my apologies in advance if you land in the 404 land of no returns.

Here are the links to the videos, now up to 4 in total.

Part 1 of the 6.5 digit Voltmeter
Part 2
Part 3
Part 4
Mk2

The design is quite simple and should be able to be built if you have a bit more than average skills.
The good news is that a follower of this design, Greg, has provided a PCB through OSH Park that greatly enhances the input section of the DVM, which is the Achilles heel of such a project.

Here is the website that details the implementation using that PCB:
Barbouri Millivolt-Meter Project

I will use that PCB, but I have a few changes in mind for the version I am going to built and I'll go through these elements here.

The front end of the design is the most critical. I'll implement that by following the PCB design. For the processor part, I originally wanted to use the same Arduino Nano that Louis Scully is using, but since the PCB layout is for an Arduino Pro Mini I'll use that. However, I have no need to drive a multi-color back light LCD, and I also do not foresee any other enhancements that will eat up the Arduino ports, so I see no need to use the Display42 PCB from Greg with the I2C MCP23017-E/SP chip.

To cut down on the amount of wires going from the Arduino the the LCD, there are 6, I am using a module that is available on eBay for a very small price : LCD Interface

The LCD that I'll end up using will be this one :
16 x 2 White on Blue LCD












My design will also power the unit from a battery, to avoid grounding issues and provide a clean supply to begin with. However, rather than using normal batteries, I will use a  rechargeable cell, and I want to provide a way to charge them while using the instrument, and also when not in use. To keep taps on the voltage level or discharge level while not on mains, I need a monitor that will warn me when the voltage is getting too low.

Here is the circuit that I use to implement the button debounce, and the power section.




One word of caution before I dive in. The DC chassis part is not quite like the Eagle symbol I used in this diagram. There is no short between the plus and minus when there is no plug inserted! The minus is however isolated from the chassis when there is no plug inserted, keeping everything isolated from the chassis. If the plug is inserted, there is an indication on the LCD, so you won't forget.

You'll notice that I deviated from the design Louis used for the two buttons. I have quite some experience now interfacing buttons if you have followed my Raspberry Pi posts. The processor of the Pi runs at 900MHz or more (yes, no typo), and interfacing with something as slow as a button has its challenges. Especially for inexpensive buttons. You'll be amazed how noisy they can be.

In any case, the filtering that I use to get rid of most switch bounce noise is by using an R/C filter on both edges (closing and opening). I always prefer to use active high buttons or switches because they avoid all sorts of power-on problems. When the switch/button is open, the capacitor is at ground level. Closing the contact will charge the capacitor through the 10K series resistor, creating a nice and clean rising edge (R/C) towards the input of the Arduino. Releasing the switch will cause the capacitor to discharge through the 10K series resistor plus the 1K to ground, again creating a nice R/C slope that will filter the high speed bounce noise. In software we can use a little delay to get rid of the slower bounce transitions, and together this will create clean signals to the Arduino program without having to resort to Schmidt-trigger gates or Flip-Flops.

One word of caution. Don't make the debounce capacitor much larger than 10nF. If you don't have 10nF, you can go as low as 1nF. The reason for this is that the slower the R/C slope, the more time the signal stays in the undefined area between digital "high's" and "low's" and that can cause glitches for the processor again.  If I peaked your button interest, have a look here : Debouncing buttons There is a lot more to buttons than you may think.

The power design is quite simple, and I've used that before. The two Schottkey (low drop) diodes D2 (this diode can actually be a 1N400X type) and D3 will decide which supply is feeding the Voltmeter. If the mains is connected (providing the 15-30V DC), D2 will have the higher voltage so it will win. If there is no mains connection, the battery supplies the voltage. The resistor (R1) in parallel of D3 determines the (re)charge current of the Ni-CAD cell. The charging current is about 0.1 x C for a 250mAh cell. Depending on the capacity of the cell(s) you're using you may need to change the value of R1 so it is within the (re)charging specification of the cell(s).

In order to keep an eye on the charge level of the cell, I have added a few parts to allow the Arduino to measure the voltage level. You don't want to run into a situation where the voltage is too low, and you will introduce errors in the measurements. Besides, you don't wat to be caught with an empty battery when you're in the middle of something. R2 and R4 create a 3:1 voltage divider with easy to find resistors. You can create 20K with 2 x 10K in series. (don't use less than 10K for R4, or it will negatively influence the ADC conversion) C4 is a small filter to get rid of noise and the output goes to one of the Arduino ADC inputs. The rest is done in software and I have also designed some battery level symbols to make it look nice.

The complete multi-meter draws less than 60 mA. About 26mA of that is used by the LCD display. It is safe to use the L part for the 12V regulator, and even for the 5V regulator on the PCB.

If you are already an Arduino user, you may have the Mini Pro, and you also may have the required programming cable. If not, here is a source that provides both as a bundle :
Arduino Pro Mini with interface

Here is a picture of the small interface board that will turn the LCD module into an i2c capable interface, reducing the number of wires, and to stay compatible with the PCB.



You need to install a new LCD library to get the i2c driver, and I selected the library from here :
i2c / LCD library

This library is tailored for a particular interface board, the FaBo #212 LCD I2C Brick, but the only difference is the i2c address with the board I have.

First of all, you need to know the i2c address of your board.
I used a little sketch to do that:
i2c address scanner

My address turned out to be 0x27, while the FaBo brick uses 0x20.
After you have installed the new i2c-LCD library on your system, you need to edit the  FaBoLCD_PCF8574.h file that is in the library source section, and change this line :

#define PCF8574_SLAVE_ADDRESS 0x27 ///< PCF8574 Default I2C Slave Address = 20


Here is a picture of the finished project. I actually build two units, because one can never have enough voltmeters. My design and the changes I made allows me to position these meters very close to my prototypes, and without and power wires attached. I can also make floating measurements, because nothing is connected to the housing. (the DC socket inputs are isolated from the chassis if no power plug is inserted)



Below is a copy of the Arduino sketch. There are many changes to the original code, so have a good look at what has been changed in case you use different hardware.

I have been playing with the two units, to see what the accuracy is and what I could change in the user interface.

I must say that I am very impressed with the accuracy! I have two calibrated voltage reference units, and also a new/freshly factory calibrated 4.5 digit bench multi-meter. The accuracy and precision of this design is astounding for such a simple and inexpensive tool.


I was a little apprehensive about feeding the LCD display with the same 5V supply as the rest of the logic. These displays are notorious for introducing spikes and noise, so I was on guard for trouble.

When I got my scope attached, I was not surprised by the noise I found, so I started with decoupling the 5V power where it enters the display module. I used a Tantalum 3.3uF together with a 100nF to begin with, because the i2c and the LCD do not have any decoupling.
Here is how that looks:

Unfortunately, this did not reduce the nasty spikes on the reference voltage and the main 5V by much. Looking into it some more, I found that the switching of the LTC_CS line to start/stop the AD conversion cycle turned out to be the culprit. Here is a screenshot:


The top trace (A) is the LTC_CS signal coming from D10 at the Arduino PCB. The bottom trace (B) is the 4.096V reference, AC coupled. The spikes are clearly caused by the switching at the digital port. They are a few nSec in width, so I selected a 4n7F capacitor that slowed the edge down enough to not cause a spike anymore. I mounted that capacitor on the PCB of the Arduino, with one leg soldered to D10 and the other end to the unused GND mounting hole just next to it.:


And this is the result:


I noticed a potential bug in the code, related to the averaging of the results. The Spi_Read function discards a reading of the ADC if it's not ready, but the main loop code counts it as a valid sample, which could result in wrong measurements. I have fixed the code below, but I could not find instances of this error when I looked for it with a Logic Analyzer.

While I had that out, I looked at the timing in more details, to see if there were any potential conflicts.

First of all, this is a picture of the ADC sampling window:

Here you can see that the CS is going down to start the cycle and the MISO is ready 1.25uS later, virtually at the same time. There is 1.5uS between that event and the first clock going high. This is after I already eliminated the little delay in the original Spi_Read code. It is not needed. You can see here the four data bytes getting read, and the actual data presented on the MISO. Note that at the third clock, we are reading the third status bit (SIG), and that indicates a V-in signal > 0. The data towards the end is the "real" 28 bit data, of which the last 4 are the "extra" sub LSB's and they are discarded in the main loop after the averaging. (look at the datasheet for details)

I selected 8 samples to be averaged in my code, and then prepare the result to go to the LCD. As I mentioned earlier, these LCD's are very noisy. In our case, this has no real influence, because the LTC2400 is put to sleep after we have read the data, and made the CS pin high again, as you can see above.

Here is a screen shot that shows the end of the data (channel 5) going through the  I2C bus to the LCD, and the beginning of another acquisition cycle:

You can see that we have a "quiet" period after the LCD got updated, and the start of a new ADC acquisition, which is 0.129 mSec (T1-T2). The total loop time, from LCD update to LCD update is 1.5 Seconds in my case. Sending the results to the LCD only takes about 36 mSec.

Here is a picture of a complete loop:

The "dead" time, averaging the results and sending it to the LCD is only 0.22 Seconds.

After playing with the meter, I got more and more dissatisfied with the flopping around of the last 3 digits, even when I has a stable voltage reference connected to it.

At first I played around with the averaging, but that is really no solution for a system that has 24 bit resolution. The reason is the inherent amount of noise when you are down to the micro-Volt level. Below is a sample of my 2.5V reference, using my non-calibrated voltmeter (the reference is calibrated as having 2.49993V)



As you can see, there is quite a lot of noise.
Averaging does not have as much influence as you may think. Here is a picture of a running average of 8 samples:







Still not good enough. I then looked at smoothing, but that was not good enough either, so I thurned to filtering. I tried a few approaches, and then really investigated an Infinite Input Response (IIR) filter design. And that showed a lot of promise:


This filter is based on the "weighing" of the new samples, based on a division. The divisor is fixed and above I used a factor of 64. This means that a new sample only contributes to 1/64th of the value to the averaged total. This is great when you have a stable signal, but what if the input voltage changes?
You could of course reduce the weighing factor, and here is one example with factor 4.


An order of magnitude better then averaging. But I was not satisfied yet. I then looked at resetting the averaged result if the sample was significantly different enough from the averaged result. I used a 5 sample input filter to avoid spikes resetting the filter, and that worked very well. If a new voltage was applied, it only took 5 cycles of 0.165 Sec. to switch to the new input.



I have just about zero experience with filters, and this extra code I designed myself, but I was convinced that there are better methods available. I eventually found the Kalman filter, which is used a lot, and seemed perfect for my application.

Knowing nothing about it, I searched and found a very nice tutorial on Youtube that explains the Kalman filter very very well, even for total Dummies like myself. (look for Michel van Biezen - Special Topics - The Kalman Filter) I wrote a simplistic version of the filter based on his explanation but was not satisfied with the result. I also worked with another example I found, but that had the same problem. Neither of them worked with relatively quick input changes, as an example when switching from 2.5V to 5.0V. They both took several seconds to show the new value. Bummer!

So Kalman looked great on paper and in simulation (using Excel), but in reality, using my Volt Meter, it was a lot worse than the IIR filter I already used. However, I stole a concept from the Kalman filter, namely the Gain calculation. This is a dynamically calculated weighing factor, so I wrote some code around my IIR filter that accomplished what I wanted. Details are in the code. The result is fantastic, I think!

If I now connect the meter to a really stable voltage, like from a reference, 5 decimal digits are rock solid with only the 6th flopping around due to the noise. When I switch from one reference voltage to another one, within a few cycles, the voltage is updated and within a second or so the 5 digits are rock solid again.

When I tested the ability to set a voltage manually with a power supply (one of those I built in the other Forum posts), I was amazed how well the response to my tuning and the accuracy was, but also I saw how noisy my power supplies turned out to be. That's what you get when you are using a 24bit ADC with micro-voltage resolution. Oops!

In any case, with this filter in place, I also added a separate calibration function, to calibrate the Volt Meter to my Voltage Standard. I already use the (Zero) Null Volt calibration to null the input level, but I now also can tune the meter to my Voltage Reference.

The accuracy is now much better as well.

With all this done, I no longer need to reduce the number of decimal digits, so that code went into the bit-bucket. In the process of going through the filter designs, I also saw a way to get to a more stable and precise acquisition delay for the LTC2400. The delay is now dynamically calculated.

Because I needed to store the calibration factor for the voltage reference, I needed to store a floating point number in the EEPROM. Turns out, the library we already used has that feature, so I could clean-up the code, and send two more functions into the bit-bucket.

My dual-button press now drives the zero calibration and the reference calibration.

With all these changes and loop tuning, the main loop time is now about 165mS, so the display is very responsive.

V3.11 update:
I found a bug in the filter calculation, based on a rounding error. This is caused by diving a long by a float, and the result going into a long again. The solution was to use a float as the result. The compounded rounding error cause the filter result to be a little bit below the raw averaged input level, as you can see in the filter graph above.
Here is a shot of the result after I fixed the code:


The difference between the filter result after 1000 samples, and the calculated median value in Excel is now very, very small.

I also added the filter weight exponent multiplier to the display.

V3.12 update:
Because the linearity of the meter was not as good as I had hoped, I created a way to measure the value of the ADR4540B chip, and updated that factor in the code. To measure that voltage with my still un-tweaked meter, I calibrated the meter with my 5V0 reference, to get as close as possible. In order to do that, I created a special cal functions for all my reference voltages. Now you can select any of them, just update the cal factors.

To do this calibration, I let my 2 meters and the reference warm-up. Then I did a fresh null calibration, and a 5V0 calibration. I then measured the ADR reference voltage for a few minutes to get the best possible filtered voltage. I had already added a pin on my PCB to do that easily. This voltage was then used to update the constant in the code.  I did the same for my other meter. After updating the v-ref constant, I did the measurement again to verify and tweak if required. Finally, I did the ref voltage calibration again. After trying all 4 reference voltages, I had the best linearity result with the 10V reference.

After this calibration the linearity is even better.
Not surprisingly, the 10V is spot on :

Reference Voltage     Measured Voltage     Delta                   %
2.49993 V                  2.49953 V                  -400uV           -0.016%
5.00181 V                  5.001515 V                -295uV          +0.059%
7.50547 V                  7.50534 V                 -130uV           +0.0017%
10.00673 V                10.00672 V                -10uV            +0.00001%

That is good enough for me. If I have e need to really measure in the sub-volt range, I can still do the 2.5V cal.

The LTC chip can actually measure an input range of +/- 12.5% of the reference voltage, so what you can do with this feature is to null the meter with a voltage you want to monitor, say a 2.5V reference, and you can then measure the drift with uV resolution over time. (this includes the drift of the meter itself as well of course, but still)

I am sure you will enjoy this tool!

Have fun!


Here is Version 3.12 of my code:

/* SCULLCOM HOBBY ELECTRONICS
 * MILLIVOLT METER USING LTC2400 24bit ADC CHIP
 * Using the PCB designed by Greg
 * http://www.barbouri.com/2016/05/26/millivolt-meter/
 *
 *  Changes by dbldutch:
 *  - added code to display the sw version in the welcome screens
 *  - added code to display a real micro symbol instead of uV
 *  - added code to use an i2c LCD display
 *  - added code to go from two buttons to one with a dual function
 *  - added code to monitor the charging level of a NiCAD/NiMH cell
 *  - changed code to display decimal digits based on volts measured.
 *  - changed the way the button press is used and displayed
 *  - Version 2.00:
 *  - stripped the 4LSB in the Read_ADC, before averaging. They add no value here.
 *    Calibration uses a larger sample size. Added a loop counter to show progress.
 *  - Version 3.00 :
 *  - Changed from average based sampling to an Infinite Input Response (IIR) filter design.
 *    Details found here: https://github.com/arduino/Arduino/issues/3934
 *    and here : https://en.m.wikipedia.org/wiki/Infinite_impulse_response
 *  - Created a dynamic ADC conversion delay for the LTC2400 to wring out some idling time.
 *  - Changed to EEPROM.put & .get to also store floats. Eliminated the previous functions.
 *  - With the IIR filtering, there is no need for reduced decimals. Eliminated the code.
 *  - Added a calibration to a voltage reference to tweak the accuracy.
 *  - Changed the dual button press code to cover the zero cal and the v-ref cal functionality.
 *  - Version 3.10:
 *  - Added a dynamic filter weight algorithm to the IIR filter.
 *  - Version 3.11:
 *  - Changed the conversion delay to (re)start at the end of the LCD update cycle, such that the LTC
 *    has 165 mSec of "quiet" time on the power and data lines to do the sampling.
 *  - Fixed a compounded rounding error of the IIR filter calculation.
 *  - Added the filter weight exponent multiplier to the display.
 *  - Version 3.12:
 *  - Added some tweaks and changes to allow a precise calibration of the reference voltage. This
 *    largely determines the linearity of the meter.
 *
 *   Software version:
 */
String SW_VERSION = " Version 3.12";
/*
 * LCD interface using the i2c bus:
 * http://fabo.io/212.html
 * https://github.com/FaBoPlatform/FaBoLCD-PCF8574-Library
 *
 *  LCD Connections:
 *  - SDA to A4 to analog pin 27
 *  - SCL to A5 to analog pin 28
 *
 * LTC2400 A2D convertor connections using SPI bus:
 *  - SCK to digital pin 13
 *  - SDO to digital pin 12
 *  - CS  to digital pin 10
 * 
 * Button:
 * - button to digital pin 3
 *
 * Battery indicator level:
 * - Analog pin A0 (via a 20K:10K divider) [10K is minimum value for the A2D]
 */
#include <SPI.h>                         // include SPI library (Serial Peripheral Interface)
#include <Wire.h>
#include <FaBoLCD_PCF8574.h>             // include the i2c bus interface and LCD driver code
#include <EEPROM.h>                      // include EEPROM library

//---- initialize the i2c/LCD library
FaBoLCD_PCF8574 lcd;                     // with this, there are no further code changes writing to the LCD

//---- Button and button debounce
const int button = 2;                    // button is connected to D2, between VCC and with a 1K to ground.
                                         // a press grounds the pin (makes it low/false), is otherwise true/high
unsigned long buttonTimer = 0;           // timer to differentiate between a long and a short button press
long longPressTime = 2000;               // 2 seconds + max 800mS overhead
unsigned long last_millis = 0;           // always use unsigned long with millis() to avoid roll-over issues
boolean buttonActive = false;
boolean longPressActive = false;

//---- LT 2400 ADC convertor
const int LTC_CS = 10;                   // set ADC chip select pin !CS (!SS) to Arduino pin D10
                                         // SPI SLCK is D13, SDO (MISO) is D12
long adcread;                            // reading from the ADC (LTC2400)
int ct = 165;                            // ADC converstion time is 160mS typical, +/-3.2 mS (data sheet)
unsigned long ct_start;                  // seed the conversion start timestamp
unsigned long ct_chk;                    // the entry timestamp, used to dynamically create the delay
float volt;                              // voltage reading from ADC
                                         // Following measurements were made after a warm up, a null calibration, followed by
                                         // a calibration with a 5V0 Ref, loading the new value and doing the measurement again.
                                         // This is a critical measurement because it determines the linearity of the meter
float v_ref = 4.09553;                   // ADR4540B Reference Voltage from the A meter
//float v_ref = 4.09588;                   // ADR4540B Reference Voltage from the B meter

//---- IIR Low Pass Filtering with a dynamic filter weight algorithm
float average;                            // holds the result of the filter
int filterWeight = 64;                   // from 4..64 Higher numbers = heavier filtering
int fw_multiplier = 1;                   // multiplier for the filterWeight calculation
int noise_level = 96;                    // 96 is +/- 234 uV input voltage differential

//---- Zero offset Calibration
int CalSetup = 0;                        // calibration check
int DecPlaces = 0;                       // limit to the number of decimal places on display
long zero_cal;                           // calibration adjustment factor
long zc_address = 0L;                    // set EEPROM memory start address location 0..3
const int cal_adj_samples = 75;          // number of samples

//---- Calibration Factor
float cal_factor = 1.0;                  // calibration factor to adjust for the linearity errors
float cal_avg;                           // the resulting value of the filter
float cal_2_5v_ref = 2.49993;            // calibrated values from my HaoQiXin Voltage Reference
float cal_5v_ref = 5.00181;              //
float cal_7_5v_ref = 7.50547;            //
float cal_10v_ref = 10.00673;            //
long cf_address = 5L;                    // EEPROM address 5..8

//---- Display the result to the LCD
String v;                                // string to hold the V, mV or uV string data for the display
String micro;                            // string to hold the real micro character
int dec_adj = 0;                         // decimal places adjustment [0..6], set manually by the button
int dec_digit = 6;                       // integer that holds number of decimal places displayed on LCD
int dTV = 5;                             // default is 10.00000 V for 10 Volt and above
int dV = 6;                              // default is 1.000000 V for 1-9 Volt range
int dmV = 4;                             // default is 100,0000 mV for MilliVolt range
int duV = 0;                             // default is 1000000 uV for MicroVolt range

//---- Arduino ADC Battery check
int batt = A0;                           // the 9V NiMH or NiCAD battery
int check_v = 0;                         // check the battery voltage
int loop_cnt = 365;                      // check it approx. every minute (loop time is 0.165 Sec)
float adc_cal = 0.0;                     // ADC calibration value
float adc_ref_volts = 5.14;              // external volt reference (VCC) -> measured
float adc_res = 1024;                    // 10 bit ADC resolution
const int adc_samples = 4;               // set number of sample readings to average the result

// Create a set of new symbols that can be displayed on the LCD
byte batt_full[8] =                      // full battery symbol, displayed at 9V
  {
  B01110,B11111,B11111,B11111,B11111,B11111,B11111,B11111
  };
byte batt_8_7[8] =                       // 8.7V level
  {
  B00000,B01110,B11111,B11111,B11111,B11111,B11111,B11111
  };
byte batt_8_3[8] =                       // 8.3V level
  {
  B00000,B00000,B01110,B11111,B11111,B11111,B11111,B11111
  };
byte batt_8_0[8] =                       // 8.0V level
  {
  B00000,B00000,B00000,B01110,B11111,B11111,B11111,B11111
  };
byte batt_7_7[8] =                       // 7.7V level
  {
  B00000,B00000,B00000,B00000,B01110,B11111,B11111,B11111
  };
byte batt_7_5[8] =                       // 7.5V level
  {
  B00000,B00000,B00000,B00000,B00000,B01110,B11111,B11111
  };
byte batt_empty[8] =                     // <7.3V Empty
  {
  B00100,B00100,B00100,B00000,B00100,B00000,B01110,B11111
  };
byte batt_charging[8] =                  // >10V Charging symbol
  {
  B00010,B00100,B01000,B11111,B00010,B10100,B11000,B11100
  };


/**************************************************************************************
 * Initialization routine, runs only at start or reboot
 */
void setup() {
  // Serial.begin(9600); // ==>> activate for debug and test only

  micro = char(228);                     // real micro symbol is char(b11100100)

  pinMode(button, INPUT);                // set the pin connected to the button to input
  digitalRead(button);                   // get rid of first unreliable input

  pinMode (LTC_CS, OUTPUT);              // set LTC_CS (pin D10 on Arduino Nano) to OUTPUT mode
  digitalWrite(LTC_CS, HIGH);            // set LCT2400 chip select pin HIGH to disable  // initialize digital pin LED_BUILTIN as an output.

  SPI.begin();                           // initialise SPI bus
  SPI.setBitOrder(MSBFIRST);             // Sets the order of bits shifted out and in to SPI bus, MSBFIRST (most-significant bit first)
  SPI.setDataMode(SPI_MODE0);            // set SPI to Mode 0 (MOSI read on rising edge (CPLI=0) and SCK idle low (CPOL=0))
  SPI.setClockDivider(SPI_CLOCK_DIV16);  // divide Arduino clock by 16 to gave a 1 MHz SPI clock

  lcd.begin(16, 2);                      // set up the LCD's number of columns and rows
  lcd.setCursor(0,0);                    // set LCD cursor to column 0, row O (start of first line)
  lcd.print("Milli-Volt Meter");
  lcd.setCursor(0,1);
  lcd.print(SW_VERSION);                 // print software version to display
  delay(2000);
  lcd.clear();                           // clear dislay
  lcd.setCursor(0,0);                    // set LCD cursor to column 0, row O (start of first line)

//  ==>> only for testing!
/*
  zero_cal = 0;                          // Start with a clean slate
  Serial.println(zero_cal);
  EEPROM.put(zc_address,zero_cal);       // store calibration factor in EEPROM
  EEPROM.get(zc_address,zero_cal);       // retrieve calibration factor in EEPROM
  Serial.println(zero_cal);
  cal_factor = 1.0;
  Serial.println(cal_factor, 6);
  EEPROM.put(cf_address,cal_factor);     // store calibration factor in EEPROM
  EEPROM.get(cf_address,cal_factor);
  Serial.println(cal_factor, 6);
*/

  EEPROM.get(zc_address, zero_cal);      // get the zero cal factor from EEPROM
  EEPROM.get(cf_address, cal_factor);    // get the cal factor from EEPROM

  lcd.clear();                           // clear dislay
  lcd.setCursor(0,0);                    // set LCD cursor to column 0, row O (start of first line)
  lcd.print("Zero Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(zero_cal);                   // Briefly show calibration factor stored in EEPROM at switch on
  delay(2000);
  lcd.clear();                           // clear dislay
  lcd.setCursor(0,0);                    // set LCD cursor to column 0, row O (start of first line)
  lcd.print("V-Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(cal_factor, 6);              // Briefly show calibration factor stored in EEPROM at switch on
  delay(2000);
  lcd.clear();
  lcd.setCursor(0,0);                    // set LCD cursor to column 0, row O (start of first line)
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
 
  // create a set of special symbols from the battery monitor definitions above
  lcd.createChar (0, batt_full);         // >9V
  lcd.createChar (1, batt_8_7);          // 8.7V
  lcd.createChar (2, batt_8_3);          // 8.3V
  lcd.createChar (3, batt_8_0);          // 8.0V
  lcd.createChar (4, batt_7_7);          // 7.7V
  lcd.createChar (5, batt_7_5);          // 7.5V
  lcd.createChar (6, batt_empty);        // <7.3V measurements can be inaccurate!
  lcd.createChar (7, batt_charging);     // > 10V charging and connected to wall-wart
  analogReference(DEFAULT);              // not needed here, use the external or default (=internal) A2D reference

  Monitor_batt();                        // get the battery level and show it on the display
 
  ct_start = millis();                   // seed the conversion start timestamp for the LTC2400
  for (int i=0;i<5;i++) {                // disregard the first five readings as they seem unstable
    average = Spi_Read();                // and also seed the IIR filter
  }
}


/**************************************************************************************
 * Routine to read the data from the LTC2400 A2D convertor through the SPI interface
 */
long Spi_Read(void){                     // SPI(Serial Peripheral Interface) read sub-routine to read data form the LTC2400 ADC
                                         // and transfer 8 bits (1 byte) at a time - total of 4 bytes.
  long result = 0L;                      // result represents rolling total of the bytes transferred
  long b;                                // b is result of reading ADC output bytes

  //calculate the minimum conversion delay dynamically
  ct_chk = millis();
  unsigned int ct_delay = ct_chk - ct_start; // use the time already spent and factor that in
  if (ct_delay < ct){
    delay(ct - ct_delay);                // use the adjusted conversion delay if needed
  }
 
  digitalWrite(LTC_CS, LOW);             // LTC2400 chip select pin taken low to wake up the ADC and enable the SDO (MOSI) output
  delayMicroseconds(1);                  // timing delay but is not really required

  if (!(PINB & (1 << 4))) {              // check for a low !EOC on the MOSI pin D12, if the ADC is ready to transmit new data
                                         // if not, try again later -> this will reduce the number of readings to average
    b = SPI.transfer(0xff);              // transfer first byte most significant bits first.
    b &= 0x0f;                           // discard first 4 status bits (bits 31 to 25) mask received data with binary 00001111
    result = b;                          // result after removing first 4 bits (replacing them with 0's)
    result <<= 8;                        // shift first byte left by 8 places
    b = SPI.transfer(0xff);              // transfer second byte most significant bits first.
    result |= b;                         // add second byte to first byte by using the OR function (now 12 bits)
    result = result << 8;                // shift result left by 8 places
    b = SPI.transfer(0xff);              // transfer third byte most significant bits first.
    result |= b;                         // add third byte to result by using the OR function (now 20 bits)
    result = result << 8;                // shift result left by 8 places
    b = SPI.transfer(0xff);              // transfer fourth byte most significant bits first.
    result |= b;                         // add fourth byte to result by using the OR function (now 28 bits)
    result = result >> 4;                // get rid of the 4 LSB bits, they don't add any value in this application
 
    digitalWrite(LTC_CS, HIGH);          // LTC2400 chip enters low power (sleep mode) and disables the ADC output.
    ct_start = millis();                 // start the conversion delay timer (restarted at the end of the main loop)
    return(result);                      // return with result as the 24 bit data representing the voltage
  }
}


/**************************************************************************************
 * Routine to run a zero offset calibration.
 * We'll calculate the input offset by manually shortening the input.
 */
void Zero_Cal_Adjust() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("=Zero Calibrate");
  lcd.setCursor(0,1);
  lcd.print("Short input");
  delay(3000);

  int cal_counter = cal_adj_samples;     // total number of readings
  average = Spi_Read();                 // seed the IIR filter

  for (int i=0; i < cal_adj_samples; i++) {  // create a moving average with an IIR filter
    average = average + (Spi_Read() - average) / 64; // maximum filterweight
    // show the progress
    lcd.setCursor(14,1);
    if (cal_counter < 10){               // get rid of the first decimal number              
      lcd.print(" ");
    }
    lcd.print(cal_counter);
    cal_counter--;
  }
  zero_cal = round(average);            // round to the next integer value
  EEPROM.put(zc_address, zero_cal);     // store binary offset calibration factor in EEPROM
 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Zero Cal Adjust:");
  lcd.setCursor(0,1);
  lcd.print(zero_cal);
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
  DecPlaces = 0;                         // if decimal places got changed, reset it
  Monitor_batt();                        // the batt level display got erased, get it back
}


 /**************************************************************************************
 * Routine to run the voltage calibration against a voltage reference.
 * The calibration factor is creatad by connecting a 2.50 reference
 * to the input. This will enhance the linearity of the conversion.
 */
void Ref_Cal_Adjust2() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("=2.5V-Ref Cal");
  lcd.setCursor(0,1);
  lcd.print("Connect V-Ref");
  delay(3000);

  int cal_counter = cal_adj_samples;        // total number of readings
  cal_avg = Spi_Read();                     // seed the IIR filter

  for (int i=0; i < cal_adj_samples; i++) { //create a moving average with an IIR filter
    cal_avg = cal_avg + (Spi_Read() - cal_avg) / 64;  // maximum filter weight
    // show the progress
    lcd.setCursor(14,1);
    if (cal_counter < 10){                  //get rid of the first decimal number              
      lcd.print(" ");
    }
    lcd.print(cal_counter);
    cal_counter--;
  }
  // convert filtered result to volt and include the reference, the input divider
  // and the zero cal. The zero cal must be done before the reference cal.
  float ref_volt = (cal_avg - zero_cal) * v_ref / 16777216 * 10;
  cal_factor = (cal_2_5v_ref - ref_volt);  // absolute difference
  cal_factor = 1 + (cal_2_5v_ref - ref_volt)/cal_2_5v_ref; // multiplication cal factor per volt
  EEPROM.put(cf_address, cal_factor);    // store the calibration factor in EEPROM
 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("VRef Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(cal_factor, 6);
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
  DecPlaces = 0;                         // if decimal places got changed, reset it
  Monitor_batt();                        // the batt level display got erased, put it back
}


 /**************************************************************************************
 * Routine to run the voltage calibration against a voltage reference.
 * The calibration factor is creatad by connecting a 5.0V reference
 * to the input. This will enhance the linearity of the conversion.
 */
void Ref_Cal_Adjust5() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("=5.0V-Ref Cal");
  lcd.setCursor(0,1);
  lcd.print("Connect V-Ref");
  delay(3000);

  int cal_counter = cal_adj_samples;        // total number of readings
  cal_avg = Spi_Read();                     // seed the IIR filter

  for (int i=0; i < cal_adj_samples; i++) { //create a moving average with an IIR filter
    cal_avg = cal_avg + (Spi_Read() - cal_avg) / 64;  // maximum filter weight
    // show the progress
    lcd.setCursor(14,1);
    if (cal_counter < 10){                  //get rid of the first decimal number              
      lcd.print(" ");
    }
    lcd.print(cal_counter);
    cal_counter--;
  }
  // convert filtered result to volt and include the reference, the input divider
  // and the zero cal. The zero cal must be done before the reference cal.
  float ref_volt = (cal_avg - zero_cal) * v_ref / 16777216 * 10;
  cal_factor = (cal_5v_ref - ref_volt);  // absolute difference
  cal_factor = 1 + (cal_5v_ref - ref_volt)/cal_5v_ref; // multiplication cal factor per volt
  EEPROM.put(cf_address, cal_factor);    // store the calibration factor in EEPROM
 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("VRef Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(cal_factor, 6);
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
  DecPlaces = 0;                         // if decimal places got changed, reset it
  Monitor_batt();                        // the batt level display got erased, put it back
}

 /**************************************************************************************
 * Routine to run the voltage calibration against a voltage reference.
 * The calibration factor is creatad by connecting a 5.0V reference
 * to the input. This will enhance the linearity of the conversion.
 */
void Ref_Cal_Adjust7() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("=7.5V-Ref Cal");
  lcd.setCursor(0,1);
  lcd.print("Connect V-Ref");
  delay(3000);

  int cal_counter = cal_adj_samples;        // total number of readings
  cal_avg = Spi_Read();                     // seed the IIR filter

  for (int i=0; i < cal_adj_samples; i++) { //create a moving average with an IIR filter
    cal_avg = cal_avg + (Spi_Read() - cal_avg) / 64;  // maximum filter weight
    // show the progress
    lcd.setCursor(14,1);
    if (cal_counter < 10){                  //get rid of the first decimal number              
      lcd.print(" ");
    }
    lcd.print(cal_counter);
    cal_counter--;
  }
  // convert filtered result to volt and include the reference, the input divider
  // and the zero cal. The zero cal must be done before the reference cal.
  float ref_volt = (cal_avg - zero_cal) * v_ref / 16777216 * 10;
  cal_factor = (cal_7_5v_ref - ref_volt);  // absolute difference
  cal_factor = 1 + (cal_7_5v_ref - ref_volt)/cal_7_5v_ref; // multiplication cal factor per volt
  EEPROM.put(cf_address, cal_factor);    // store the calibration factor in EEPROM
 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("VRef Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(cal_factor, 6);
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
  DecPlaces = 0;                         // if decimal places got changed, reset it
  Monitor_batt();                        // the batt level display got erased, put it back
}

 /**************************************************************************************
 * Routine to run the voltage calibration against a voltage reference.
 * The calibration factor is creatad by connecting a 5.0V reference
 * to the input. This will enhance the linearity of the conversion.
 */
void Ref_Cal_Adjust10() {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("=10.0V-Ref Cal");
  lcd.setCursor(0,1);
  lcd.print("Connect V-Ref");
  delay(3000);

  int cal_counter = cal_adj_samples;        // total number of readings
  cal_avg = Spi_Read();                     // seed the IIR filter

  for (int i=0; i < cal_adj_samples; i++) { //create a moving average with an IIR filter
    cal_avg = cal_avg + (Spi_Read() - cal_avg) / 64;  // maximum filter weight
    // show the progress
    lcd.setCursor(14,1);
    if (cal_counter < 10){                  //get rid of the first decimal number              
      lcd.print(" ");
    }
    lcd.print(cal_counter);
    cal_counter--;
  }
  // convert filtered result to volt and include the reference, the input divider
  // and the zero cal. The zero cal must be done before the reference cal.
  float ref_volt = (cal_avg - zero_cal) * v_ref / 16777216 * 10;
  cal_factor = (cal_10v_ref - ref_volt);  // absolute difference
  cal_factor = 1 + (cal_10v_ref - ref_volt)/cal_10v_ref; // multiplication cal factor per volt
  EEPROM.put(cf_address, cal_factor);    // store the calibration factor in EEPROM
 
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("VRef Cal Factor:");
  lcd.setCursor(0,1);
  lcd.print(cal_factor, 6);
  delay(3000);
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("mVolt Meter      ");        // print Millivolt Meter to display and clear the rest
  DecPlaces = 0;                         // if decimal places got changed, reset it
  Monitor_batt();                        // the batt level display got erased, put it back
}


/**************************************************************************************
 * routine to check if the button was pressed, and depending on the length, decide what action to take
 */
void Button_press() {
  if (digitalRead(button) == HIGH) {     // only relevant for the polling loop
    lcd.setCursor(15, 1);                // placeholder for the button press ack
    lcd.print("0");                      // back to default
  
    if (buttonActive == false) {
      buttonActive = true;
      buttonTimer = millis();
    }
    // How much time has passed, accounting for millis() fix roll-over issues with subtraction
    if ((unsigned long) (millis() - buttonTimer > longPressTime) && (longPressActive == false)) {
      longPressActive = true;
//      Ref_Cal_Adjust2();
//      Ref_Cal_Adjust5();
//      Ref_Cal_Adjust7();
      Ref_Cal_Adjust10();
    }
  } else {
    if (buttonActive == true) {
      if (longPressActive == true) {
        longPressActive = false;
      } else {
        Zero_Cal_Adjust();
      }
      buttonActive = false;
    }
  }
}


/**************************************************************************************
 * The battery level monitor routine
 */
void Monitor_batt() {
  int i;
  long sum = 0;
  int sensorValue;
  for (i=0; i<(adc_samples); i++) {
    sensorValue = analogRead(batt) + adc_cal; // read from A0 and add the calibration factor
    delay(100);
    sum += sensorValue;
  }
  sum = sum / adc_samples;

  float batt_voltage = sum;
  // Convert the analog reading (which goes from 0 - 1023) to a voltage 0 - 12V:
  batt_voltage = (sensorValue * (adc_ref_volts / adc_res) * 3) + adc_cal;
  /*  note that during the test with a potmeter connected to 5V, the resulting maximum volt level is just below
   *  20V, if you connect to a PC with a USB cable for power.
   *  With the power coming from the 78L12, the maximum level will be 20V, unless you create a voltage divider to
   *  limit the maximum voltage coming from the potmeter to 3V, which is equal to the 20K:10K divider to the cell.
   */

  // print out the "battery" voltage level at the right-hand fields of the first line
  lcd.setCursor(15, 0);                  // start of the batt level field (line 1, last position)

  if (batt_voltage < 7.3) {              // critical batt level for the reference and the ADC is < 7.3V
    lcd.print(char(6));                  // battery is empty!
    } else if (batt_voltage < 7.7 && batt_voltage > 7.3){  // batt getting too low, connect to mains or stop
      lcd.print(char(5));                // stop measuring
    } else if (batt_voltage < 8.0 && batt_voltage > 7.7){  // batt charge is getting critical
      lcd.print(char(4));                //
    } else if (batt_voltage < 8.3 && batt_voltage > 8.0){  // batt charge is OK
      lcd.print(char(3));                //
    } else if (batt_voltage < 8.7 && batt_voltage > 8.3){  // batt charge is OK
      lcd.print(char(2));                //
    } else if (batt_voltage < 8.9 && batt_voltage > 8.7){  // batt charge is OK
      lcd.print(char(1));                //
    } else if (batt_voltage < 10 && batt_voltage > 8.9){   // batt is full
      lcd.print(char(0));
    } else if (batt_voltage > 10){      // batt is charging
      lcd.print(char(7));                // charging
    }
}


/**************************************************************************************
 * The main routine
 */
void loop() {
 
  // The minimum (also normal) looptime is 166 mSec, determined by the aquisition delay of the LTC2400
 
  Button_press();                        // check if the button was pressed

  // Check the battery level approx. every minute
  if (check_v > loop_cnt) {
      check_v = 0;
      Monitor_batt();                    // checking the battery level takes about 810 mSec
  } else {
    check_v ++;
  }

  // Take a new raw LTC2400 reading
  adcread = Spi_Read();
 
  // Check if the new reading is outside the noise level band of the filtered result
  // and dynamically adjust the filter weight accordingly.
  if ((adcread > average + noise_level)||(adcread < average - noise_level)){
                                        // reading is outside the noise band
    fw_multiplier--;                    // scale the filterWeight down with powers of 2
    if (fw_multiplier < 1){             // lower limit to 2<<1 = 4
      fw_multiplier = 1;                // bottom-out
      average = adcread;                // and reset the filter
      }
  }else{                                // the reading is inside the noise band
      fw_multiplier++;                  // scale the filterWeight up with powers of 2
      if (fw_multiplier > 6){           // upper limit is 2<<5 = 128
          fw_multiplier = 6;
      }
  }
  // update the filter weight; ranges from 4..128
  filterWeight = 2 << fw_multiplier;

  /*  Run the quisition through the IIR filter and take the new reading with a
   *  grain of filterWeight salt. ie. divide the new reading by the filterWeight factor (from 4..64)
   *  Note: average must be a float, otherwise there will be a compounded rounding error in the result.
   */
  average = average + (adcread - average) / filterWeight;

  /*
   *  Convert the filtered result to volts.
   *  16777216 = 2^24, the maximum number with 24 bits.
   *  Multiply by 10 because of the voltage divider at the inputConvert the filtered result to volts.
   *  Subtract the zero calibration factor and apply the cal_factor.
   *  The cal_factor is calculated against a known voltage reference.
   *  Cast the result to a floating point variable to get decimals.
   */
  volt = (average - zero_cal) * v_ref / 16777216 * 10 * cal_factor;
 
  // prepare for the display of the data
 
  // ==>> for debugging & Testing, to be analyzed with MS-Excel:
  /*
  Serial.print(adcread);
  Serial.print("\t");
  Serial.print(average);
  Serial.print("\t");
  Serial.println(filterWeight);
  */
  //  ==>> for testing purposes only, read and display the 9V cell instead
  //  volt = (analogRead(A0) * (adc_ref_volts / adc_res) * 2) + adc_cal;

  if (volt <0.001) {                     // check if voltage reading is below 1 milli-Volt   
    volt = volt * 1000000;               // if so multiply reading by 1.000.000 and display as micro-Volt
    v = micro + "V     " ;               // use uV on display after voltage reading
    dec_digit = duV;                     // set display to 0 decimal places (1000000 uV)
 
  } else if (volt < 1){                  // check if voltage reading is below 1 volt
    volt = volt * 1000;                  // if below 1 volt multiply by 1.000 and display as Millivolt
    v = "mV     ";                       // use mV on display after voltage reading
    dec_digit = dmV;                     // set display to 4 decimal places (100.0000 mV)

  } else if (volt < 10){                 // check if voltage reading is below 10 volt
    v = "V     ";                        // use V on display after voltage reading
    dec_digit = dV;                      // set display to 6 decimal places (1.000000 V)
  
  } else {                               // volt is > 10V
    v = "V     ";                        // if 10 volt or higher use letter V on display after voltage reading
    dec_digit = dTV;                     // set display to 5 decimal places (10.00000 V)
  }

  lcd.setCursor(0, 1);                   // set LCD cursor to Column 0 and Row 1 (second row of LCD, first column)
  lcd.print(volt, dec_digit);            // print voltage as floating number with x decimal places
  lcd.print(" ");                        // add one blank space after voltage reading
  lcd.print(v);                          // print either uV, mV or V to LCD display
  lcd.setCursor(15,1);
  lcd.print(fw_multiplier);              // show the filterweight exponent multiplier
 
  ct_start = millis();                   // After this noisy display intermezzo, reset the LTC2400 conversion time counter
                                         // such that the ADC has the full 165 mSec to sample a new acquisition
                                         // during a quiet period.
}

14 comments:

  1. Really useful. You are awesome. But I am using different controller (18F microchip series). I would like to use kalman filter. How can I incorporate kalman filter into my program? Any sample code with just using kalman filter? How did you calculate the number 96 for noise level. Please help.

    ReplyDelete
  2. If you want to use the Kalman filter, you really need to figure it out yourself. Earlier in my post, I show a search term for finding a very good explanation on YouTube.

    In my own opinion and experience, using the Kalman filter on a DMM with the hardware we have is too slow to be useful, which is why I ditched it.

    To get to the 96 as a noise level factor, I just did many measurements and pumped the results into Excel files for further analysis. While I was playing with the filter variables to get improved filter results and improved filter and display response times, I simply empirically decided that everything within this 96 range, which is only a mere 234uV level change, is probably noise, and everything outside this level is probably a "real" new value, requiring a filter weight change.

    Depending on your own noise levels, or responsiveness of the filter and display update frequency to new values, you can change the 96 value to whatever you like.

    If, make that a very big IF, I decide to use a rotary encoder as the GUI interface instead of the single button, I can make more variables changeable by the user. So far, I have not really seen the need, but since it is possible, I probably will, sometime.

    Success!

    ReplyDelete
  3. Your analysis of the project is incredible! I have leant much reading your take on the whole design. This is sort of a retrospective and new improvements of the project all in one. Thanks for the long post.

    ReplyDelete
  4. Wow! thank you!
    i have a few questions (and I apologize if they are dumb - I'm learning and looking for a project to work on with my mentor) -
    1. how would this change if measuring A/C signals?
    2. If using a nano, would it be possible to add have the outputs to include LED light, audio, AND bluetooth transmission??? OR would that create too much noise as well?

    Thanks again
    Ben

    ReplyDelete
    Replies
    1. I suggest you solicit your mentor to see if adding AC measurement capabilities is a doable and worthwhile project for you on the basis of this design. To prepare for that discussion, I suggest you use Google to familiarize yourself with that concept before you do.

      Adding an LED to one of the Nano outputs is possible, although I fail to see why you would want to do that. Why can't you use the LCD?

      I also don't think you want to add extra noise by doing anything with audio or bluetooth when the front-end is capable of measuring single digit milli-volts. How do you envision to keep the audio/transmission noise away from the measurements?

      If you are looking for a more software heavy project on the basis of this hardware, I suggest you look into better noise filtering or increasing the measurement frequency by optimizing the filtering/averaging algorithms.

      Delete
  5. Hi, impresive post!
    I also trying to uV meter using 24bit fully diff. ADC. I tried several ADC's but can't get stable 1uV measurement. Now I am trying with Analog Device AD7124. Do you think it's possible to measure uV range with 24bit ADC?

    ReplyDelete
  6. AFAIK, it is generally understood that with "normal" means in terms of layout and used components, you will only get up to 18 bits of resolution with 24-bit ADC's. Make the calculation and see where your limits will be.

    ReplyDelete
  7. Does this project involve analysing the adc or any other component (other than the voltage reference output) in order for the calibration to work correctly? It seems that you manually got the value for the unlinearities in your adc, and put those in the calibration routine..?

    ReplyDelete
  8. No whos, I only use an external voltage reference to calibrate. I have one of those good eBay ones that also lists the actual voltages after calibration/verification, so you know what the output is. As an example, mine has 2.49993 in the 2.5 setting, etc. That is good enough for me and I calibrate the DVM accordingly. Note that you can calibrate towards 2.5V, 5V, 7.5V and 10V, depending on your intended application.

    ReplyDelete
    Replies
    1. Gotcha. Thanks. Did your units two last digits jumping all around the place too? (I'm looking at those youtube videos) That defiates the purpose of having all those digits IMHO..

      Delete
  9. whos, the last 2-3 digits can indeed jump around. This is inherent with the sensitivity level due the number of digits. Apart from the stability of the meter itself, it can also be caused by the instability or noise level of the source you are measuring. Due to the "weighted averaging" algorithm that I implemented, and with a stable and clean source, even the last digit can be rather stable.

    Keep in mind that when you're dealing with this high of a number of digits and the resulting sensitivity levels, you also need to be aware of the minute amount of noise or fluctuation in uV that will cause the last digits to move. (know thy instrument)

    As a last resort, when the source is not that stable or the value is moving (you're adjusting), you can still reduce the digit count.

    ReplyDelete
  10. This comment has been removed by a blog administrator.

    ReplyDelete
  11. This comment has been removed by a blog administrator.

    ReplyDelete
  12. This comment has been removed by a blog administrator.

    ReplyDelete