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

ATMega8-ADC-test.c (4950Bytes)

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.

hammer-shaky-front-01-small.jpg


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 :slight_smile:

 

**:)From the Start Here:Why **

:slight_smile:

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 :slight_smile: So now it is written in the documentation to do this! :slight_smile: