How to build a 5 milli Kelvin thermometer with an Arduino?

How to build a 5 milli Kelvin thermometer with an Arduino?

Figure 1: a 10k NTC from the store, no idea who makes it, but it turns out to be a 5 mK thermometer.


In this blog I will show how you can build a 5 milli Kelvin (5 mK) thermometer with an Arduino Uno, all you need is a 10 kilo Ohm NTC, a 16 bit ADC board from Adafruit, a Vellman probe and an Arduino proto shield.


Negative temperature coefficient (NTC) resistors appear in many electronic circuits. In the battery charger I use a 10k NTC to monitor the temperature within the case since the current regulator generates heat and we don’t want it to overheat. Once we reach 90 degrees Celsius close to the LM317 heat sink the microcontroller decides to temporarily stop charging. In other designs such as the oven crystal oscillator there is a thermostat to keep the temperature within a strict regime to guarantee optimal frequency stability. I demonstrated that 1 part per billion, or 1 milliherz relative to one Megahertz can be obtained mainly because of the quality of the thermostat. The NTC resistor is an amazingly simple passive component that comes in many different designs. The 10k resistor is supposed to have a reference value of 10000 Ohm at 20 degrees centigrade. But this is where the trouble starts; cheap NTC’s usually don’t come with a calibration curve, so I thought it could be a fun experiment to try to do this myself during the holidays.

Simple circuit

The simplest circuit that allows you to measure the NTC resistance is to put a 10k resistor in series connected to 5 Volt, and to measure the voltage across the NTC to the ground. There is very little to design, you measure the voltage with an analog channel on the Arduino UNO and, pronto, it works. But soon you find out that a simple design is limited 3 issues:

  1. The voltage measure across the NTC has a resolution not better than 5 millivolt because the Analog to Digital converter is designed for 10 bit resolution (5000 mV / 1024 is approximately 5 mV)
  2. The measurement is susceptible to noise, because the 5000mV that comes from the power supply of the Arduino UNO fluctuates slightly, a standard deviation of 15 mV is rather typical.
  3. Since there is a current going through the NTC we slightly heat it up, the 10k NTC in series with a 10k resistor generates a current of 5000/20e3 = 250 microamperes. When multiplied times the 2.5 Volt across the NTC we dissipate 625 microWatt which slightly increases the temperature. The more we dissipate the more the temperature runs up, and the resistance decreases so that the current increases even more. To stabilize this problem you would need a constant current, or a large resistor in series.

Advanced circuit

The challenge is therefore to design something that works better. To tackle issue 1 you need a better ADC for which I got the ADS1115 16 bit ADC from Adafruit which has 4 channels (adc0 to adc3). You do not need anything better for this experiment. Issue 2: To do something against the supply voltage fluctuations you use two channels on the ADC, one channel monitors the voltage across the NTC, and another channel monitors the voltage of a potentiometer adjusted roughly at the voltage level across the NTC. The schematic contains therefore a so-called Wheatstone bridge. Issue 3: To circumvent the self-heating problem you have to reduce the current through the NTC, I’ve put therefore the possibility to switch between four different resistors. But be careful here, you can easily damage the ADS1115 by a voltage that exceeds the limit which is depends on the gain setting of the ADS1115. It has an internal amplifier that allows to set the gain between 2/3, 1, 2, 4, 8 and 16. With a 10k NTC and a 100k resistor in series a gain of 2 allows a maximum voltage of 2048 mV with a 300 mV tolerance. As long as we don’t put the board in very cold temperatures the ADC survives. The power dissipated in the NTC remains under 20 microWatt which is a factor 30 better than a 10k resistor in series.

Figure 2: Schematic NTC calibration
Figure 3: The top figure shows the voltage across the 10k NTC, both in differential mode (blue) and in single ended mode (green). The bottom graph shows the reference voltage across the lower end of the potentiometer.

Single-ended or Differential?

