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.

Microcontroller Programming » Storing ADC Data use as a limit

March 06, 2012
by stanvan
stanvan's Avatar

Hi,

I'm a beginner looking for help. I want to store the initial reading from the ADC. and use it as an upper limit to turn a motor on and off. Any help would be appreciated. Thank you

Stan

March 09, 2012
by JimFrederickson
JimFrederickson's Avatar

What do you mean 'store'?

Do you mean that when your Microcontroller starts you want to read the ADC Value and then store that until the Microcontroller is powered down or reset?

Define some variable Read the ADC Value Store the result....

So something like this...

    uint16_t curr_adc_value;
    uint16_t first_adc_value;

    first_adc_value = adc_read();

    while(1) {
        curr_adc_value = adc_read();

        if (curr_adc_value > first_adc_value) {
            motor_turnon();
            }
        else {
            motor_turnoff();
            }

        delay_ms(10000);
        }

This should work in the 'simplest form'... (This is not a complete program only a snippet for the part you asked about. You will still need to initialize everything else that is necessary.)

These are the main steps:

1 - Basically it will read the ADC Value.

2 - Enter into a 'forever loop' and read the current ADC Value. If it is above the first value read then turn the motor on, if it is below the value then turn the motor off.

3 - Wait some period of time. Before checking again. Here it just waits for 10,000ms, so 10 seconds. You don't want to be rapidly turning the motor, on and off over and over... There needs to be some 'duty cycle'. Also it is possible that 'turning the motor on/off can affect your circuit too. So waiting will help for things to 'settle'.

For me.

I would complicate this considerably.

The minimum complication I would make is to read the ADC 5 times before using the value. I would throw out the largest value and the smallest value and average the 3 remaining values.

Why?

Because that would, hopefully, get rid of any anomalies/problems in the values. So if there is a problem in your circuit and a value is misread it won't have an affect, or at the very least as much of an affect.

I would probably also get the first_adc_value by reading 10 times throwing out the highest and lowest values and then average the remaining 8 values. Maybe over 30 seconds or something like that.

An alternate method would be to determine some "maximum rate of change"/"maximum delta" in the value that you are happy with, and then only adjust by value within that maximum rate.

March 10, 2012
by stanvan
stanvan's Avatar

Jim,

Thank you for your response. My hope is when I turn the processor on to save the ADC value and use it as an upper limit as you described. Then set a difference value as a lower limit.

I was using the Nerd Kits ATmega168 micro controller and went through a lot of the projects and tutorials with success.

I'm currently using an ATmegam32 micro controller. So far I've spent most of my time working on the the LCD display. Using the data sheet I've been switching the programs over. The acd_read is one I haven't gotten to work.

Below is the bare code I've been using for reading the ACD. Is there any way you tell me how to make what you described work with what i have or lead to to a somewhere to find the information.

Thank you again, Any help you could lend is greatly appreciated.

Stan

//ADC Code
#include <stdlib.h>
#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <math.h>
#include "MyLCD.h"

uint16_t LowLimit = 50; 
// Difference value.
// Set with up/dn button too turn the motor on.

uint16_t Upper = 800; 
// Held adc value to turn the motor off.

int main(void)
{
InitializeMyLCD();

Send_A_StringToMyLCDwithLocation(3,1,"ACD = ");
Send_A_StringToMyLCDwithLocation(3,2,"Diff = ");
Send_A_StringToMyLCDwithLocation(3,3,"Upper = ");

ADCSRA |= 1<<ADPS2;
ADMUX |= (1<<REFS0);
ADCSRA |= 1<<ADIE;
ADCSRA |= 1<<ADEN;

sei();

ADCSRA |= 1<<ADSC;

while(1) 
{

}

}

ISR(ADC_vect)

{ uint8_t theLow = ADCL; uint16_t tenBitValue = (ADCH << 8 | theLow);

  Send_An_IntegerToMyLCD(11,1,tenBitValue, 4); //ADC value

  Send_An_IntegerToMyLCD(11,2,LowLimit, 4);
 //This should be the difference value.

  Send_An_IntegerToMyLCD(11,3,Upper, 4);
     // This should be the first ADC value to use as upper limit

   ADCSRA |= 1<<ADSC;

}

