Let's Make Robots!

Beware of floating ADC inputs. They may play tricks on you

Last night I was building my next bot, Hammer, and I got to the point where I had all the electronics more or less completed and some mechanics done too. With some help from my dear friend hot glue I managed to build a shaky test platform. I hoped I could test Sharp IR distance sensor and motors. Maybe even do some simple navigation stuff. Here are a couple of pictures how Hammer looks like right now.


Front


Back

So I started up AVR Studio and opened my Hammer project. I already had some ADC test code from my infrared tracking Evil Eye tests so I didn't have to do awful lot of coding. My idea was to run ATMega8's ADC in free running mode. That means it will keep on sampling until it is told to stop (instead of stopping after each conversion like in "single conversion mode"). I'll be using 5 ADC channels (1 for Sharp + 4 for Evil Eye) and after each conversion I will change the ADC to sample next channel. The test code writes ADC values to USART few times a second so I can see what's going on.

Code was up and running in no time and when nothing was connected reading fluctuated between 0 and 3. I thought that it's ok because they are floating. I hooked up the Sharp to its place in ADC4 (ADC0-ADC3 not connected anywhere, reserved for the Evil Eye) but the values I got weren't what I expected. ADC values looked like this (ADC0 leftmost, then ADC1, ...):
111, 110, 109, 108, 113
112, 111, 111, 110, 113
112, 111, 110, 110, 112
112, 111, 110, 109, 113
114, 113, 112, 111, 116
...
(BTW: You can spot a teeny-weeny tip of what's going on from those values. At late night I didn't.)

It was like all channels were showing the reading from Sharp. I was puzzled. My expected result would have been something where ADC0-ADC3 still fluctuate between 0 and 3 and ADC4 shows the result from Sharp. First I thought that I had made a solder bridge between all ADCs (which would have been quite an accomplishment). I checked my soldering and it was just fine.

Then I started to debug my ADC test code. I spend quite some hours with it and didn't find any problems. In desperation I even tried implementing ADC reading in a few different ways but I always got same results. It was already past 3 AM when I tried hooking Sharp to ADC4 and Vcc to ADC0. This time values looked like this:
255, 254, 252, 250, 106
255, 253, 252, 250, 109
255, 253, 252, 250, 107
255, 253, 251, 249, 107

(Yes, that tiny tip it still there. This time I noticed it.)

Ha! I got ADC0 and ADC4 showing what I was expecting and this time I noticed something on floating ADCs. They were getting a little bit lower values each time (from ADC0 to ADC1 to ADC2 and to ADC3). They were kinda floating, just like I left them. I even thought like this before I ran any tests: I can leave those unused ADCs floating because they won't mess up the channel I'm reading. That's exactly what happened. When ADC mux selected floating channel its comparator was "floating" at the previous value because I wasn't "actively driving" it to anything else. Floating channels didn't mess the channel that had something plugged in. It was the other way around. Damn! It was working all the time and I spend hours trying to fix it. I just got fooled by those weird looking values because I was expecting something else.

I ran a test where I dumped ADC values at 76800 baud rate as fast as I can (previously I only dumped values abt. 10 times a second). This test showed clearly what was going on. Here's what I got when I had Vcc on ADC0 and then disconnected it:
255, 253, 252, 250, 247
244, 241, 241, 241, 244
227, 226, 227, 225, 227
210, 209, 209, 208, 210
192, 192, 191, 191, 194
178, 179, 178, 177, 179
166, 165, 165, 165, 166
155, 155, 155, 155, 156
144, 144, 143, 143, 144
129, 129, 129, 128, 130
116, 116, 116, 115, 117
104, 104, 104, 104, 105
95, 95, 95, 94, 95
89, 88, 87, 88, 89
79, 80, 80, 79, 81
71, 70, 70, 70, 71
63, 62, 62, 62, 63
56, 56, 55, 55, 56
50, 50, 50, 48, 50
46, 44, 45, 46, 46
40, 41, 41, 41, 41
35, 34, 34, 34, 35
28, 28, 28, 28, 28
24, 24, 24, 24, 24
21, 21, 21, 20, 21
20, 20, 18, 20, 20
16, 18, 19, 19, 19
15, 15, 15, 15, 13
11, 11, 11, 11, 11
9, 9, 9, 9, 9
8, 8, 8, 8, 6
8, 8, 5, 7, 8
8, 8, 8, 8, 8
6, 8, 8, 8, 8
7, 7, 7, 7, 7
5, 5, 4, 4, 5
3, 3, 3, 3, 3
2, 2, 2, 2, 2
2, 2, 2, 2, 2
3, 2, 0, 3, 3
4, 4, 4, 4, 4
4, 4, 4, 4, 2
3, 3, 1, 3, 3
1, 1, 1, 1, 1
0, 1, 1, 1, 1

