NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Sensors, Actuators, and Robotics » Sampling from several different analog inputs?

April 08, 2009
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

One common question we get is how to read from several different analog sensors at once. For example, if you are using one microcontroller to read the temperature in two different rooms and control the heating system appropriately, you'll need to read from two different sensors.

To switch between different analog inputs, all you have to do is set the ADMUX register to point to whichever ADC input you'd like. For example:

uint16_t sensor1, sensor2;
int16_t difference;
while(1) {
  // read sensor 0 (on pin PC0)
  ADMUX=0;
  sensor1 = adc_read();

  // read sensor 1 (on pin PC1)
  ADMUX=1;
  sensor2 = adc_read();

  difference = sensor1 - sensor2;
  lcd_home();
  fprintf_P(&lcd_stream, PSTR("A = %d, B = %d"), sensor1, sensor2);
  lcd_line_two();
  fprintf_P(&lcd_stream, PSTR("A-B = %d"), difference);
}

This will keep updating the screen with the two analog-to-digital results, as well as the difference between them. Techniques like this can come up in feedback systems.

Look at page 257 of the ATmega168 datasheet for the full description of the ADMUX register, and let me know if you have questions!

Mike

May 02, 2009
by CJS
CJS's Avatar

Hi all, I am trying to use the AVR to read multiple sensor values via the ADC.

No matter what I measure, or how, I seem to get high readings on all channels, even though I only have, say 4V applied to channel 1, and the rest are floating, or set to ground.

I thought it was a short at first, or that the ADMUX was not updating. I have tried grounding unused pins, using a pull-down resistor of 1 Mohm and witchcraft of various kinds.

I was using interrupts, then I changed my code thinking the problem was with them, but alas, no. I have even re-built my circuit on a breadboard in case it was bad soldering or something.

Anyway, here is an example output (via the UART) when I put 5V on a single ADC channel, and the rest are floating. Even with a pull down resistor, the other values still go high, so it is not the floating that is the problem:

1024 968 1000 895

As you can see, for an input on one channel of around 5V, ALL the ADC channels read high. The same happens I think with 2.5 V, only all the channels read mid-level. I tried averaging multiple readings, but it did not help.

The code is crappy 'cos I rewrote it as basic as possible to debug.

[code] uint16_t ADCresult[4]; //stores ADC results uint8_t dataFlag = 0; //when all four sensors have been read, set flag

void adc_init() { // set analog to digital converter // for external reference (5v) ADMUX = 0 ;

// set analog to digital converter // to be enabled, with a clock prescale of 1/128 // so that the ADC clock runs at 115.2kHz.

ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

ADCSRA |= (1<<ADSC);

}

uint16_t getADC() {

while (ADCSRA & (1<<ADSC)) {
    // wait...
}
 uint16_t result = ADCL;
uint16_t resultHigh = ADCH;
result = result + (resultHigh<<8);

ADCSRA |= (1<<ADSC); //start next conversion on new channel

return result; }

int main() {

uart_init();

// init serial port

FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
stdin = stdout = &uart_stream;

adc_init(); //start ADC

while (1) {
printf_P(PSTR("%c"), 'A');
        printf_P(PSTR("%c"), 'D');
        printf_P(PSTR("%c"), 'C');
        printf_P(PSTR("%c\t"), '_');
        printf_P(PSTR("%c"), 'R');
        printf_P(PSTR("%c"), 'e');
        printf_P(PSTR("%c"), 's');
        printf_P(PSTR("%c"), 'u');
        printf_P(PSTR("%c"), 'l');
        printf_P(PSTR("%c\t\r"), 't');

ADMUX = 0x00;
ADCresult[0] = 0;
ADCresult[0] = getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] += getADC();
ADCresult[0] /=10;
printf_P(PSTR("%d\r\n"), ADCresult[0]);

ADMUX = 0x01;
ADCresult[1] = 0;
ADCresult[1] = getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] += getADC();
ADCresult[1] /=10;
printf_P(PSTR("%d\r\n"), ADCresult[1]);

