How can I slow down the ADC

Go To Last Post
16 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hey Freaks.

I've build a temp display using an Atmega8. I read the temp from an LM35 (going to change as soon as I get hold of another temp sensor that can give negative temps but with single rail supply only) via the ADC (where else would you read it from? lol). I then display the temp on 7 segments via 1 x 74HCT47 decoder using transistors to switch the displays. The displays flashes between two readings. For example, 16 and 17 then the displays will alternate between 6 & 7 and then it looks like an 8. When the temp gets close to 20 the 2nd display also starts alternating between a 1 and a 2.

How can I solve this problem? I'm thinking changing the ADC reading intervals but I've tried that and it's still to fast.

Here is the code;

#include  
#include  

/****************************************************************************************
 Delay Routine
****************************************************************************************/
void Delay(unsigned long value)
{
        unsigned long cnt;
        for(cnt = value; cnt > 0; cnt--);
}

/***********************************************************************************
ADC Init
***********************************************************************************/
void ADC_Init()
 {
   ADCSR |= (1<<ADPS1) | (1<<ADPS2); 		// Prescale 64 -> ADC clock is 125kHz 
   											// Leave MUX3-0 in ADMUX = 0 for ADC0 
   ADMUX |= ( (1<<REFS0) | (1<<ADLAR) );    // AVCC with external capacitor 
   											// at AREF pin, Left justify    
   ADCSR |= (1<<ADEN); 						// Enable the ADC 
 }


/***********************************************************************************
Read Temp
***********************************************************************************/
void Read_Temp()
 {
   ADCSR |= (1<<ADSC); 						// Start conversion 
   while(! (ADCSR & (1<<ADIF)) ); 			// Wait for ADC conversion complete 
   ADCSR |= (1 << ADIF);
 }


/****************************************************************************************
Write Shift Clock Pulse
****************************************************************************************/
void clockpulse(void)
 {
    PORTC |= (1<<PC5);						//Serial Clock = 1
	PORTC &= ~(1<<PC5);						//Serial Clock = 0
 }

/****************************************************************************************
Write Shift Data
****************************************************************************************/
void Write_shift(unsigned int shift_Data)
 {
  unsigned char BitCntr;
   for(BitCntr = 0; BitCntr < 9; BitCntr ++)
    {
     if(BitCntr)
      {
       shift_Data = shift_Data << 1;
      }
     if(shift_Data & 0x80)
      {
       PORTC |= (1<<PC4);	//Serial Data = 1
	  }
     else
      {
	   PORTC &= ~(1<<PC4);	//Serial Data = 0
      }
     clockpulse();
    }
 }

/***********************************************************************************
Test Displays
***********************************************************************************/
void Test_Disp(void)
 {
    //PORTB |= (1<<PB5); 					//LT = 1
	PORTB &= ~(1<<PB5);	 					//LT = 0
		
	PORTB |= (1<<PB4);						//BI = 1
	//PORTB &= ~(1<<PB4);					//BI = 0 - Blank Displays

	//PORTB |= (1<<PB3);					//RBI= 1
	PORTB &= ~(1<<PB3);						//RBI = 0


	PORTD |= (1<<PD5);						//Transistor 1 = 1
	Delay(50);
	PORTD |= (1<<PD6);						//Transistor 2 = 1
	Delay(50);
	PORTD |= (1<<PD7);						//Transistor 3 = 1
	
	Delay(50000);
			
	PORTD &= ~(1<<PD5);						//Transistor 1 = 0
	Delay(50);
	PORTD &= ~(1<<PD6);						//Transistor 2 = 0
	Delay(50);
	PORTD &= ~(1<<PD7);						//Transistor 3 = 0
	Delay(50);

	PORTB |= (1<<PB5); 						//LT = 1

 }

/***********************************************************************************
 Main Routine
***********************************************************************************/
int main(void) 
  {  
	
	unsigned long Disp_Delay;
			
	DDRB|=(1 << PB5)|(1 << PB4)|(1 << PB3);	// PB 3,4,5 digital outputs
	DDRD|=(1 << PD5)|(1 << PD6)|(1 << PD7);	// PD 5,6,7 digital outputs
	DDRC|=(1 << PC5)|(1 << PC4)|(1 << PC3);	// PC 3,4,5 digital outputs
	PORTC &= ~(1 << 0);						// PC 0 input

	ADC_Init();
	
	Test_Disp();
	
	PORTC &= ~(1<<PC3);						//Master Reset=0 - Reset 74HCT595
	PORTC |= (1<<PC3);						//Master Reset = 1
	
	Read_Temp();

	while (1) 
      { 
		
		Read_Temp();
		
		uint8_t Temp1 = ADCH % 10;
		uint8_t Temp10 = ADCH / 10;
		
		for(Disp_Delay = 0; Disp_Delay > 0; Disp_Delay--);
			{
				Write_shift(Temp1);
				PORTD |= (1<<PD5);			//Transistor 1 = 1
				Delay(50);
				PORTD &= ~(1<<PD5);			//Transistor 1 = 0
		
				if(Temp10<1)
					{
						PORTD &= ~(1<<PD6);	//Transistor 2 = 0
					}

				if(Temp10>=1)
					{
						Write_shift(Temp10);		
						PORTD |= (1<<PD6);	//Transistor 2 = 1
						Delay(50);
						PORTD &= ~(1<<PD6);	//Transistor 2 = 0
					}
	
				PORTD &= ~(1<<PD7);			//Transistor 3 = 0
		}

		Disp_Delay = 65000;
	  } 
 } 