March 11, 2012
by stanvan
stanvan's Avatar

It seems like no mater how I use adc_read()

uint16_t curr_adc_value;

and

uint16_t first_adc_value;

are always the same. So far i can't get the uint16_t first_adc_value; to stay so i can use it to compare uint16_t curr_adc_value;.

Thanks Stan

March 11, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Stan,

In your 'bare code' that you are using to read the ADC there is no adc_read() that was used?

It seems the code you posted is incomplete?

Since you are also setting ADIE for the ADC you are intending to use Interrupts to get your ADC result, right?

I haven't used the ADC Interrupts.

Basically, in my programs I create 1 time-base using a Timer Interrupt. From there I 'poll' everything else that I want to act on.

Since I have not used the ADC Interrupts I don't think I would be much help in that.

I would 'definitely' get it to work without Interrupts first, in any case, and then add Interrupts after you have gotten it to work.

Also NOTE.

What I would do, for your testing, is to setup your Code to be the same as the Nerdkit Code. Then when that is working you can modify things from that point.

You could use the tempsensor.c Sample File for Nerdkits as well as a starting point. (Since that does do ADC Conversions.)

Also the way the ADC is used on the AVR Microcontroller is:

1 - setup the ADC 2 - set the source whose value to determine 3 - begin the ADC Conversion 4 - wait/loop and check for the ADC Result to be ready

For testing you can just tie your ADC Source to be VCC. Then you should always be getting the max ADC value. (Assuming your ADC Reference is set to VCC.)

Tying it to VCC would eliminate potential part problems and lessen the chance of wiring problems.

I hope that helps a little.

March 11, 2012
by Ralphxyz
Ralphxyz's Avatar

Stan shouldn't you be assigning your initial value before your while(1) loop?

Something like this:

int main(void)
{
InitializeMyLCD();

Send_A_StringToMyLCDwithLocation(3,1,"ACD = ");
Send_A_StringToMyLCDwithLocation(3,2,"Diff = ");
Send_A_StringToMyLCDwithLocation(3,3,"Upper = ");

ADCSRA |= 1<<ADPS2;
ADMUX |= (1<<REFS0);
ADCSRA |= 1<<ADIE;
ADCSRA |= 1<<ADEN;

sei();

ADCSRA |= 1<<ADSC;

uint16_t first_adc_value = adc_read();
uint16_t curr_adc_value; 
while(1) 
{ ... }

Then in your loop you would do:

curr_adc_value = adc_read();
     if curr_adc_value != first_adc_value
        {
           Do something;
        }

Ralph

March 11, 2012
by pcbolt
pcbolt's Avatar

Stan -

I think the problem is in the interrupt service routine (ISR). I don't think a new conversion can be started without clearing the interrupt bit first. Normally this is done automatically at the end of the ISR. Since you are triggering the next ADC reading before this happens, it won't ever re-trigger since the interrupt bit is still set. Try clearing the interrupt bit manually before executing instruction on line 9 in your last code block above. Should be something like:

  ADCSRA &= ~(1<<ADIF);
March 11, 2012
by stanvan
stanvan's Avatar

Hi everybody,

Thank you for all your help. Here's what i have so far. I'm using a potentiometer.

If the code isn't inside the isr nothing is displayed on the LCD. (I'm not sure why)

Inside the isr if first_adc_value = adc_read() the first_adc_value and curr_adc_value remain equal. (if i turn the pot both numbers change)

If I put a value, say 500 as first_adc_value the 'if' statements works.

I'm still trying to figure out how to make 'first_adc_value' a fixed number = to the value the ADC is when it's powered up. (or an average of many samples.)

This one is baffling me. Any help or suggestions are appreciated.

Thanks again.

Stan

//ADC Code

#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <math.h>
#include "MyLCD.h"

static volatile uint16_t LowLimit = 450;