ADMUX = 0X02;
ADCresult[2] = 0;
ADCresult[2] = getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] += getADC();
ADCresult[2] /=10;
printf_P(PSTR("%d\r\n"), ADCresult[2]);

ADMUX = 0x03;
ADCresult[3] = 0;
ADCresult[3] = getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] += getADC();
ADCresult[3] /=10;
printf_P(PSTR("%d\r\n"), ADCresult[3]);

}

If someone could let me know if they see any obvious, glaring errors, I would be grateful!

May 02, 2009
by mrobbins
(NerdKits Staff)

mrobbins's Avatar

Hi CJS,

One thing I noticed about your code is that the alignment of the ADSC (start conversion) and the ADMUX changes is off from what you are expecting. Notice that getADC waits for the reading to finish, and then starts the next conversion. So for the very first getADC call of each ADCresult slot, you're actually getting the value returned from the previous ADMUX setting. This was done for the tempsensor project so that the conversion could mostly run "in the background", but in this case it might be confusing so you might want to switch the order of the getADC function around, so it first starts the conversion and only then waits for it to finish.

Also, if you take a quick look at Figure 23-1 on page 246 of the ATmega168 datasheet, you'll see an overview of how the ADC works. After the multiplexer (MUX), there's something called a "sample and hold comparator". It isn't explicitly written in the datasheet, but what "sample & hold" means is that there's essentially a capacitor that gets charged up to match the voltage of the input, so that the exact same voltage gets used for the full duration of the measurement. If you are pointing the mux to floating inputs, there's not necessarily any new voltage to change whatever's stored in the sample and hold capacitor, so I'm not all that surprised that the voltage does not change.

However, if you have for example PC0 connected to +5 and PC1 connected to GND and are still getting readings like this, then something is definitely strange.

Another thing to try would be to use a multimeter and actually measure the voltage on those pins, which might help you rule out whether anything fishy is going on electrically. Let us know how this all turns out -- I'll be happy to help you debug this further.

Mike

May 05, 2009
by CJS
CJS's Avatar

Hi, Thanks for the tip. I changed the software.

I used pull down resistors because when nothing was connected the ADC floated around 350 or so and when even 1 pin was connected they all went high.

Given that you said that the ADC uses caps to do the hold and sample, I thought maybe the pulldown resistors were too high, so I used smaller 4k ones. This worked when measuring 5V from a power supply - only the pins I touched with the lead went high, the rest stayed at zero.

The problem is that when I connected some phototransistors the reading from them was only around 30 on the ADC when light was shined directly on them. When I measure the output of the phototransistors with a DMM they show 5V, but it seems when connected via the pulldown resistors they show hardly anything.

So, to solve my problem I need to use smaller resistors, yet I get nothing from the sensors. To get more measured from the sensors I use larger resistors, and am back to all the ADC channels reading high, even if only 1 is connected.

I have another chip I will try tonight and see if it works - I could have fried something on my original one without realizing it.

Thanks for your help with this, I appreciate it!

April 04, 2010
by Ralphxyz
Ralphxyz's Avatar

I have inserted the code sample and get the two sensor reading but the readings do not make sense. I have a associated thread in the Support form with the code links.

When sensor A is at VCC I get 153 and with sensor B is at GND I get 0.

This implies sensor B might be correct but what about sensor A that should be around 495.

Thanks for any help,

Ralph

April 04, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Ralph,

The fact that you are getting some reading, but not quite you expect is odd. Have you tried removing the temperature sensors and then connecting the ADC input pins to +5, then to GND and seeing if you get anything? Are you sure the power connections to the right side of the chip are correct. These are the reference voltages to ADC, so a missconnection there could cause what you are seeing. I would suggest removing temperature converting code and just printing out the raw ADC values until you get numbers that you can make sense out of.

If you can, post pictures of your setup, perhaps we can spot something amiss.

Humberto

December 15, 2010
by kemil
kemil's Avatar

With regards to Mikes initial post, i see you have set ADMUX to a pin and then called the adc_read() function. Does this mean you have not given ADMUX a value within the adc_read function its self. Or by giving a value to admux before you call the functon are you over riding any value previously given to ADMUX.

The reason i ask is the in the temsensor sourse code you set ADMUX to a pin within the adc_read() function. I was wondering if that is no longer necessary when you are dealing with multiple sensors. theremin.c deals with multiple sensors in a much more confusing way...

kemil

December 15, 2010
by Ralphxyz
Ralphxyz's Avatar

Here is my multi sensor code. I have not used this in a couple of months but I believe this is working code... This is just modified Nerdkit tempsensor project.

 while(1) {
    // take 100 samples and average them!
    temp_avg = 0.0;
    uint8_t mux;            // place holder for the ADMUX value
    uint8_t delta = 0;
    //char sensor[1];
    for(mux=0; mux<=5; mux++)
        {
            ADMUX=mux;
            temp_avg = 0;    // re-set temp_avg to 0 for each new ADMUX value!

                for(i=0; i<100; i++) 
                {
                    last_sample = adc_read();
                    this_temp = sampleToFahrenheit(last_sample);

                    // add this contribution to the average
                    temp_avg = temp_avg + this_temp/100.0;

                    switch(mux)
                    {
                        case 0:
                            sensor0 = temp_avg;
                            break;
                        case 1:
                            sensor1 = temp_avg;
                            break;
                        case 2:
                            sensor2 = temp_avg;
                            break;
                        case 3:
                            sensor3 = temp_avg;
                            break;
                        case 4:
                            sensor4 = temp_avg;
                            break;
                        case 5:
                            sensor5 = temp_avg;
                            break;
                    }       
                    //delay_ms(1000);

                }

        }    
            if(sensor1 > delta || sensor1 < delta)   // this probbale does not make sence or any difference
                {
                // write message to LCD
//*             
                lcd_line_one();
                lcd_write_string(PSTR("Sensor:")); 
                lcd_line_two();
                fprintf_P(&lcd_stream,PSTR(" 0=%d"), sensor0);
                //fprintf_P(&lcd_stream,PSTR(" 0=%.2f"), sensor0);
                lcd_write_data(0xdf);
                lcd_write_data("f");
                fprintf_P(&lcd_stream,PSTR(" 1=%df"), sensor1);
                //fprintf_P(&lcd_stream,PSTR(" 1=%.2f"), sensor1);
                lcd_write_data(0xdf);
                lcd_line_three();
                fprintf_P(&lcd_stream,PSTR(" 2=%df"), sensor2);
                //fprintf_P(&lcd_stream,PSTR(" 2=%.2f"), sensor2);
                lcd_write_data(0xdf);
                fprintf_P(&lcd_stream,PSTR(" 3=%df"), sensor3);
                //fprintf_P(&lcd_stream,PSTR(" 3=%.2f"), sensor3);
                lcd_write_data(0xdf);
                lcd_line_four();
                fprintf_P(&lcd_stream,PSTR(" 4=%df"), sensor4);
                //fprintf_P(&lcd_stream,PSTR(" 4=%.2f"), sensor4);
                lcd_write_data(0xdf);
                fprintf_P(&lcd_stream,PSTR(" 5=%df"), sensor5);
                //fprintf_P(&lcd_stream,PSTR(" 5=%.2f"), sensor5);
                lcd_write_data(0xdf);
                delay_ms(3000);
                //lcd_init();

        delta = sensor1;        // this probbale does not make sence or any difference
            }
        }

  return 0;
}
December 15, 2010
by Ralphxyz
Ralphxyz's Avatar

