How to combine ADC and Timer 1 interrupt on Atmega16

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

Hallo, I have a problem with my Atmega16 project and I am afraid that I am running out of ideas of solution. Here is my task:
I have two POTs connected to Atmega16 ADC. By these POTS I want to change the frequency and the duty cycle of a signal generated by Timer 1. POT_0 is connected to ADC channel 0(determines the frequency) and POT_1 is connected to ADC channel 1(determines the duty cycle). I have set up timer 1 in the following way: ICR register determines the frequency and OCR register determines the duty cycle. I use Phase and frequency correct PWM mode.
I store the data from the ADC in an 8x10 array, because later I will probably need all 8 channels of the ADC and I want to keep some data history.
I update the Timer 1 registers in Timer1 Overflow interrupt. There I pick up the desired value from the ADC data array. As ICR register is not double buffered so I use a software buffer for it. In this way I can update correctly OCR and ICR register.
The frequencies I need vary between 8kHz and 266kHz.

If I use only the ADC (Timer is off) it is working perfect(I sample and store the data without any problems). If I disable the ADC and turn Timer 1 on everything is fine again. By writing predefined data to the timer registers I successfully generate PWM signal.

But when I turn both ADC and timer together everything falls apart. Neither of the POTs is working. What I found out is that the the program never enters the ADC interrupt. Obviously the timer interrupt occupies too much time. I tried to allow interrupts during Timer 1 ISR, but this resulted in constant reset of the MCU?!?!

Please give me some clues?

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

Here is my code:

#include "avr/io.h"
#include "avr/interrupt.h"

#define ADC_length 10
#define ADC_channels 8
#define ADC_range 5.10
#define ADC_scale 1024
#define F_8kHz 1000.00
#define F_260kHz 30.00
#define F_NORM 0.9472
#define D_MIN 0.003
#define D_MAX 1030.00
#define COIL 2
#define ELEKTROD 0
#define COIL_MAX 1024.00
#define C_NORM 0.50
#define F_30Hz 255


//globals
int *p_adc_data;//points to the data array, curent position
int *p_adc_data_end;//points to the data array, first position
int *p_adc_data_init;//points to the data array, last position

//separation of int variable to 2 chars
union {unsigned char bytes[2];
	   int result;}adc_res;

int adc_data[ADC_length][ADC_channels];

#define ADC_START ADCSRA=0b11101111
#define ADC_STOP ADCSRA=0b00001111
#define ADC_CHAN(num) ADMUX = ((ADMUX & 0b11111000) | num)

#define STOP_TIMER_0 (TCCR0 & 0b11111000)
#define STOP_TIMER_1 (TCCR1B & 0b11111000)
#define STOP_TIMER_2 (TCCR2 & 0b11111000)
#define START_TIMER_0 (TCCR0 & 0b11111101)// x1024
#define START_TIMER_1 (TCCR1B & 0b11111001)// x1
#define START_TIMER_2 (TCCR2 & 0b11111010)//x8


int adc_out(unsigned char high, unsigned char low)
{
	adc_res.bytes[1]=high;
	adc_res.bytes[0]=low;
	return adc_res.result;
}

//data array init
void adc_data_init(void)
{

	while(p_adc_data=offset)
	{
		if(p_adc_data<(p_adc_data_init+8))//if it points to the zero row, take the value from the last row
		{
			p=(p_adc_data_end-7)+channel;
		}
		else
		{
			p=p_adc_data-8;	
		}
	}
	else
	{
		//if points to a cell after the desired value take the value from the current row
		if(p_adc_data<(p_adc_data_init+8))
		{
			p=(p_adc_data_end-7)+channel;
		}
		else
		{
			p=(p_adc_data-offset)+channel;	
		}
	}
	return *p;
}


void pwm_set(void)
{
	union {unsigned char timer[2];
		   unsigned int adc;}freq_set;
	union {unsigned char timer[2];
		   unsigned int adc;}duty_set;

	static float duty_cycle=0.00;

	static unsigned char buffer_ICR[2][2]={255,255,
					       255,255};//software buffer for ICR register

	//take the adc result and norm to 1
	duty_cycle=(((float)address(1))/D_MAX)+D_MIN;

	freq_set.adc=(int)(F_NORM*(float)address(0)+F_260kHz);//determine OCR value for frequency
	duty_set.adc=(int)(duty_cycle*(float)freq_set.adc);//determine ICR value for duty cycle

	OCR1AH=duty_set.timer[1];
	OCR1AL=duty_set.timer[0];
	
	ICR1H=buffer_ICR[0][1]=buffer_ICR[1][1];//high
	ICR1L=buffer_ICR[0][0]=buffer_ICR[1][0];//low

	buffer_ICR[1][0]=freq_set.timer[0];//low
	buffer_ICR[1][1]=freq_set.timer[1];//high
}

ISR(TIMER1_OVF_vect)
{
	unsigned char stat_reg=0x00;
	stat_reg=SREG;
	sei();
	pwm_set();//update na ICR
	SREG=stat_reg;
}