/***********************************************************************************
************************************************************************************
***********************************************************************************/

Any additional tips on the code will also be appreciated.

Regards,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You could take a rolling average of the measurements (at 125kHz sample rate, you could go with an average of the last 2^16 samples, or about 0.5s) , and output the current average to the LCD.

Also, you may want to store the value in ADCH instead of accessing it multiple times. There are only two consecutive (non-atomic) reads, here, so your implementation will still work, though.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you'd better write adc collect function, display function, delay function;see some examples of others.
for example

unsigned int ADC_Collect( void )
{
    /* function content */
    unsigned int adc_temp, tempH;
    ADCSR |= (1<<ADSC);                   /* Start conversion */ 
    while(! (ADCSR & (1<<ADIF)) );        /* Wait for ADC conversion complete */
    ADCSR     |= (1 << ADIF);
    adc_temp   = ADCL;
    tempH      = 3&ADCH;
    tempH    <<= 8;
    adc_temp  |= tempH;
    return adc_temp;
}
void Dis_Seg( unsigned int )
{
    /* Write the function content. */
}

Then set you display time , ADC_Collect -> Dis_Seg -> Delay
you can also add some filter function to enhance the acquisition accuracy!
Good luck!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Your delay routine is most likely optimized away by the compiler. Use the delay routines that come with the compiler.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

tlucas wrote:
You could take a rolling average of the measurements (at 125kHz sample rate, you could go with an average of the last 2^16 samples, or about 0.5s) , and output the current average to the LCD.

I thought about the average of a few readings, but I've got no idea how to calculate the average in C, is there a shorter way or do I just add 10 values and use that and divide that value by 10? Or is there a mathematical function that I should use?

alleazy wrote:

    adc_temp   = ADCL;
    tempH      = 3&ADCH;
    tempH    <<= 8;
    adc_temp  |= tempH;
    return adc_temp;
}

Can you please explain the sample code part I've quoted? I'm not 100% sure what you are trying to explain.

Regards,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

mtlost wrote:
tlucas wrote:
You could take a rolling average of the measurements (at 125kHz sample rate, you could go with an average of the last 2^16 samples, or about 0.5s) , and output the current average to the LCD.

I thought about the average of a few readings, but I've got no idea how to calculate the average in C, is there a shorter way or do I just add 10 values and use that and divide that value by 10? Or is there a mathematical function that I should use?

alleazy wrote:

    adc_temp   = ADCL;
    tempH      = 3&ADCH;
    tempH    <<= 8;
    adc_temp  |= tempH;
    return adc_temp;
}

Can you please explain the sample code part I've quoted? I'm not 100% sure what you are trying to explain.

Regards,


The code example is easier written as:

    return ADCW;      // most compilers will understand

There is no magic 'average' function. You normally do something like this:

uint32_t total = 0;
for (i = 0; i < samples; i++) total += nextvalue;

average = total / samples;

In practice you will increment your sample_count and add to the total every time you call your ADC routine.

Then remember to test for division by zero, and your

average = (total / sample_count);

is available any time you want it.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You can't filter you out of the problem (AVG is just a LP filter) it will only hide the problem, using your numbers :
When the temp is precise 16.5 your display will flip with (up to) )your display rate.
You need to make a hysterese function so when you show 16 you will no change before the reading is about 16.7, and on the way down from 17 it will be that you don't change before 16.3 .

Jens

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sparrow2 wrote:
You can't filter you out of the problem (AVG is just a LP filter) it will only hide the problem
This is also a LP filter - one that skips even tenths.