I suppose I should add:

    // PIN DEFINITIONS:
    //
    // PC0 -- temperature sensor analog input
    // PC1 -- temperature sensor analog input
    // PC2 -- temperature sensor analog input
    // PC3 -- temperature sensor analog input
    // PC4 -- temperature sensor analog input
    // PC5 -- temperature sensor analog input

//*    DECIMAL Output
  uint16_t sensor0 = 0; 
  uint16_t sensor1 = 0; 
  uint16_t sensor2 = 0; 
  uint16_t sensor3 = 0; 
  uint16_t sensor4 = 0; 
  uint16_t sensor5 = 0;
  int16_t difference = 0;
//*/

Ralph

December 15, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi kemil,

ADMUX is a register on the chip, which means it is globally accessible and remains set after you set it to something, even if you jump to another function. The last value ADMUX was set to when the analog to digital converter is told to start a conversion dictates which pin the value will be read from. Does that make sense?

theremin.c does take a sightly different approach to setting the ADMUX register. Where in most other videos we set ADMUX first, and then make the call to adc_Read() to start a conversion, in theremin.c we pass a value into the adc_read() function as an argument. That way we can make the call to adc_read() and tell it which pin to read from. All these approaches are basically equivalent. Hope that helps.

Humberto

December 15, 2010
by kemil
kemil's Avatar

I understand the first part. Im just struggling to see exactly where in the theremin.c script mux is given a numerical value.

kemil

December 15, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi kemil,

In theremin.c the hand position is handled by the main while loop. On line 146 and 147 it does two calls to adc_average()

hand_position_0 = adc_average(0, 25);
hand_position_1 = adc_average(1, 25);

notice that the first time it calls it with the first parameter 0, and the second time with the parameter 1. The adc_average function then turns around and calls adc_read() with the the same value it was passed. Does that make sense?

Humberto

December 16, 2010
by kemil
kemil's Avatar

Ah i see! Thanks Humberto

August 31, 2011
by devinsbusiness
devinsbusiness's Avatar

Hi All,

In my current project I am trying to sense both temperature using a temp sensor and voltage from a boost converter at the same time. I have used a voltage divider circuit to reduce the boost converter voltage down to something the MCU can handle and have come up with a division factor that gives me a reasonably accurate voltage measurement from the boost converter. All of these things work independently of one another, but when I try to marry them all together I am really having some frustrating problems. I read Ralph's code (thank you Ralph for all of the help and information you share), and I admittedly don't know/understand enough about writing code yet to be able to modify or glean from it what I need. I have tried to simply point to the pertinent ADMUX and assign that reading to the appropriate variable as Mike demonstrated in his post. When I do this, I get output, but it is totally inaccurate as long as both sensors are connected to their respective pins. But when I disconnect the temp sensor (ADMUX 0) from it's pin, the voltage reading (ADMUX 1) becomes accurate. Any tips/pointers/lessons would be greatly appreciated.