uint16_t adc_read() {

  while(ADCSRA & (1<<ADSC)) {
    // do nothing...
  }

  uint16_t result = ADCL;
  uint16_t temp = ADCH;
  result = result + (temp<<8);

  ADCSRA |= (1<<ADSC);

  return result;
}

   int main(void)
{
    InitializeMyLCD();
    Send_A_StringToMyLCDwithLocation(2,1,"ADC = ");
    Send_A_StringToMyLCDwithLocation(2,2,"Lower = ");
    Send_A_StringToMyLCDwithLocation(2,3,"Upper = ");

    ADCSRA |= 1<<ADPS2;
    ADMUX |= (1<<REFS0);
    ADCSRA |= 1<<ADIE;
    ADCSRA |= 1<<ADEN;

    sei();

    ADCSRA |= 1<<ADSC;

     while(1)
   {    }

   }

ISR(ADC_vect)

    {
           uint16_t first_adc_value = adc_read(); //Upper Limit
           // If I put a value, say 500 as first_adc_value
           //  the 'if' statements works.

           uint16_t curr_adc_value;

            curr_adc_value = adc_read();

        Send_An_IntegerToMyLCD(10,3,first_adc_value, 4);
        Send_An_IntegerToMyLCD(10,1,curr_adc_value, 4);
        Send_An_IntegerToMyLCD(10,2,LowLimit, 4);

    if (LowLimit >= curr_adc_value) 
      {
         Send_A_StringToMyLCDwithLocation(2,4,"ON");
      }
    if (first_adc_value <= curr_adc_value)

      {
          Send_A_StringToMyLCDwithLocation(2,4,"OFF");
      }

            ADCSRA &= ~(1<<ADIF);
            ADCSRA |= 1<<ADSC;

      }
March 11, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Stan,

First... As I suggested, I would get your thoughts to work without the ADC Interrupts first.

Second... It is generally a good idea to keep Interrupt routines VERY SMALL.

It is a very good habit to get into and to tailor your thoughts to. It will, in the long run, make your programming efforts easier.

Interrupts are usually not really a 'separate task' they are are interruption of 'your primary task/tasks'.

That aside...

The Interrupt Routine that you have posted will not function properly in any case...

Here are the steps that I see...

1 - read_adc() and store the result in first_adc_value

2 - read_adc() and store the result in the curr_adc_value

3 - display first_adc_value on line 3 of the LCD

4 - display curr_adc_value on line 1 of the LCD

5 - display lowlimit on line 2 of the LCD

6 - if LowLimit >= curr_adc_value then display "ON"

7 - if first_adc_value <= curr_adc_value then display "OFF"

8 - clear ADC Interrupt

9 - Start ADC Conversion

Problems: 1 - you call read_adc() 2 times. If you were purely using interrupts to handle the ADC Conversions the problem with that is that for the second read no conversion would take place.

2 - Unless you have modified the read_adc() function, that function is designed to get ADC values in a polled mode, not through an interrupt.

3 - you also save the first_adc_value and the curr_adc_value each time the code is executed. The problem being is that you really only want to set the first_adc_value the first time you execute the code, not every time that you execute the code.

4 - is your 'lowlimit' the same as the 'first_adc_value'? 'lowlimit' is never written to or defined.

Maybe my original code snippet didn't have enough details to be clear.

So;

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 temp = ADCH;
    result = result + (temp<<8);

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

    return result;
    }

int main(void) {
    InitializeMyLCD();

    Send_A_StringToMyLCDwithLocation(2,1,"ADC = ");
    Send_A_StringToMyLCDwithLocation(2,2,"Lower = ");
    Send_A_StringToMyLCDwithLocation(2,3,"Upper = ");

    adc_init();

    uint16_t curr_adc_value;
    uint16_t first_adc_value;

    first_adc_value = adc_read();

    while(1) {
        curr_adc_value = adc_read();

        if (curr_adc_value > first_adc_value) {
            motor_turnon();
            }
        else {
            motor_turnoff();
            }

        delay_ms(10000);
        }
    }
March 12, 2012
by stanvan
stanvan's Avatar

Hello Jim,

Thank you for your response and your help. I'm sure your details were clear, that is, if you were talking to someone that knew what they were doing. Unfortunately in this instance that's not the case.

To me it looks like the code is doing what it should. I'm not sure what I'm doing wrong, probably many things.

Any insight you could lend would be great. Thank you again for your help.

Stan

Here's what's on the LCD no matter where the pot is.

ADC = 0 Lower = 450 Upper = 1024 OFF

Here's the code I have so far.

//ADC Code