You may also use a rolling average, which keeps track of the last N samples, where N is the size of your buffer. (If a ring buffer is used, each value doesn't need to be shifted in; simply overwrite the oldest value.) Every time you refresh the display, simply average all values in the ring buffer. Note that the first N values you output will be very low due to the uninitialized or null-values present in the buffer. This likely isn't an issue, as it will correct itself in N/sample_rate[Hz], or 10/125kHz ~ 80us. This touches on another point - this changes your refresh rate for a 0.1 degree change to N/2/sample_rate. If you want it to be relatively slow (it sounds like you do), increase the buffer size. N = 2^16 results in a 262 ms delay in displaying a 0.1 degree change.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I've tried the hole averaging thing and the display is still to fast.

Here is the read ADC code;

/***********************************************************************************
 Read Temp
***********************************************************************************/
void Read_Temp()
 {
   uint32_t total = 0;
    
   for (i = 0; i < 250; i++) total += ADCH;
     {
   		ADCSR |= (1<<ADSC); 				// Start conversion 
		while(! (ADCSR & (1<<ADIF)) ); 		// Wait for ADC conversion complete 
		ADCSR |= (1 << ADIF);
	 }

   if(i >= 250)
     {
	   	average = total / 250; 
	   	Temp1 = average % 10;
   		Temp10 = average / 10;
		i = 0;
     }
 }

Is this the correct way? The reason for the Temp1 and Temp10 is because I can only send 4 bits to the shift a t a time because I'm only using 1 display decoder. There is a third display as well for the decimal but I'll get to that later, I first want to get a constant reading before I take on that part of the code.

Regards,

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quite why you want to read the temperature so many times is a mystery.

You should get fairly steady readings unless the real temperature is exactly 20.5 C. In which case sometimes the average will say 20C and sometimes 21C.

I presume that you are multiplexing your displays. e.g. displaying Temp10 for 10ms and then displaying Temp1 for 10ms. Incidentally you will be better off with using _delay_ms(10) than some unknown Delay() function.

I would have to check the 7447 data sheet, but I think it will decode its inputs regardless. The required 7seg only displays when its column driver transistor is on.

David.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
   for (i = 0; i < 250; i++) total += ADCH;
     {
         ADCSR |= (1<<ADSC);             // Start conversion
      while(! (ADCSR & (1<<ADIF)) );       // Wait for ADC conversion complete
      ADCSR |= (1 << ADIF);
    } 

The "total += ADCH;" is at a wrong position (the result is totally useless code).

Stefan Ernst

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

david.prentice wrote:
Quite why you want to read the temperature so many times is a mystery.

There is no need to read the temp so often, once a minute is fine.

david.prentice wrote:
I presume that you are multiplexing your displays. e.g. displaying Temp10 for 10ms and then displaying Temp1 for 10ms. Incidentally you will be better off with using _delay_ms(10) than some unknown Delay() function.
That is correct, I'm multiplexing the displays. I've tried the _delay_ms routine but it chows up flash, currently the code is about 16%, when I start using the _delay_ms routine it goes up to way above 80%. I know I'm doing something wrong to cause that, still have to figure that one out as well.

david.prentice wrote:
I would have to check the 7447 data sheet, but I think it will decode its inputs regardless. The required 7seg only displays when its column driver transistor is on.
The decoder decodes the binary data just fine. There is no problem in that department. My problem is that the temp sensor is to sensitive and even a slight change in temp changes the display, so you get 18 when in fact it's changing between 16&17.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

sternst wrote:

   for (i = 0; i < 250; i++) total += ADCH;
     {
         ADCSR |= (1<<ADSC);             // Start conversion
      while(! (ADCSR & (1<<ADIF)) );       // Wait for ADC conversion complete
      ADCSR |= (1 << ADIF);
    } 

The "total += ADCH;" is at a wrong position (the result is totally useless code).

mtlost slaps himself.

It looks much better now, still a little bit of flickering but slow enough so that I can actually see what's on the display.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
    adc_temp   = ADCL;                  /* Get value of ADCL ( 8 bit )*/
    tempH      = 3&ADCH;                /* Get value of ADCH ( 2 bit )*/
    tempH    <<= 8;                     /* tempH left move 8 bit     */
    adc_temp  |= tempH;                 /* get adc value              */
    return adc_temp;
} 

When ADCL is read, the ADC Data Register is not updated until ADCH is read. Consequently,
if the result is left adjusted and no more than 8-bit precision is required, it is
sufficient to read ADCH. Otherwise, ADCL must be read first, then ADCH.(form atmega8 datasheet ,page 205 )

My mother tongue is not English,and my english is not good,but I hope you understand.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is the GCC forum so it's a pretty fair bet that avr-gcc/AVR-LibC is being used so why would anyone mess about with reading ADCH/L separately and joining them? Just:

   return ADC;
}

for some models

   return ADCW;
}

is an alternative that may be used - it may be more obvious to the reader that it's a Word width register being used.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The project I'm working on uses the ADC for reading voltages and Amp's. and the basic setup is like this:

1). The ADC runs in free running mode and continuously updates a little volatile array in the ADC conversion complete interrupt with all ADC inputs which are used.

2). I have a separate function which is connected to a sort of "timer" library and that function does the synchronisation between the interrupt and the rest of the software, it also does filtering on the raw ADC values and does post processing to generate "human readable" values with units of [10mA] and [10mV] and updates a few global variables with these numbers.

3). The global variables are safe to use anywhere else in the program, just read them at will.

4). These global variables are displayed on a HD44780LCD @ 10Hz at the moment but that is very easily adjusted to any desirable rate.

The complete progam isn't finished yet but the ADC part works for now. To compile the code in this attachment you'll also need some librarys from my website. Hope it helps.

Attachment(s): 

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com