void init(void)
{	
	//**************************** ADC *********************************************//
	adc_data_init();
	ADMUX=0b01000000;
	ADCSRA=0b10001111;//bit7 -> aktivira verigata na ADC
			  //bit6 -> start i stop na ADC
			  //bit5 -> autotrigger
			  //bit4 -> interrupt flag
			  //bit3 -> interrupt enable
			  //bit2/1/0 -> prescaler, izbran e delitel 128 -> ADC_clock=125kHz
	ADC_CHAN(0);
	//******************************************************************************//


	//********************************* TIMER 1 *******************************************//
	ICR1H=0x00;
	ICR1L=0xf0;
	OCR1AH=0x00;
	OCR1AL=0x50;
	OCR1BH=0x00;
	OCR1BL=0x00;
	TCNT1H=0x00;
	TCNT1L=0x00;
	TCCR1A=0b11000000;//inverting mode
	TCCR1B=0b00010001;//ICR1x determines TOP, preskaler 1. 
	TIMSK=0b00000100;
	//************************************************************************************//


	//***************** I/O port diretion *******************//
	DDRA=0x00;//PORA e vhod
	DDRD=0xff;//Vsk4ki pinove na PORTD saizhodi
	PORTD=0x00;
	//*******************************************************//


	//razre6avam prekasvaniqta
	sei();


	//***************** Activirane na periferiqta ******************//
	ADC_START;//puskam ADC
}



int main(void)
{
	int adc_data[ADC_length][ADC_channels];//adc data storage
	//each column contains the data for each adc channel

	//Adresiram promenlivite za popalvane na masiva
	p_adc_data=&adc_data[0][0];//predavam na4alniq adres na masiva s dannite
	p_adc_data_init=&adc_data[0][0];//predavam na4alniq adres na masiva s dannite
	p_adc_data_end=&adc_data[ADC_length-1][ADC_channels-1];//polu4avam adresa na posledniq element ot masiva

	init();//inicializiram MCU, aktiviram periferiqta i prekasvaniqta 

	while(1)
	{
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You sei() in the middle of an ISR - this is extremely bad juju.

I don't see why you feel the need to do that?

You also call a function from an ISR - this is bad juju.

Calling a function from an ISR adds a huge PUSH/POP overhead.

You do floating point calculations in an ISR - this is very bad juju.

The idea of ISRs is to execute in microseconds and do the absolutely minimal amount of work possible. Move the long work out to main() and have it triggered by a volatile flag set in the ISR.

Finally why bother to do the OCR/ICR update in the timer interrupt anyway? Why not just do it from time to time in main()'s while(1)

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

Ok , I can't defend my idea on any of your points. Just the last point:
I need to follow the ADC constantly because later it will be connected to a pedal(one of the adc channels will be connected to a pedal, the rest will sample slower signals).

Ok I will try to fallow you guidelines.

Thank you.

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

One other thing: you need to make the variables used in the ISR as volatile. I'm not sure that making them pointers is the same thing. Some one with more experience may shed some more light on this.

int *p_adc_data;//points to the data array, curent position
int *p_adc_data_end;//points to the data array, first position
int *p_adc_data_init;//points to the data array, last position
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

But these are global variables? They should be accessible from anywhere.
I am not used to using volatile variables, so I avoid them. But I have to get more familiar to them.

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

From what I understand it isn't about accessibility. It's the fact that these variables can change while the ISR is running and mess up the result. Try making them volatile and see if it works.

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

'volatile' is required on any data object that may be accessed in two separate paths of execution. Usually on variables communicating between main() and an ISR() but it could be between two ISR()s. What it does is ensure that the compiler will generate code to always retrieve/store data in the object's location in RAM rather than generating optimised code that just cache's the value into a local machine register or even the discard of code that looks like it's not doing anything within the single thread of execution.

On the whole the optimiser is not smart enough to discard or shorten accesses through a pointer so doing so may remove the need to make the shared object, being pointed to by the pointer, from being volatile.

But if in doubt - do.

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

So neither the pointers nor the object(in my case the data array) need to be volatile.

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

I don't know - your code is too complex to follow what's going on with just a cursory glance.

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

If I may continue with the silly questions:
a variable which is used in the main fcn and in an ISR should be declared as volatile. But does it have to be a global variable, or I can declare it in the main fcn?

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

Quote:

a variable which is used in the main fcn and in an ISR should be declared as volatile. But does it have to be a global variable, or I can declare it in the main fcn?

That question doesn't make a lot of sense if you think about it. If it was declared inside main() then it couldn't be visible to an ISR() could it? Having said that I guess it could be made visible with a pointer:

int *p_int;

int main(void) {
 int local;
 p_int = &local;
}

ISR() {
 *p_int++;
}

but there's always going to be a global object of some sort.

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

Yes this is what I wanted to know, thank you.

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

Ok, I made the necessary changes in my code, used the volatile variables and reduced to minimum the ISR calculations. And now indeed I can change the frequency of my signal and I can change its duty cycle.
But, on my scope I can see that form time to time some strange pulses come up. They have a different frequency and a different duty cycle. They also vary by changing the main frequency.
Have you ever experienced such a problem?

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

I am sorry for my last question, it was a software error.
Now everything is perfect.
Thank you all for the help.