ATtiny5 ADC Conversion Complete Problem

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

Hello again, everyone!

 

I have an issue with the ATtiny5 recognizing the end of an ADC conversion, which leads me to believe I have an issue with how I'm clearing the flag or something to that effect (explained in more detail below).  Below is my code:

 

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

unsigned int volatile ADCCount = 0;
signed int volatile tempADC = 0;

void HardwareInit()
{
	// CLK - ATTiny5
	CLKMSR = (0 << CLKMS1) | (0 << CLKMS0); // Set to calibrated internal 8 MHz oscillator
	CCP = 0xD8; // Write to protection register to access CLKPSR 
	CLKPSR = (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0);	// Set clock prescaler to 0

	//ATTiny5
	DDRB = 0b00000001; // Set PORTB pin 0 as output, rest as input
	PORTB = 0b11111110;  // Activate pull ups on inputs
}

void ADCInit()
{
	//ATTiny5
	ADMUX = (1 << MUX1) | (0 << MUX0);	// Enables ADC2

	ADCSRA = (1<<ADEN)	// Enables ADC
		|(1<<ADIE)	// Enables ADC interrupt
//		|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);	// Sets ADC Prescaler to 128 (clk/128 = 62500 Hz)
		|(0<<ADPS2)|(0<<ADPS1)|(0<<ADPS0);	// Sets ADC Prescaler to 2 (clk/2 = 4 MHz)

//	ADCSRB = ( 0 << ADTS2) | ( 0 << ADTS1) | ( 0 << ADTS0);	// Sets in free run mode
}

void TimerInit()
{
	//CTC Setup - ATTiny5
	TCCR0A = (0 << COM0A1) | (1 << COM0A0) //  Connect OC0A, Toggle OC0A on Compare Match
	| (0 << COM0B1) | (0 << COM0B0) // OC0B disconnected
	| (0 << WGM01) | (0 << WGM00); // (WGM[3:0] = 0100) CTC mode, TOP = OCRA, updated of OCR0x immediately, TOV0 flag set on MAX

	TCCR0B = (0 << ICNC0) | (0 << ICES0) // ICNC0 input capture noise canceller is disabled, ICES0 input capture on falling edge (not used)
	| (0 << WGM03) | (1 << WGM02) // See: TCCR0A
	| (0 << CS02) | (0 << CS01) | (1 << CS00); // No prescaling (clk/1)

	TCCR0C = (0 << FOC0A) | (0 << FOC0B); // FOC0x bits are active when the WGM bits specify a non-PWM mode, not active here
}


ISR(ADC_vect)          // interrupt subroutine
{

}

int main(void)
{
	HardwareInit();
	TimerInit();
	ADCInit();

	sei(); 
	ADCSRA |= 1<<ADSC;		// Start Conversion

	while(1)
    {    
    	ADCSRA |= 1<<ADSC;		// Start Conversion
    
    	while ((ADCSRA & 0x10)==0) // Wait for the AD conversion to complete
    	{
    			
    	} 
    	ADCSRA|=0x10; // Set flag bit to clear		
    	
    	tempADC += ADCL;
    
    	if (ADCCount < 15)
    	{
    		ADCCount += 1;
    	}
    	else
    	{
    		// tempADC algorithm here
    		
    		OCR0A = tempADC;
    
    		tempADC = 0;
    		ADCCount = 0;
    	}
    }
}



 

When running the code in the simulator (and on the chip), the OCRA is not being set to be tempADC.  Upon further investigation, it appears the issue is the code is not getting past the wait for conversion line the second time (so I'm assuming I'm not clearing the flag correctly).  To confirm this, I've changed ADC Count loop code:

 

if (ADCCount < 15)
{
	ADCCount += 1;
	if(ADCCount==1)
	{
		OCR0A = 380;
	}
}