Figure 3 shows what is measured during the experiment, the lower graph displays channel adc1 and the upper graph shows in green the adc0 measurements. As you can see, the voltage across the potentiometer fluctuates by approximately 1 millivolt for a 341 mV average, across the 25k potentiometer we get typically 15 millivolt (5000/341) which is caused by fluctuations in the supply voltage. The Arduino UNO used in this experiment obtains the supply over the USB cable, and it has an internal regulator. The 15mV is quite normal, but it is rather large for our goal. The ADS1115 designed by Adafruit has an internal regulator and filters that take away to supply voltage fluctuations. To eliminate the supply voltage noise I take the difference between channel adc0 and adc1 on the ADC. This difference doesn’t have the disadvantage that it is affected by supply voltage fluctuations, but, differential measurements introduce a bias into our problem. To circumvent the latter one takes the average value over the adc1 channel, which is then added to the differential voltage, and this is shown as the blue continuous line in the upper graph. The blue line is clearly more accurate than the green stuff on channel adc0, the blue line is the best we can do for measuring temperatures. ADC terminology: Single-ended measurements on the ADC concern a direct voltage measurement on any channel, like adc0 or adc1 directly. Differential measurements on the ADC give a direct value for the adc0-adc1 voltage. The ADS1115 library has functions that allow you to do both. So the way to go is to take a differential measurement where the potentiometer setting is somewhere near the average NTC resistance, and to add the average, or a low pass filtered value of the reference channel adc1 back in to arrive at the blue curve in figure 3.

External calibration

In the end you also want to know the relation between the voltage measured across the NTC and the ambient temperature. Now, this is an external effort because there is nothing set-up within our circuit that allows me to obtain an absolute temperature. But, there is a probe from Velleman which has a digital resolution of 0.1% which is probably its precision, but not its accuracy. The best I could do at home is to compare the NTC voltages against the external thermometer readings, the set-up of the experiment and the calibration analysis results are as follows:

velleman probe
Figure 4: External calibration
arduino proto shield
Figure 4a: close up of the experiment

The external calibration is somewhat affected by the self-heating of the Arduino, but as long as the tip of the Velleman probe is close to the grey 10k NTC then it may be assumed that both refer to the same ambient temperature. The following figure shows the relation between NTC voltage and external measurement, and it turns out that there is near perfect linear relation within the temperature interval of 25 to 29C, the relation is:

T=-0.0683 U + 29.8, with U in [10,70] and T in [25,29]

where U is given in millivolts and T is in degrees Celsius. In other words, a change of 1 millivolts is equivalent to 68.3 mK. And since the ADC resolution is 0.0625 millivolt we get the internal precision of 0.0625 * 68.3 = 4.26 mK. Since NTC voltage is obtained by a differential measurement one can wonder what the possible external accuracy could be. (External in the sense that the velleman probe tells the truth). The adc1 channel has 550 samples and the noise by sample is estimate around 1 milliVolt standard deviation, the bias that we account for in the differential mode has therefore a standard deviation of 1/sqrt(550) or 0.0426 mVolt, this adds a tiny amount of noise to the calibration, in fact, it should be added to the ADC noise so that the external accuracy becomes sqrt(0.0625^2 + 0.0426^2) * 68.3 = 5.17 mK. I thought it was a pretty amazing result for a DIY experiment, we have built ourselves a 5mK thermometer which is 20 times better than the 100 mK of the Velleman probe.

Figure 5: External calibration of the NTC

Precision thermometer version 2: on a circuit board

Version 2 of the precision thermometer, it is now on a printed circuit board and has its own LCD display.

