Low ADC Readings

I have a thermistor connected to A6 in the following way:

GND |–//\T//–A6–//\22K//–| 3.3*

3.3* = 3.29V
Thermistor @ room temperature reads as 220K (68*F)

I checked out the master branch, built, and flashed the tinker app onto my core. When I take a reading from A6, it shows ~3575 when it should be reading closer to 3725.

22000 / ((4095 / 3574) - 1) = 150,917 Ohms
22000 / ((4095 / 3725) - 1) = 221,586 Ohms

I read about adding a 0.1uF cap to ground, so I added it. With the cap, my readings dropped further, so I removed it.

I tried flashing tinker from the phone app, and my readings were similar ~3575.

I then thought the breadboard could be a problem, so I soldered up a quick perfboard with the same results. Without the core in the circuit, the thermistor reads ~220K, but when I add the core back in, the resistance drops to ~150K.

Does anyone have a clue as to what’s going on?

HI @carlvon

Because of the way the ARM processor chip on the Spark core implements the ADC, the input impedance of analog input depends on how fast you run the ADC. More samples per seconds means a lower input impedance so there is a trade-off. At the default settings in the current master branch, the input impedance is a bit tricky to calculate because the core uses a special interleaved mode with two ADCs to try to raise the input impedance, but at 220K ohm, your thermistor is too large a resistance for this input impedance of the core and so the core changes the reading. When you think about, an input impedance of around 800K ohm to ground would give the readings you are seeing.

You have a bunch of options here:

  • You can live with it and use math in the core to rescale the answers to good values. With a thermistor, there is typically a Z-shaped curve that you need to fit anyway to get good answers so this might not be a big deal.
  • You can change to a lower resistance thermistor like a 10K ohm.
  • You can add circuitry to lower the impedance that the core input sees, probably using an op-amp or a specialized op-amp called an instrumentation amplifier. You can read about this here or here.
  • You can use a specialized IC that converts thermistor resistances to digital values like the ones described here.

It is really difficult to design a circuit to linearize the thermistor resistance (that z-shaped curve) over a large temperature range, but doing that in software is usually much easier, so figuring out which things to fix in hardware and which in software is key.


Hi @bko , and thank you for your insight.

I thought about buffering the thermistor, but in reviewing other designs that incorporated the ET-73 probes I’m using, it looked like one wasn’t needed (even for the core).

@avidan is using the same probes, and it appears his ADC readings are more on par to where mine should be. The only difference I can see, is he’s using A4 instead of A6; however, that shouldn’t be an issue. I’ll try A4 later tonight to see if it make’s a difference (I wanted to leave the SPI ports free for future expansion). In the meantime, can you think of any reason my readings would be so far off from his? I wired my core exactly like he shows in post 11 of that topic, with the exception of using pin A6. My fixed 22K R measures out at 21k6, so I don’t understand why my readings are way off.

Hi @carlvon

Let’s focus on what we can see. Can you measure the voltage across the thermistor while it is connected to the core and running? Also the 3.3V* supply–is it still 3.29V?

Maybe we can figure it out from there!

That thread you mention goes on about finding the linearization equation and how you need to use log10() in the equation, but let’s get the ADC values sorted first.

One more thing I should have mentioned is that there is bug in the current webIDE where reading from multiple analog pins produces wrong results. This bug is fixed in the current master branch so if you compiled locally and loaded your code onto your core using dfu-util or the command line with the .bin file you built yourself, then should be fine. If you loaded your program onto your core another way, then you could be seeing the bug.

One version of this bug made the results be 1 part in 20 too low, which is close to the numbers you are seeing, but you would have to using multiple analogRead() calls on different pins.

The only thing I was concerned about in that topic was that he was able read using the same setup I have, but I’m unable to produce the same results.

Just for grins, I tried A4 - same low results (as expected).

@ 238K, I’m reading 3.01 across the thermistor, and 3v3* is still 3.29. Both as expected. However, the core still reads it as 3538 or 2v84.

OK I am a bit stumped. You should be getting 3746 +/- 1 from analogRead() with 3.01 V on the input pin and 3.29 V 3v3*.

How about I ping back here after the next update to webIDE because the latest master branch for local builds has some fixes to the ADC that I thought only affected multiple ADC channels but do cause ADC readings to be 5% too low.

Me too. I checked out the master last night, and burnt the default tinker to the core, so if those changes were merged before then, it should have them.

In the meantime, I’m going to order an ADS1015.

1 Like

Hi @bko,

As a reference, I built the circuit using my Arduino UNO. The results are as follows:

Vin/AREF = 3v31
Vthermistor = 3v02
Rthermistor = 232k
R1 = 21k6
Aval = 936

The circuit checks out on the Arduino, so I’m thinking there is something wrong with my core. Would you be able to test using fixed resistors? If you could, we could at least eliminate the Ri question.

@carlvon please upload your program so we can check that out as well, and I’ll try to make some time today and test with fixed resistors. 22k and 220k.

Here’s how to add your code nicely:

Thanks @BDub!

I have a few other things going on that will prevent me from getting to this for a while.