Here is what I have for code:

  #define F_CPU 11059200
  #include <stdio.h>
  #include <math.h>
  #include <avr/io.h>
  #include <avr/interrupt.h>
  #include <avr/pgmspace.h>
  #include <inttypes.h>
  #include "../libnerdkits/delay.h"
  #include "../libnerdkits/lcd.h"
  #include "../libnerdkits/uart.h"

 // PIN DEFINITIONS:
 //
 // PB2 -- OC1B -- BOOST CONVERTOR high-speed switchy thing (nFET gate)
 //     used to generate the high voltage supply

 void adc_init() {
 // set analog to digital converter
 // for external reference (5v), single ended input ADC0
 //ADMUX = 0;

 // set analog to digital converter
 // to be enabled, with a clock prescale of 1/128
 // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

 // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
  }

  uint16_t adc_read() {
 // read from ADC, waiting for conversion to finish
 // (assumes someone else asked for a conversion.)
 // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
 // do nothing... just hold your breath.
  }
 // bit is cleared, so we have a result.

 // read from the ADCL/ADCH registers, and combine the result
 // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t high = ADCH;
  result = result + (high<<8);

 // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  return result;
  }
 double sampleToFahrenheit(uint16_t sample) {
 // conversion ratio in DEGREES/STEP:
 // (3280 mV / 1024 steps) * (1 degree / 10mV)
 // ^^^^^^^^^^^      ^^^^^^^^^^
 //     from ADC          from LM34
  return sample * (3280.0 / 1024.0 / 10.0);  
  }

 double sampleToVolts(uint16_t volt_sample) {
 //conversion ratio in DEGREES/STEP:
 // (3280 mV / 1024 steps) *(1 volt / 106.8mV)
 //    ^^^^^^^^^^^             ^^^^^^^^^^
 //     from ADC            from volt divider
  return volt_sample * (3280. / 1024.0 / 106.8 );
  }

  int main() {

 // init serial port
 uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

 // start up the LCD
   lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
   lcd_home();

 // start up the Analog to Digital Convertor FOR VOLTAGE SENSOR

 // holder variables for temperature data
  uint16_t last_sample = 0;
  double this_temp;
  double temp_avg;
  uint8_t i;

 // holder variables for VOLTAGE data
  uint16_t last_volt_sample = 0;
  double this_volt;
  double volt_avg;
  uint8_t v;

 // PB2 as output
  DDRB |= (1<<PB2);

 //FUNCTION TO ACTIVATE BOOST CONVERTOR
  void boost_on(){
 //SET fPWM
  OCR1A = 10;
 //SET DUTY CYCLE
  OCR1B = 5;
 // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10);
  }

 //FUNCTION TO TURN OFF BOOST CONVERTOR
  void boost_off(){
 // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = ~(1<<COM1B1) | ~(1<<WGM11) | ~(1<<WGM10);
  TCCR1B = ~(1<<WGM13) | ~(1<<WGM12) | ~(1<<CS10);
  }

  double freq_a;
 // main loop
  while(1) {
  boost_on();

  ADMUX = 0;
  adc_init(); 
 last_sample = adc_read();

  ADMUX = 1; 
  adc_init();
 last_volt_sample = adc_read();

 temp_avg = 0.0;
   for(i=0; i<100; i++) {
 //last_sample = adc_read();
  this_temp = sampleToFahrenheit(last_sample);
 // add this contribution to the average
  temp_avg = temp_avg + this_temp/100.0;
  }

 // take 100 samples and average them!
 volt_avg = 0.0;
 for(v=0; v<100; v++) {
 //last_volt_sample = adc_read();
 this_volt = sampleToVolts(last_volt_sample);
//add this contribution to the average
volt_avg = volt_avg + this_volt/100.0;
  }

lcd_home();
fprintf_P(&lcd_stream, PSTR("   Volts: %.2f"), volt_avg);
lcd_line_two();
fprintf_P(&lcd_stream, PSTR("ADMUX 1:%d"), last_volt_sample);
lcd_line_three();
fprintf_P(&lcd_stream, PSTR("TEMP:%d"), temp_avg);
lcd_line_four();
fprintf_P(&lcd_stream, PSTR("ADMUX 0:%d"), last_sample);

//if( volt_avg < 17){
//boost_on();
//}
//else{
//boost_off();
//}

  }

  return 0;
  }
September 01, 2011
by bretm
bretm's Avatar

The adc_read routine returns the result of the conversion that was started the previous time you called it, so your two results will be switched around. The first post in this thread only works because it's only using the difference, not the individual results. The adc_read from the tempsensor is not suitable for multi-channel use unless you realize the multi-channel implications.

September 02, 2011
by devinsbusiness
devinsbusiness's Avatar

Thanks bretm,

Per what you said "The adc_read from the tempsensor is not suitable for multi-channel use unless you realize the multi-channel implications.", I created two separate "adc_read" functions for each sensor in which I assigned the ADMUX for each sensor within the read function itself.