I moved away from the #arduino proto development shield to standard proto-board with its own LCD display. The reason is that I prefer to have a separate display, and I want to be able to move around the house to increase the temperature range. The living room is 3 to 4 degrees cooler than the second floor during this hot summer of 2014. To increase stability of the resistance measurement I now use 1% tolerance metal film resistors, and I added a separate 3,3V regulator to the Wheatstone bridge (it is a le33). The gain on the #adafruit ADC is set to 1, you don’t want to increase it to avoid damage when there is no NTC in the connector block in which case it should display the output voltage of the le33 (this can be set by a jumper left of the le33, the one on the north east of the le33 is a voltage check point for the multimeter). The top line on the photo shows the resistances for the left and the right NTC, and the bottom line shows the temperatures in centi-degrees Celsius for both corresponding thermistors. The resistance measurement is calibrated with a set of metal film reference resistors. Metal film resistors increase the thermal stability of the design, I don’t really care about their resistances, as long as they are stable and independent to the current going through the resistor. The resistances shown on the top line should be better than 0,1% and they were compared to several multimeters. My 30 year old Fluke 75 that is fortunately grey rather than the ugly yellow they go for these days, and the red fancy Velleman dvm893 which was so cheap in the shop that I couldn’t leave it there. Having two multimeters is always better than 1, ok, there is somewhere a very old analog monacor on the workbench, but that one is 40 years old, and I also have somewhere one of the very early digital ones from Sinclair that I don’t take too seriously. Interchannel biases on the precision thermometer v2 can be removed by exchanging a series of matching metal film reference resistor sets on both channels, eventually you get to the <0.1% accuracy measurement on the resistances. Actually, the #adafruit ADC outperforms ALL my multimeters, but I wouldn’t sell you it as the perfect multimeter since the ADC has no protection circuitry at all. The #Velleman probe has 0,1 degree C resolution, the manual states 1 degree accuracy, yet the precision level is really a lot better I think. (I’ve put the probe in a kettle of boiling water and it arrives to within 0.1 of a degree at 100C, and the same is true for melting ice cubes, compliments Velleman, this is a brilliant design). But other evidence for the accuracy of the Velleman probe comes from the following two graphs that consist of 10 datapoints for both thermistors (red is left and blue is right to keep up with the political system). The linear regression analysis was done in matlab.

redntc bluentc

I only had to edit out 1 datapoint which was taken apparently too early in the calibration measurement series so that the Velleman probe and the NTC’s did not match. You should allow for about two minutes in a flow free environment before you take any measurements. The regression coefficients for both NTC’s match to better than 10%, the only thing that really matters is the offset between them, but both parameters can easily be determined by means of regression. My conclusion is therefore that the Velleman probe is really better than 1C accuracy that they claim in the manual (not sure where that statement comes from). Perhaps they put it in there to avoid unjustified claims. But the sensitivity of  precision thermometer v2 is amazing, if you stand close to the thermistors it senses your body temperature, 0.1 of a degree increase is very easily achieved. Yesterday I’ve put the thing in a policarbonate ‘case’, that is, two pieces of polycarbonate, you mount the circuit board on the bottom plate and, a connector on the bottom one, and four separators to the top plate. This is better than carrying around an unprotected bottom site of the circuit board so that you might introduce, or worse short circuit parts on the circuit board.

Experiment in a polycarbonate case
Left (red) NTC calibration curve
Left (red) NTC calibration curve
Right (blue) NTC calibration curve
Right (blue) NTC calibration curve

My external comparison to the Velleman pin probe gave me the following model parameters for these NTCs:

  • Reference resistance = R0 = 9424 Ohm
  • Left offset = Rl = 25.32 C
  • Right offset = Rr = 24.51 C
  • Slope = alpha = -0.00256117 C/Ohm
  • Accel = beta = 1.30976e-07 C/Ohm^2
  • RMS of fit = 0.2856 C (refers to observed – computed)

The model is:

  • x = (R-R0)  where R is the observed resistance of the either the left or the right NTC.
  • Left temperature: Cl = Rl + alpha * x + beta * x * x  
  • Right temperature: Cr = Rr + alpha * x + beta * x * x;

Four datapoints were removed from the time series because the difference was too large, and the RMS of fit is here your helping hand. Eventually the NTC calibration parameters are determined by democracy in a least squares algorithm.  I suspect that it is sufficient to keep the slope and the acceleration parameter as fixed for a series of thermistors, and to determine the offset once for each NTC in a series. And as you can see, that offset is apparently depending on how the NTC was produced.

Do we need a 16 bit ADC?

You could have done the same with a 10 bit ADC which is standard on the Arduino, but this reduces the resolution of the output.

Suppose that you take a 3.3V reference on the ADC with a 10 bit sampling, that would result in 3 mV steps, and probably 6 mV steps because of the noise on the ADC (you never get to the full bit range). Steps of 6 mV will result in 0.4 degree C jumps which is maybe a bit large for the precision thermometer but good enough for the standard kitchen thermometer.

In some designs I’ve included a tuned Wheatstone bridge and an LM358  opamp to focus on the temperature range that we most use, and this is perhaps the cheaper way to accomplish the same as in this experiment. Roughly a factor 10  of amplification on a differential opamp should do the job, but be careful in scaling down the output of the LM358 to the voltage range of the Arduino ADC.

Last update: 31-7-2014 E. Schrama

Battery charging: a maximum power point tracker with an Arduino


The purpose of this Arduino experiment is to build a general purpose charger that uses a maximum power point tracking strategy. The design originally considered a 150mAh 9V NiMh battery, later I redesigned it so that anything under 1000 mAh can be charged, either with a NiCd or a NiMH battery up to the limits of the used current regulator (an LM317). Initial designs were based on a constant current charger and a timer, but this is not a fail-safe method that can lead to overheated batteries. So I started adding timers and temperature control circuits, but this did not give me what a maximum power point tracker can achieve.  So what does a charger actually do? The charger should put a constant current through a number of cells. The voltage that you get to see across any cell would look like what is shown in figure 1:

Fig 1: Charge curve for a 9V 150 mAh battery

Figure 1 shows what a 9V 150 mAh NiMH battery does during a charge cycle. In the first 20 minutes or so the voltage runs up till you get a plateau, this level is maintained for a while until we see that the voltage peaks to 10.8 Volt. After the maximum power point at 140 minutes since the charger started you see that the voltage drops because the efficiency of the charging process deminishes. This is the point where the batteries get hotter, and it is a good practice to stop after you have reached the maximum power point. The datasheet actually warns against temperatures greater than 40 to 50 degrees.

The charger itself, and in particular its current regulator, also gets warm during the process. This depends on the voltage difference across current regulator. At 7.5V across the battery the regulator has to dissipate at least 11V – 7.5V  times 85 milliamperes = 298 mW. In this case you can simply mount the LM317 on  a piece of circuit board with a 3mm nut and bolt. But at 266 mA (the high setting) you need more, so I mounted a piece of aluminum on the lm317 and I drilled some holes into the case so that the temperature stays well under 90C.

Batteries are by definition a current source, the capacity is expressed in milliamp hours, a value of 150mA.h means that 150 milliampere could be delivered for 1 hours. To compute the charge level  of a battery you have to add up all time intervals times times the current. The charge process has a certain efficiency, not all the charge that we put in affects the electrolytic reaction within the battery. In figure 1 we see  that 215 mAh was put into a 9V NiMh cell that was good  for 150 mAh.

We speak about trickle charging when the current is below 1/10th of its capacity. For example, a 150 mAh battery is charged with 15 mA so that you need at least 10, and probably more like 16 hours. This is a perfectly safe method since nothing can go wrong. But if you are impatient then C/2 or C is also possible. If you read the datasheets of a battery then fast charging is a process that needs to be carefully controlled. So the idea was, let’s do this with an atmel328p microcontroller that keeps an eye on this process.

When you discharge a battery you get the see a curve as shown in figure 2 where voltage is shown over time.

Fig 2: Discharge curve for a 1.2V NiMh cell.

During discharge you get to see a rapid decrease in voltage in the first 2 hours (the data comes from the GPS tracker, it draws 50 mA from a 800mA pack), until we hit a plateau that is maintained for the coming 8 hours, after 10 hours the voltage gradually falls until the cell is depleted. Running an cell empty is something you should try to avoid, for older batteries this usually means that you killed the poor thing. It is actually a common problem in cars, UPS’s, notebooks and a lot of other consumer electronics. So, handling batteries is something delicate, in the end you always lose. A battery management system can help to extend to lifetime.

The charger

This design consists of a microprocessor, an atmega328p, which is the same as found in an Arduino UNO, an N-channel power MOSFet, a current limiter, an EEPROM and a LCD screen. The EEPROM is used for recording voltages, so this is how I make graphs like figure 1 and 2.

