At the tail end of last week, Alain Pannetrat, founder of Omzlo Electronics, took a look at the SAM D21 ADC while developing an Arduino shield and concluded that a four-year old bug in the Arduino
analogRead() function could mean it is providing incorrect results when measuring voltages.
It turns out your Arduino might be lying to you.
“…we noticed that measurements were all offset by about +35mV. In other words, all ADC readings on these boards were overestimating their input by about 35mV. These boards have one thing in common: they use the Microchip/Atmel SAM D21 Arm Cortex M0+ 32-bit micro-controller. Further tests across multiple analog pins and multiple SAM D21 boards showed offset errors ranging between 25mV and 57mV.”—Alain Pannetrat, Founder of Omzlo Electronics
So I guess it was time to take a look myself.
Testing voltages ranging from 0.0V through to 3.3V, Pannetrat saw an offset of approximately +34 mV on pin
A0 consistently between the reading on his meter and the value returned by the
Unfortunately, I didn’t have a good enough voltmeter to hand—as the one on my bench has a 0.0 to 2.0 V, and a 0.0 to 20.0 V range—so I couldn’t quite cover the entire 0.0 to 3.3 V range to three significant figures as Pannetrat had. However, I could take a look between 0.0 to 2.0 V, so setting my bench power supply successively to 0V, 0.5V, 1.0V, 1.5V, and 2.0V I went ahead and did that, slightly modifying Pannetrat’s original script to provide a running average for the value reported by
analogRead() to smooth out any blips.
Interestingly, I didn’t get the same results as Pannetrat. Instead I saw the offset between the voltage I measured using my voltmeter and the value reported by the Arduino
analogRead() function increasing with higher voltage.
Which wasn’t what I was expecting at all.
So I replicated my experiment with an Arduino Nano 33 IoT, which is also based around the SAM D21, and this time I saw different results.
Unlike with the MKR WiFi 1010, where I saw an increasing offset between the value measured with the meter and the value reported by the Arduino
analogRead() function, with the Nano 33 IoT I saw a more or less consistent offset of around +18 mV.
This result is more like the one Pannetrat observed. Although what I didn’t see is the same consistent offset when the
A0 pin is directly connected to ground as he did.
I also tried an alternative method, using a two resistors to split the voltage in similar fashion to that suggested by Pannetrat. Connecting a 10 kΩ resistor between the
A0 pins, and a second 10 kΩ resistor between the
A0 pins, I measured the voltage across the first resistor.
With the MKR WiFi 1010, I obtained a reading 1.636 V using a multimeter, and 1.662 V using the
analogRead() function. That’s an offset of +26 mV, and that is more or less in line with the previous results using the bench power supply.
It’s not actually that unusual for a micro-controller to have an ADC offset — for instance, the Espressif ESP32 has one and it’s discussed in the documentation, and while I’m not exactly sure what’s going on here, I’m not sure it’s really as serious as some people are making it out to be.
There’s definitely something interesting going on here, but my results are somewhat puzzling as they’re not directly comparable with those found by Pannetrat. So I’m going to keep poking at it for a bit and see if anything else turns up.
However, examining the ADC calibration code provided by Arduino, this looks to be a known problem, and one for a lot of people isn’t really going to be that problematic. Best to figure out what’s going on though, something that I’m sure will happen over the next day or two. The wisdom of crowds is about to make itself known.