ADMUX = 0;
// set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.
  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t high = ADCH;
  result = result + (high<<8);
  return result;
}
  //******TRY WRITING A SPECIAL ADC READ FOR VOLTS AND TEMP INDEPENDENTLY, THEN CALL EACK ONE IN THE WHILE LOOP
uint16_t adc_volt_read() {
ADMUX = 1;
// set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.
  // read from the ADCL/ADCH registers, and combine the result
  uint16_t volt_result = ADCL;
  uint16_t high = ADCH;
  volt_result = volt_result + (high<<8);
  return volt_result;
}

This has fixed the sensor read problem. The voltage displayed on my LCD matches my multimeter very closely. And the value displayed for last_volt_sample is fairly stable. So the voltage sensing part of my project works. Now the problem is that the last_sample (temp sensor) reading is stable and apparently accurate when I do the math manually. But the temp_avg and this_temp values are not even close. They are wide ranging, random, rapidly changing numbers. I don't get it. Any help would do wonders for saving my sanity. Here is my code in it's entirety:

#define F_CPU 11059200
#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PB2 -- OC1B -- BOOST CONVERTOR high-speed switchy thing (nFET gate)
//      used to generate the high voltage supply

void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {

ADMUX = 0;

// set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  uint16_t result = ADCL;
  uint16_t high = ADCH;
  result = result + (high<<8);

  return result;
}
  //******TRY WRITING A SPECIAL ADC READ FOR VOLTS AND TEMP INDEPENDENTLY, THEN CALL EACK ONE IN THE WHILE LOOP
uint16_t adc_volt_read() {

ADMUX = 1;

// set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);
  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  uint16_t volt_result = ADCL;
  uint16_t high = ADCH;
  volt_result = volt_result + (high<<8);

  return volt_result;
}
double sampleToFahrenheit(uint16_t sample) {
  // conversion ratio in DEGREES/STEP:
  // (3280 mV / 1024 steps) * (1 degree / 10mV)
  //    ^^^^^^^^^^^      ^^^^^^^^^^
  //     from ADC         from LM34
  return sample * (3280.0 / 1024.0 / 10.0);  
}

 double sampleToVolts(uint16_t volt_sample) {
//conversion ratio in DEGREES/STEP:
// (3280 mV / 1024 steps) *(1 volt / 106.8mV)
//    ^^^^^^^^^^^             ^^^^^^^^^^
//     from ADC            from volt divider
return volt_sample * (3280. / 1024.0 / 106.8 );
}

int main() {

  adc_init();

// init serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

// start up the Analog to Digital Convertor FOR VOLTAGE SENSOR

// holder variables for temperature data
  uint16_t last_sample = 0;
  double this_temp;
  double temp_avg;
  uint8_t i;

  // holder variables for VOLTAGE data
  uint16_t last_volt_sample = 0;
  double this_volt;
  double volt_avg;
  uint8_t v;

  // PB2 as output
  DDRB |= (1<<PB2);

  //FUNCTION TO ACTIVATE BOOST CONVERTOR
  void boost_on(){
  //SET fPWM
  OCR1A = 10;
  //SET DUTY CYCLE
  OCR1B = 5;
   // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10);
  }

  //FUNCTION TO TURN OFF BOOST CONVERTOR
  void boost_off(){
  // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = ~(1<<COM1B1) | ~(1<<WGM11) | ~(1<<WGM10);
  TCCR1B = ~(1<<WGM13) | ~(1<<WGM12) | ~(1<<CS10);
  }

  while(1) {
  //TURN ON THE BOOST CONVERTER
  boost_on();

    temp_avg = 0.0;
    for(i=0; i<100; i++) {
      last_sample = adc_read();
      this_temp = sampleToFahrenheit(last_sample);
       // add this contribution to the average
      temp_avg = temp_avg + this_temp/100.0;
    }

     // take 100 samples and average them!
    volt_avg = 0.0;
    for(v=0; v<100; v++) {
    last_volt_sample = adc_volt_read();
    this_volt = sampleToVolts(last_volt_sample);
    //add this contribution to the average
    volt_avg = volt_avg + this_volt/100.0;
    }

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("VOLTS: %.1f"), volt_avg);
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("ADMUX 1:%d"), this_volt);
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("TEMP:%d"), temp_avg);
    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("this temp:%d"), this_temp);

    //if( volt_avg < 25){
    //boost_on();
    //}
    //else{
    //boost_off();
    //  
    }
  return 0;
}
September 02, 2011
by bretm
bretm's Avatar