Yup, it's pretty clear now. It takes some time for floating ADC channel to float down (or close) to zero. Oh man, I feel so stupid now. Note to self: Floating ADC input may/will float at the value of connected input. Don't care too much of those floating values.

Lastly a few words about my test program. I have attached the last version that dumps ADC values to USART at "full 76800 baud rate". As I mentioned earlier the ADC is on free running mode. The catch in free running mode is that Interrupt Service Routine (ISR) that handles ADC conversion complete interrupt (ADC_vect) will be running "one conversion behind". This happens because when ISR is called ADC will already be doing next conversion. I did a little chart showing how free running conversion goes. Hopefully it makes it a bit clearer to you. At least it made it clear to me when I created the chart. MCU column tells what's going on in MCU (i.e. in "C code"), adc_selector column shows the value in adc_selector variable (used to change channel when it's time), ADMUX column shows the value of channel bits in ADMUX register, ADC column tells what ADC is doing and channel column tells what channel ADC is working on right now. Looking at the attached C code may help you to decipher the chart (if you know C, that is).

And here's the chart:

MCU adc_selector ADMUX ADC channel
Init ADC and start sampling 0 0    
  0 0 Start 1. conversion 0
0 0 Converting… 0
0 0 0
0 0 0
0 0 0
0 0 1. Conversion complete 0
ISR(ADC_vect)  0 0 Start 2. conversion (on ADMUX ch) 0
Read 1. conversion result (ch0) 0 0 Converting… 0
adc_selector++ 1 0 0
Update ADC Channel MUX 1 1 0
  1 1 0
1 1 2. Conversion complete 0
ISR(ADC_vect)  1 1 Start 3. conversion (on ADMUX ch) 1
Read 2. conversion result (ch0) 1 1 Converting… 1
adc_selector++ 2 1 1
Update ADC Channel MUX 2 2 1
  2 2 1
2 2 3. Conversion complete 1
ISR(ADC_vect)  2 2 Start 4. conversion (on ADMUX ch) 2
Read 3. conversion result (ch1) 2 2 Converting… 2
adc_selector++ 3 2 2
Update ADC Channel MUX 3 3 2
  3 3 2
3 3 4. Conversion complete 2
ISR(ADC_vect)  3 3 Start 5. conversion (on ADMUX ch) 3
Read 4. conversion result (ch2) 3 3 Converting… 3
adc_selector++ 4 3 3
Update ADC Channel MUX 4 4 3
  4 4 3
4 4 5. Conversion complete 3
ISR(ADC_vect)  4 4 Start 6. conversion (on ADMUX ch) 4
Read 5. conversion result (ch3) 4 4 Converting… 4
adc_selector++ (back to 0) 0 4 4
Update ADC Channel MUX 0 0 4
  0 0 4
0 0 6. Conversion complete 4
ISR(ADC_vect)  0 0 Start 7. conversion (on ADMUX ch) 0
Read 6. conversion result (ch4) 0 0 Converting… 0
adc_selector++ 1 0 0
Update ADC Channel MUX 1 1 0
  1 1 0
1 1 7. Conversion complete 0
ISR(ADC_vect)  1 1 Start 8. conversion (on ADMUX ch) 1
Read 7. conversion result (ch0) 1 1 Converting… 1
adc_selector++ 2 1 1
Update ADC Channel MUX 2 2 1
  2 2 1
2 2 8. Conversion complete 1

It all clear now isn't it :-)

 

AttachmentSize
ATMega8-ADC-test.c4.83 KB

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

For Arduino / AVR users probably the simplest solution is to use the internal pullup resistors so that unused analog inputs are pulled high.