When I run the code with the loop above (both in the simulator and on chip), the PWM output is updated correctly (which makes sense if it's able to get through the first conversion, which it would if the flag was already initialized as zero).  When I set it to:

 

if (ADCCount < 15)
{
	ADCCount += 1;
	if(ADCCount==2)
	{
		OCR0A = 380;
	}
}

The OCR0A register is NOT set, so it's not getting to the second iteration.  When I run the simulation, it definitely gets hung up here the second time:

 

while ((ADCSRA & 0x10)==0) // Wait for the AD conversion to complete
{
	
} 

This, to me, makes sense it would get caught here in code because it's not going to be able to clear a hardware flag in code on it's own.  However, when I burn the same code to the chip, it's definitely not making it to update the OCR0A on the second iteration either.  

 

When I read the datasheet (pg 135), it says the following about the flag:

 

Bit 4 – ADIF: ADC Interrupt Flag This bit is set when an ADC conversion completes and the Data Registers are updated. The ADC Conversion Complete Interrupt is executed if the ADIE bit and the I-bit in SREG are set. ADIF is cleared by hardware when executing the corresponding interrupt handling vector. Alternatively, ADIF is cleared by writing a logical one to the flag. Beware that if doing a Read-Modify-Write on ADCSRA, a pending interrupt can be disabled. This also applies if the SBI and CBI instructions are used

 

I saw the line above and I'm assuming I'm doing something wrong there (since I don't see anything else).  Unfortunately, I don't really know what to do with this information.  

 

Any ideas as to what I am doing wrong?  Any help you can give me would be appreciated!  Thanks, guys!

This topic has a solution.

Last Edited: Wed. Oct 7, 2015 - 08:29 PM
This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
tempADC += ADCL;

Why are you reading only ADCL? You MUST read ADCH before you start the next conversion. If you want the entire ADC value, then read ADC (or ADCW). If you want just an 8 bit result, then set the ADLAR bit and just read ADCH.

 

You are also enabling the ADC interrupt, but not doing anything in ISR. As soon an the ISR is run, the interrupt flag is cleared, so it is unlikely that the main loop will see that the flag is set. Either don't enable the interrupt, or do the reading in the ISR.

Regards,
Steve A.

The Board helps those that help themselves.

Last Edited: Tue. Oct 6, 2015 - 09:39 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0
tempADC += ADCL;

Why are you reading only ADCL?

Ummm--ATtiny5 only has an 8-bit A/D.  And the result register is indeed named ADCL in the datasheet and toolchains may well follow that.

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

Well, you nailed it.  My initial code had an issue where it has bursts of high and low values in the PWM output so I had tried to move the code outside of the interrupt to see how that played out but it looks like that part of the issue is the same.  Unfortunately, I had left that interrupt in (a remnant of the previous code but, admittedly, I didn't know would cause an issue).  I didn't realize it would clear the flag when calling the interrupt routine but thinking about it, that makes perfect sense.  

 

I have another issue but I will place that in another post.  Thanks for your help!

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

My initial code had an issue where it has bursts of high and low values in the PWM output ...

???  But your code says you are using CTC, not PWM.  So you are generating a 50% duty cycle square wave?

 

What I think you were/are seeing is either a runt or elongated pulse when OCR0A was changed at an arbitrary time.

 

If your ending frequency isn't too fast, you could set up an OCR0A compare match ISR and only change the OCR0A register there.  Or use another mode, or watch flags and TCNT1 values, or...

//		|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);	// Sets ADC Prescaler to 128 (clk/128 = 62500 Hz)
		|(0<<ADPS2)|(0<<ADPS1)|(0<<ADPS0);	// Sets ADC Prescaler to 2 (clk/2 = 4 MHz)

Tell more about why you need 100+ksps for this app. 

13.5 Prescaling and Conversion Timing
By default, the successive approximation circuitry requires an input clock frequency between 50
kHz and 200 kHz to get maximum resolution.

...

Clock Frequency 50 200 kHz

 

 

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

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

The "while" loop will never see ADIF=1 because as soon as it is,  the ISR is vectored to, which clears it.

So ADIF can never be seen as 1 by base-level code if the interrupt vector is enabled.

Your code will therefore fall straight through to the end of "main" and likely get there before

the ADC has finished its first conversion, and you will never get a second conversion.

 

What would cure this is to declare a data byte as a software flag, set it to "busy" (say $FF) when you

start the AtoD conversion, set it to $00 within the ISR routine, and have your "while" loop wait for

that byte becoming zero.