#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <math.h>
#include "MyLCD.h"

static volatile uint16_t LowLimit = 450;

void adc_init()
    {
      ADMUX = 0;
      ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
      ADCSRA |= (1<<ADSC);
    }

    uint16_t adc_read() {

    while(ADCSRA & (1<<ADSC)) 
{
    // do nothing... just hold your breath.
}

   uint16_t result = ADCL;
   uint16_t temp = ADCH;
   result = result + (temp<<8);

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

   return result;
  }

int motor_turnon(void)
{
Send_A_StringToMyLCDwithLocation(2,4,"ON");
    //In here will also be turning the data register on. 
}

int motor_turnoff(void)
{
Send_A_StringToMyLCDwithLocation(2,4,"OFF");
    //In here will also be turning the data register off. 
}

 int main(void)
{
InitializeMyLCD();
Send_A_StringToMyLCDwithLocation(2,1,"ADC = ");
Send_A_StringToMyLCDwithLocation(2,2,"Lower = ");
Send_A_StringToMyLCDwithLocation(2,3,"Upper = ");

adc_init();

    uint16_t curr_adc_value;
    uint16_t first_adc_value;

    first_adc_value = adc_read();

Send_An_IntegerToMyLCD(10,1,curr_adc_value, 4);
Send_An_IntegerToMyLCD(10,2,LowLimit, 4);
Send_An_IntegerToMyLCD(10,3,first_adc_value, 4);

while(1) 
{
    curr_adc_value = adc_read();

    if (LowLimit > curr_adc_value)
 {
        motor_turnon();
    }

if ( curr_adc_value > first_adc_value) 
     {
        motor_turnoff();
     }

  else 
 {
        motor_turnoff();

     }

       _delay_ms(10000);
}
}
March 12, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Stan,

This looks better and easier to manipulate to me.

One thing...

Look at your main program loop. (The 'while (1)...')

It does the following:

1 - read_adc and set the curr_adc_value

2 - Turns the motor on/off based on conditions

3 - delays 10,000ms

???

Do you see the problem?

Where does it 'display the curr_adc_value'?

The "curr_adc_value" on the LCD will not change, because it is never written to the LCD after the first write.

In this case the 'simplest' fix is just to move your line "Send_An_IntegerToMyLCD(10,1,curr_adc_value, 4);" into your "While(1)" loop after your read the curr_adc_value...

Lastly... Is your 'hardware circuit'...

I DO NOT really create circuits.

I take functionally working circuits from where I find them and use them for my needs.

I am pretty sure that if you are trying to measure a potentiometer inline, that will not work. (What I mean is the potentiometer is connecter to VCC and then the output is connected to the Microcontroller.)

You need to create a 'voltage divider' in order to measure the potentiometer value.

Now maybe you did that, maybe not.

Look at this link, it should help you in understanding the hardware side.

AVR Freaks - Newbies Guide to AVR ADC

March 12, 2012
by stanvan
stanvan's Avatar

Hello Jim,

Thank you for getting back to me.

The pot is wired up as a voltage divider with a 10uf cap to smooth the voltage. I'm generally pretty good with circuits. I've always been an analog guy.

Last fall i did a project very similar to this with an ADC chip two comparators and some AND and NOR gates. That's when I started getting into micro controllers and found Nerd-Kit's.

C++ programming is brand new to me. I was doing ok till I wanted to use an ADC reading as a variable.

Also I don't have a very good grasp of interrupts. The last program I copied most of from another web site. It worked as long as I put a number in for the upper limit.

That was a good catch on where 'Send_An_IntegerToMyLCD(10,1,curr_adc_value, 4)' was placed. For some reason the program still doesn't work. It sure seems like it should.

I'm not sure what I'm doing wrong but I'm pretty sure it's doable. There's something I'm missing.

Thank you for your help, it's really appreciated.

Stan

March 12, 2012
by JimFrederickson
JimFrederickson's Avatar

I will take a closer look at your code tomorrow evening. (PST, Pacific Standard Time...)

About the hardware side...

Are you sure you are in ADC0? (I have made that mistake numerous times. Mainly because the pins seem to be labeled in a backwards manner to me. ADC0 is at the bottom of the group towards the middle of the chip.)