You don't need two separate functions--just set ADMUX before calling the corrected adc_read. Your correction was to start the conversion when you enter the function and not start another one as you exit the function, like the temp_sensor project does.

It would be even better if you get rid of lines 28 and 29.

It's not obvious by looking at the code that the wild data is a code problem. Are you sure the sensor is wired to ADC0 correctly?

September 03, 2011
by devinsbusiness
devinsbusiness's Avatar

I checked the datasheets for the MCU and LM34 and I have it wired properly. Also, I am getting good values when I check the last_sample and last_volt_sample variables. This leads me to believe that the ADC part is working and for some reason the temp_avg value is not resetting back to 0.0 before it goes into the averaging loop? I don't understand that though because I address that on line 144, further, this process is working for the voltage sensing part of the program. Or could it be that somehow there is a problem between last_sample and sampleToFahrenheit?

Thanks, Devin

September 03, 2011
by bretm
bretm's Avatar

Are you reading reasonable voltages from the LM34 with a multimeter?

September 03, 2011
by devinsbusiness
devinsbusiness's Avatar

Yes. With the LM34 disconnected from it's MCU pin I read 794 mV. With it connected to the MCU I read a value for last_sample of around 250. So 250*3280/1024=800.78 mV. So this is reasonably close. Also to check this I measured the temperature with an instant read thermometer to be 81 degrees. So 800.78/10=80 degrees sensed by the temp sensor. That is reasonably close. This is why this is so confusing to me. The ADC is putting out the right values but somewhere the formulas aren't converting them correctly.

September 09, 2011
by devinsbusiness
devinsbusiness's Avatar

Hello All,

For anyone who may be interested in this topic, it now works. My circuit and program now read temperature and boosted voltage accurately! I borrowed Ralph's idea of using a switch case to assign the values of the ADC readings to the appropriate variable (Thanks Ralph). I was still getting a strange output for temp_avg, so I tried several different things (shooting in the dark really) to try to cure the problem. I finally swapped which pins the two sensors were attached/assigned to and that fixed it! If anyone can explain why that is what it took to fix the problem for future knowledge, that would great. Here is the code in full for anyone interested:

#define F_CPU 14745600

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"

// PIN DEFINITIONS:
//
// PC0 -- VOLTAGE sensor analog input
// PC1 -- TEMPERATURE sensor analog input
// PB2 -- PWM-CONTROL FOR BOOST CONVERTER

//variable for ADMUX setting
  uint8_t mux;
void adc_init() {
  // set analog to digital converter
  // for external reference (5v), single ended input ADC0
  //ADMUX = 0;

  // set analog to digital converter
  // to be enabled, with a clock prescale of 1/128
  // so that the ADC clock runs at 115.2kHz.
  ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);

  // fire a conversion just to get the ADC warmed up
  //ADCSRA |= (1<<ADSC);
}

uint16_t adc_read() {

  ADMUX = mux;

  // set ADSC bit to get the *next* conversion started
  ADCSRA |= (1<<ADSC);

  // read from ADC, waiting for conversion to finish
  // (assumes someone else asked for a conversion.)
  // wait for it to be cleared
  while(ADCSRA & (1<<ADSC)) {
    // do nothing... just hold your breath.
  }
  // bit is cleared, so we have a result.

  // read from the ADCL/ADCH registers, and combine the result
  // Note: ADCL must be read first (datasheet pp. 259)
  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  return result;
}