One other thing for @carlvon: while @BDub tests with 220K/22K resistors, maybe you could try your Spark core with 22K/2.2K fixed resistors. That would be more in keeping with the input impedance of the Spark core.

Even the datasheet for the ATmega328p on the Arduino says

The ADC is optimized for analog signals with an output impedance of approximately 10 k ohm or less. If such a source is used, the sampling time will be negligible. If a source with higher impedance is used, the sampling time will depend on how long time the source needs to charge the S/H capacitor, with can vary widely.

The Spark core ADC works in a similar way, but the clock rate is much higher and samples faster and so the impedance range is different. Some recent changes in the Spark firmware that are not in the webIDE yet will allow you to change the sample rate of the ADC but there are still some software concerns over how exactly to make that work with the particular DMA mode used.

Hi @BDub,

I’m not using any custom code. I’ve tired with Tinker flashed from the iOS app, and built from the default application.cpp in the master branch. Both return the low value when the pin is set to analogRead and I sample the value.

carlvon@master-bedroom ~/spark/core-firmware $ git show --oneline -s
6b82698 Updated WLAN_MANUAL_CONNECT code (Not to be confused with static IP ...)

Hi @bko,

I’m heading out to get the wife and kids some brunch, but I’ll try this when I get back. I did test it with 22/220 yesterday, and the value was spot on. Since at least one other core user was able to get valid readings using higher impedances, I’m assuming my core has a defective/lower tolerance ADC, but I need some more users to confirm that they are able to get valid readings at higher impedances.

Hi @carlvon

I think you also have to ask how the other users were getting their readings. What version of the core-firmware were they using, for instance? Do they use analogRead() or do they control the STM32 ADC more directly. With more direct control, you can slow way down.

The ADC has been undergoing some changes recently to improve stability, particularly with multiple ADC channels. There were a lot of complaints in the in the past that @BDub actually helped to resolve figuring out that using the fastest possible sample rate leads to the lowest input impedance. The current master branch for local compile uses a mode that runs two ADCs fairly fast but not at the fastest rate in alternating steps and then averages twenty samples for each analogRead(). The Spark engineers found that doing it this way is still faster that analogRead() on Arduino, but I hope you can see there are lots of engineering tradeoffs.

Have a good brunch!

Here’s the test with 2k19/21k6:

iOS flashed: low @ 3646
Locally flashed: correct @ 3717

The circuit:

Tinker flashed from iOS app:

Tinker flashed locally:

OK, so that is about 2% low for the iOS flashed version. I don’t know which version of Tinker you get when you flash from the iOS app. Maybe @Dave can comment on that.

But you do have a good ADC when running the most recent code and using lower valued resistors.

I think you have a couple of directions to explore:

  • Ask @avidan how his thermistor meat thermometer is working (cool project!)? He seems to have a lot of calibration code with constants to work out plus the log() versus log10() thing.
  • Add an op-amp to buffer the 220K thermistor. An LM358 in a follower configuration would probably work great.
  • Try a software solution to slow down the ADC for your high-impedance thermistor.

Hi @bko. Brunch was good - thanks!

I understand what you’re saying, but I have never been able to get my core to properly read my circuit. This is the first circuit I tried, and out of the box with the shipped firmware, various test using the webIDE, iOS flashed Tinker, and locally compiled Tinker have always produced low results using this circuit on my core. Low sample rates are fine for my project, so I’ll look over the firmware and see about modifying it for my needs.

Firmware bugs aside, I’m beginning to think the problem lies in my core. If others are able to produce good results at higher impedances, then I think my core is defective. I’m not sure what kind of testing is done on the SMT32, but mine probably wouldn’t have passed if it included high impedance ADC tests. If my assumption is correct, then I’d be better off including an external ADC in my final design. If @BDub is able to read good values, then I think we’ll be able to put this one to rest.

Sorry guys, meetings all damn morning here… gimme a sec to walk to the lab and get some resistors :smile:

With Tinker from Local Master

A6 input = 2.8110V
3V3* = 3.2694V
220k = 218.71k ohms
22k = 21.800k ohms

Expected value should be: 3724

3528 (First reading via Tinker)
3563 (4.3% low)

3521 (First reading via Tinker)

With Tinker from the Sparkulator (web IDE)

3714 (0.3% low)


BTW local master is behind where it’s at now:

c:\Spark\core-firmware>git log
commit 4bcc9aa1da428b223f5190c2f146a996ebd631c1
Author: Satish Nair <satishgn77@gmail.com>
Date:   Fri Mar 7 19:32:49 2014 +0530

Latest Build Products

In my opinion this test is inconclusive because I’ve had better readings with the local master recently, but now this is suggesting the Sparkulator is best. Not good.


Hi @BDub

I’m confused here. If you measured A6 at 2.8110V and 3V3* at 3.2694, then the ADC should read 3521, shouldn’t it. You seem to saying that even though the measured voltage at A6 is low (should be 2.9731V if perfect), the core should somehow get the right value (3724) for the measured resistance ratios, even though something is clearly lowering the voltage on A6.

For me, this is very clearly the input impedance of the ADC, which would only have to be around 350K ohm to completely explain these results.

What voltage does the series resistor pair read if it is not connected to core input, just 3v3* and GND?

Thanks for checking this!