You could also remove the potentiometer/voltage divider circuit and put in a jumper on the ADC0 so you can move it to GND and VCC.

That way you should be able to see it fluctuate between the two extremes, as you plug and unplug between GND and VCC.

March 12, 2012
by stanvan
stanvan's Avatar

Hello Jim,

When I use it in the isr I could display the 10 bit number on the LCD. It would go from 0 to 1023. So far that's the only way it's worked. I think I'll have to take a look at the temperature sensor again.

Thanks again for your help.

Stan

March 13, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Stan,

I really don't see anything wrong with the code that I see.

You have made 'custom LCD Functions'.

So let's test them.

Change the first part of your main program loop to: (keeping the remainder of the loop the same. I just showed the 'adc_read' as being commented out, but you can delete it if you prefer.)

while(1) {
    //curr_adc_value = adc_read();

    if (curr_adc_value != 1) {
        curr_adc_value = 1;
        }
    else {
        curr_adc_value = 255;
        }

    Send_An_IntegerToMyLCD(10,1,curr_adc_value, 4);

This should cycle the display between 1 and 255 at 10 second intervals if everything is working.

That should check the Send_An_IntengerToMyLCD for byte values.

Then change the 1 and 255 to 256 and 790. (NOTE: There are 2 1's to change.)

This way it will verify it is displaying numbers beyond the 8 bit range too.

March 13, 2012
by JimFrederickson
JimFrederickson's Avatar

Also...

You could load the 'temperature sensor code' in as well and try that.

It should display changes in temperature when you adjust your potentiometer.

If that doesn't work then there would have to be some sort of wiring problem or hardware problem with the microcontroller.

If it does work then you know the problem is purely with your other code as well.

March 13, 2012
by JimFrederickson
JimFrederickson's Avatar

Stan,

I have verified that the following functions seem to be working properly adc_init() adc_read()

I put your code, changing the lcd functions that were used back to the stock lcd functions onto my microcontroller. All works properly. (By properly I mean I get the same ADC Values that I would get with other code that I have that I know works, and the curr_adc_value is read and written to the lcd every 10 seconds.)

March 13, 2012
by JimFrederickson
JimFrederickson's Avatar

Stan,

I have verified that the following functions seem to be working properly adc_init() adc_read()

I put your code, changing the lcd functions that were used back to the stock lcd functions onto my microcontroller. All works properly. (By properly I mean I get the same ADC Values that I would get with other code that I have that I know works, and the curr_adc_value is read and written to the lcd every 10 seconds.)

March 14, 2012
by stanvan
stanvan's Avatar

Hello Jim,

Thank you for verifying the code. Right now I'm thinking it has to do with some difference between the 168 and the m32. it may be in the wiring differences.

Verifying the code lets me know that the problem must hardware related. I'm going to do a little more studying and see what I find. I'll keep you posted.

Thank you again for your help.

Stan

March 14, 2012
by JimFrederickson
JimFrederickson's Avatar

Hello Stan,

If you are talking about an ATMega168 vs an ATMega328, they are mostly equivalent.

As far as I know once you add in the additional header file for the ATMega328 the same codes runs on both. (At least that has been my experience. In fact, I have compiled code for the ATMega168 and taken the .hex file and put it onto both an ATMega168 and ATMega328 without recompiling without any problems.)

Aside from running out of Program Space or RAM I haven't had any programs that don't successfully and seamlessly run in both.

It is still possible that there is some issue with your 'custom lcd routines'.

March 14, 2012
by stanvan
stanvan's Avatar

Hello Jim,

The other chip i'm using is the Atmega32 not the ATMega328

I needed more pins and a friend of mine had an Atmega32. Right now I'm not sure of all the difference. I know there's several in the way I have it wired. Mostly I wasn't using the 14.7456MHz crystal oscillator. That's because the tutorial I was learning from didn't use it.
The first thing I want to do is get it working on the ATMega168. Then I'll wire the Atmega32 up as close as possible to the 168 accounting for any differences.

I'll let you know what i find.

Any insight you have would be greatly appreciated.

Thank you for you help.

Stan

Post a Reply

Please log in to post a reply.

Did you know that you can build a digital read out (DRO) for a lathe or milling machine? Learn more...