For Arduino users this is simply a matter of performing a digital write to the Analog pin in their setup.

Thank You Nuumio for the fast answer. I have tried with big hysteresis at the center, and even added 1nF caps between signal and ground on each pot, and tried calculating the average of 10 readings, but with limited succsess. I think you are right that the problem might be the joysticks. That's why I ordered some more from Sweden last week, and hopefully they will arrive tomorrow.

Maybe you could use multimeter to check the output voltage from your joysticks and rule out problems on the Picaxe side (either HW or SW). If I have understood correctly adding caps between signal and ground will "slow down" the signal change. Although 1nF sounds very small so it probably wouldn't affect the change rate too much, I guess (I'm still at very beginner level on electronics). Maybe someone who actually knows these thing could answer :-)

 

Okay, this might just go over my head a bit. :)

Let us assume that I use a microcontroller with configurable ADC ports, such as a Picaxe 28x2. Should I tie all unused ADC inputs to ground, although I have configured them in the code to not be inputs?(with the adcsetup-command)

I have some problems with two thumb-joysticks(four pots), giving me crazy values. They are starting to "lean" to either side while the pot is in the center. And after they have been to either side, they tend to lean that way.

 

 

Manual 1 says this about adcsetup: "Using adcsetup does NOT actually ‘connect’ the internal adc to the input pin - the adc is always connected. Using adcsetup just disconnects the digital input buffer, so that the internal digital input circuitry does not affect the analogue reading." If floating ADC inputs really affect the value of connected (meaning connected to for example potentiometer) then those pin will affect your readings regardless. Using adcsetup would not change it.

However, I have not experienced this myself (floating inputs affecting values of connected ones). The trick my floating ADC inputs played on me was that I was reading the values of floating inputs when I shouldn't have read them. If I had just disregarded those values everything would have been ok. Values from connected ADC input seemed to be ok.

If I had to guess I'd say the problem is with your joysticks. Maybe they just don't center themselves exactly at the same position every time. You might need some hysteresis at the center position if you haven't already tried that. For example with 10-bit ADC values center position would be range from 500 to 524 (choose a range that fits your needs) instead of exact value of 512.

:)

From the Start Here:


Why this? A brief and not very scientific explanation is; these 4 inputs (0, 1, 2 and 3) are analogue. Which means they measure "how much pressure is on the line". However, they are connected, if they like it or not. And so, a little pressure on one of them actually does something to the next. They are "left floating". By tying the 3 that we do not use to V, they are just returning "full value", and they are not left floating. So the last one, number 0, that we use, is way more accurate.

I have not read documentation that tells you to do this, however, I have at several occasions experienced strange readings, until I tied all unused analogue pins to either ground or V. Oh... and in fact I am writing documentation here (sort of :) So now it is written in the documentation to do this! :)

SH Bot always knows better. Maybe I should build one :-) Oh well, my Beep is close enough.

Just out of interest I tried this with Picaxe 28x2 on 28 project board and it behaves similarly. When I have V1+ on analogue input 0 (others left floating) and run this code:
main:
readadc10 0, w0
readadc10 1, w1
readadc10 2, w2
readadc10 3, w3
debug
goto main


This is what I get:

I'm too lazy to check PIC18F2520 datasheet right now but I'm guessing its ADC works much in the same way: It has one comparator and channel is connected to it (though multiplexer) when the channel's value is wanted. If the channel connected to the comparator it tied to V1+ (or some voltage X) then the comparator is "charged" to V1+ (or some voltage X). Then if a floating channel is read right after that some charge from the previous sample still remains in the comparator and that's why it shows "a weird value". How's that for an explanation? :-)

You can even try something like this and see what happens (potentiometer on ADC0 input is nice if you want different "real" readings on ADC0):
main:
readadc10 0, w0
readadc10 1, w1
readadc10 1, w2
readadc10 1, w3
readadc10 1, w4
readadc10 1, w5
readadc10 1, w6
readadc10 1, w7
readadc10 1, w8
readadc10 1, w9
readadc10 1, w10
readadc10 1, w11
readadc10 1, w12
readadc10 1, w13
readadc10 1, w14
readadc10 1, w15
readadc10 1, w16
readadc10 1, w17
readadc10 1, w18
debug
goto main