double sampleToMv(uint16_t sample) {
  // conversion ratio in DEGREES/STEP:
  // I AM USING A REGULATED 3.28 VOLTS TO POWER MY MCU 
  // (3280 mV / 1024 steps)

       return sample * (3280.0 / 1024.0);  
}

int main() {
  // start up the LCD
  lcd_init();
  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
  lcd_home();

  // start up the Analog to Digital Converter
  adc_init();

  // start up the serial port
  uart_init();
  FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);
  stdin = stdout = &uart_stream;

  // holder variables for temperature data
  uint16_t last_sample = 0;
  double this_Mv;
  double Mv_avg;
  uint8_t i;

  //variables for individual sensors
  uint16_t temp_sensor;
  uint16_t volt_sensor;
  uint16_t temp_avg;
  double volt_avg;

  DDRB |= (1<<PB2);

  //FUNCTION TO ACTIVATE BOOST CONVERTOR
  void boost_on(){
  //SET fPWM
  OCR1A = 10;
  //SET DUTY CYCLE
  OCR1B = 5;
   // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = (1<<COM1B1) | (1<<WGM11) | (1<<WGM10);
  TCCR1B = (1<<WGM13) | (1<<WGM12) | (1<<CS10);
  }

  //FUNCTION TO TURN OFF BOOST CONVERTOR
  void boost_off(){
  // PWM FAST MODE W PRESCALE OF 1
  TCCR1A = ~(1<<COM1B1) | ~(1<<WGM11) | ~(1<<WGM10);
  TCCR1B = ~(1<<WGM13) | ~(1<<WGM12) | ~(1<<CS10);
  }

  while(1) {

  boost_on();
    // take 100 samples and average them!
    //Mv_avg = 0.0;
    for(mux=0; mux<=1; mux++) 
    {
        ADMUX=mux;
        Mv_avg = 0;

      for(i=0; i<100; i++) 
       {
        last_sample = adc_read();
        this_Mv = sampleToMv(last_sample);

        //add this contribution to the average
        Mv_avg = Mv_avg + this_Mv/100.0;

         switch(mux)
         {

          case 0:
            //temp_sensor = Mv_avg;
            volt_sensor = Mv_avg;
            break;
          case 1:
            //volt_sensor = Mv_avg;
            temp_sensor = Mv_avg;
            break;

          }
        }   
    }

    temp_avg = temp_sensor/10.0;

    //THIS DIVISION FACTOR WILL VARY DEPENDING ON YOUR VOLTAGE DIVIDER CONFIGURATION
    volt_avg = volt_sensor/106.8;

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("VOLTS: %.1f"), volt_avg);
    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("ADMUX 1:%d"), volt_sensor);
    lcd_line_three();
    fprintf_P(&lcd_stream, PSTR("TEMP:%d"), temp_avg);
    lcd_line_four();
    fprintf_P(&lcd_stream, PSTR("ADMUX 0:%d"), temp_sensor);

    //if( volt_avg < 25){
    //boost_on();
    //}
    //else{
    //boost_off();
    //}

    }

  return 0;
}

Hopefully this code can be of use to someone else in the future.

Thanks to all who contribute to this forum for the help.

Devin

September 14, 2011
by BobaMosfet
BobaMosfet's Avatar

I found it simpler, for single ended inputs, to simply modify the adcMUXInit() function slightly:

void adcMUXInit(channel)
    {
    ADMUX = channel;   // Set the channel

    ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
    ADCSRA |= (1<<ADSC);
    }

BM

Post a Reply

Please log in to post a reply.

Did you know that spinning a permanent magnet motor makes a voltage? Learn more...