Schematic of the charger
Design before it had heat sinks and a fast charging option. (Design completed in February 2014)
The circuit board, now with heat sinks and a 4.7 Ohm + 10 Ohm resistor for the LM317. By a switch you can short circuit the 10 Ohm resistor, this implements the fast charge. The LM317 law is I=1.25/R where R is here 4.7 or 10+4.7 Ohm, so that I=265 or I=85 milliAmp.
Rear side of the circuit board, I put it here so that I can check the board in case I run in trouble.
Typical use of the charger, add it to a lab power supply, connect a battery, all via 5 mm plugs. Two push buttons are included, left turns the back light on and off, right is used to start and to cycle between different screens while charging.


The power connector on the left (pin is positive, edge is negative, 5mm diameter) accepts anything from a laptop charger which is typically 16 Volt DC and upwards which is needed because the current regulator steals 1.25V, while two Silicon diodes take away 1.4 Volt. To obtain 11V at the battery you need 13.65V at least as input. The battery connector on the right takes on the positive lead the output of the LM317 current regulator (via a Si diode) and at its negative lead a IRL540N N-channel MOSFet. Essentially the latter is a switch or a solid state relay. One could design all of this with smaller components in an SMD design, yet the game was here prototyping on a standard Velleman circuit board, it is a DIY activity.


Screen 1: shows charge supplied.
Screen 2: shows three voltages discussed in the text

Figures screen 1 and screen 2 show two screen shots of the charger.

In screen1: H means that we are charging with a high current, and L would mean a low current. This is controlled by a switch near the battery connector on the right side. High means 266 mA (calculated as 1.25/4.7 according to the LM317 datasheet) and low means 85 mA (=1.25/14.7) . The low setting is good for 9V 150mAh NiMh batteries,  high is good for anything above 500 mAh such as a pack of 1,5 AAA or AA penlite cells welded in series. For most of my experiments I use four 1.5V AAA NiMh cells, with a capacity of 800mAh. In figure 5 you see another example of 10 NiCd cells of 1.2V each with a capacity of 900 mAh.  The H of L in figure 6a follows from the calculated charge delivered to the battery. The second line shows R 99:33, meaning that we are running for 99 minutes and 33 seconds, and T=262 is a temperature indicator. The 262 refers to a voltage across a 10k ntc between null volt in series with a resistor of 10k which is mounted close to the heat sink of the LM317 current regulator in the schematic. Below T=130 the temperature goes above 90C and the microcontroller interrupts the charging until it has cooled off to above T=160. Actually, the LM317 has an internal circuit that already protects overheating, but it recommends to stay under 125C.

Screen 2: This display shows what the tracker is doing. P14639 on the top rows means that we reached a maximum voltage of 14639 millivolt at T10, thus 10 seconds ago. The most recent lowest voltage was L14150 thus 14150 millivolt. This reading is taken just after the each relax window that lasts 5 seconds. Every 2 minutes we shortly interrupt the process to measure the voltage drop across the battery, 14150mV is the value that we recently obtained, and C14614 is the actual measured voltage (14614 millivolt).

The problem with charging any type of battery is that you have to watch the point where the most recent measured voltage does not exceed the maximum (or peak) voltage found by the tracker, also, that the peak occurred not too long ago. The end of charge condition is determined by three factors: 1) The highest voltage point occurred 900 seconds ago 2) We are 25 millivolt below the peak point, 3) Alternatively, more than 1000 mAh  was delivered.

After the peak voltage, the charging process becomes ineffective since the supplied current is not used anymore to feed the chemical reaction in the battery, instead the electrolyte starts to heat it up. If you would extend this indefinitely then the battery will die at some point. This is a problem with all chargers, if the algorithm runs  too short and you miss capacity, if it runs too long you damage the batteries. The standard solution is to apply a C/10 or C/20 trickle charge which lasts a day or so.  This will result in a steady equilibrium where usually no damage is done. In a fast charger the design has to include an algorithm to track the maximum power point, and to stop after this point to avoid thermal damage.


In it is explaining how batteries behave during charging and discharging. One design hurdle is that the peak voltage is temperature, battery age and charge condition dependent. With analog only electronics the peak power point is hard to find. As a compromise you can preset a maximum voltage (as I did in earlier designs), but it is not an optimal design. The maximum power point discussion follows from Volt against Time graphs on the battery datasheet. In professional designs battery power management is handled by a dedicated IC such as the LT3652HV from linear technology, see
Last update 23-july-2014