How can I count the interrupt on INT4 on ATMEGA128 ?

Go To Last Post
114 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Guys,

How can I count the interrupt on INT4 on ATMEGA128 ?
And blink LED on PD3 everytime there is interrupt...

I have created :

	//Init INT4
	MCUCR|=(1<<ISC01 );   //Falling edge on INT4 triggers interrupt.
	EICRB|=(1<<INT4);  //Enable INT4 interrupt

	//Timer1 is used as 1 sec time base
	//Timer Clock = 1/1024 of sys clock
	//Mode = CTC (Clear Timer On Compare)
	TCCR1B|=((1<<WGM12)|(1<<CS12)|(1<<CS10));
	//Compare value=976

	OCR1A=976;

	TIMSK|=(1<<OCIE1A);  //Output compare 1A interrupt enable

	//Enable interrupts globaly
	sei();

	//LED Port as output
	DDRD|=(1<<PD3);


	lcd_string(0x01);

	lcd_string("RPM =");
	lcd_string("RPS =");

	while(1)
	{
		lcd_string(rpm);
		lcd_string(rps);

		if(PINE & (1<<PE4))
		{
			PORTD|=(1<<PD3);
		}
		else

		{
			PORTD&=(~(1<<PD3));
		}

		Wait();
	}

}

ISR(INT0_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	count++;
}

ISR(TIMER1_COMPA_vect)
{
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	rpm=rps*60;
	count=0;
}

Please correct me if I'm wrong,
Any suggestions will be appreciated, I'm using 16Mhz clock
Thanks

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

What does MCUCR have to do with external interrupts? Is there a ISC01 bit in that register? (No).

Is there a INT4 bit in EICRB? (No).

There's only four registers involved with external interrupts, one of them is a flag register. Look them up in the datasheet. You can not write random bits to random registers and expect it to work.

ISR(INT0_vect) will not be called when INT4 occurs.

Is the variable count volatile?

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

I downloaded the mega128 data sheet and searched for "INT4".

Within seconds, I had obtained all the relevant information. Principally that INT4 has got its own vector e.g. ISR(INT4_vect) in avr-gcc.

I am very impressed that you have layed your code out neatly on the page and have comments !

Just read through your own code and logic. You should be able to do exactly what you want.

Hint. Use a pull-up resistor on pin PE4. (or external electronics)
Hint. Look at itoa() or sprintf() to convert integer to ascii string.

Google is your friend.

David.

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

Ok, I'll have a look....

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

I can see here : EICRA,EICRB,EIMSK,EIFR
page 90 ATMEGA128 datasheet,

I'm using a code from ATMEGA8 that's why it's different

Please correct me if I'm wrong,
thank you

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

The obvious strategy is to use the equivalent mega8 pins on your mega128. e.g. INT0

You will still have to compare both data sheets for every SFR. Most of the time, the names are the same. The compiler looks after the different physical addresses.

Having built and tested the mega8 program for INT0, you may rearrange for INT4 pin.

Seriously, there are no short cuts. I would have to look up the data sheet too.

All the same. If you have neat readable code, you will be able to develop your project easily.

David.

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

The issue is, I used pin INT0 as SCL for I2C, so I need to use other free pin

Thanks for your consideration...

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

I read about :

How can I use EIFR ?
I can understand this one :

This one as well :

How can I relate the timer with it ? I set the timer already there....and is it like this ?

Please correct me if it's not right or give me a clue, thanks in advance

EICRB = 0x02; //Falling edge on INT4 triggers interrupt.
EIMSK = 0x10;

//Timer1 is used as 1 sec time base
	//Timer Clock = 1/1024 of sys clock
	//Mode = CTC (Clear Timer On Compare)
	TCCR1B|=((1<<WGM12)|(1<<CS12)|(1<<CS10));
	//Compare value=976

	OCR1A=976;

	TIMSK|=(1<<OCIE1A);  //Output compare 1A interrupt enable

	//Enable interrupts globaly
	sei();

ISR(INT4_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	count++;
}

ISR(TIMER1_COMPA_vect)
{
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	rpm=rps*60;
	count=0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm guessing only for the timer currently try to understand from the datasheet now..

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

Please post compilable snippets. Or even a minimal buildable project.

It is best to develop a project in stages. e.g.
1. print "hello" on LCD
2. print a number on LCD
3. blink an LED in a timer ISR()
4. increment counter in a pin-change ISR()
5. print the counter on LCD
6. note that counter must be volatile

At each stage, ensure that your code is neat and your comments are truthful.

David.

Last Edited: Fri. Sep 20, 2013 - 11:16 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

it's giving a response to LED already....but the LCD hasn't displayed properly yet...

The video :
http://youtu.be/n4Ms9sLu-xw

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

Ok I'll follow your step, thanks for guiding

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

I think I have a conflict between LED and LCD :
I need to move the LED port somewhere else....

#define rs PORT_D.b3 and
....
if(PINE & (1<<PE4))
		{
			PORTD|=(1<<PD3);
		}
		else

		{
			PORTD&=(~(1<<PD3));
		}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I created the variable like this :

volatile uint16_t count=0;    //Main revolution counter

volatile uint16_t rpm=0;   //Revolution per minute

volatile uint16_t rps=0;   //Revolution per second
.
.
.
ISR(INT4_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	count++;
	lcd_cmd(0x01);
	lcd_string("interrupt4");
}

ISR(TIMER1_COMPA_vect)
{
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	rpm=rps*60;
	count=0;
	lcd_cmd(0x01);
		PORTD|=(1<<PD7);
		lcd_string("Hello");

	
}

it said "hello" for timer and if I gave input to interrupt for,
it said "interrupt 4"
but it didn't properly telling me how many interrupts per second ?

Any clues ? thanks a lot

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

bianchi77 wrote:
but it didn't properly telling me how many interrupts per second ?
What do you think which part of the shown code should actually do that?

Stefan Ernst

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

I'm expecting these codes displaying it

while(1)
	{
		lcd_string("RPM =");
		lcd_string("RPS =");

		lcd_string(rpm);
		lcd_string(rps);

		if(PINE & (1<<PE4))
		{
			PORTD|=(1<<PD7);
		}
		else

		{
			PORTD&=(~(1<<PD7));
		}

		Wait();
	}

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

lcd_string can do both, displaying a string and an integer? I doubt that. Or are you programming in C++?

PS: Wasn't "converting an integer into a string" already a topic in your numerous threads?

Stefan Ernst

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

yea you're right, I'm gonna rectify it...

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

It's displaying 0.0000000 now....
not yet counting, did I miss something elese here ?
did I set the timer and interrupt register correctly?
How can I use EIFR ?

Thanks for helping

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

I can not but repeat: Mr Bianchi should do very well in the long run if he set up a rule for himself to not post more ofrten than once every two or three hours..

This guy is not only shooting from the hip, he is doing with an *utomat*c!

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Where should I do this ?
5. print the counter on LCD

is it inside the main loop ? while{}

inside ISR(INT4_vect)
{
}

or inside
ISR(TIMER1_COMPA_vect)
{
}

thank you for helping... :)

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

I am gobsmacked by your hardware skills!

As a general rule, you make ISR()s short and sweet. e.g.
1. set/clear flags
2. set/clear peripheral registers
3. fill/empty a buffer
4. leave

You NEVER introduce any busy-loop into an ISR()
You NEVER introduce long delays or call SLOW functions
It is ok to have a delay_us(1). Anything much longer is unwise. Likewise, just use common sense with external functions.

So you might set a flag to tell main() to display count.

If you want to reset the count in the ISR(), you simply copy the current count value to another variable for main().

If you use this strategy, current_count should be volatile.

David.

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

Please correct me if I have made mistake..
current_count = count ?
sorry I'm a bit confuse on understanding it..
Have a look on my code below,
I tested on ISR for displaying something in LCD and it does,

and on
ISR(TIMER1_COMPA_vect), I put another LCD test, which is alright, what I can't understand, it can't display the value on "count" to LCD at the main loop....

may be you have better knowledge on it ?
thanks in advance

ISR(INT4_vect)
{
//CPU Jumps here automatically when INT4 pin detect a falling edge
count++;

}

the whole code :

volatile uint16_t count=0;    //Main revolution counter
volatile uint16_t rpm=0;   //Revolution per minute
volatile uint16_t rps=0;   //Revolution per second


void Wait()
{
	uint8_t i;
	for(i=0;i<2;i++)
	{
		_delay_loop_2(0);
	}
}


void main()

{
	char rpm_lcd[50],rps_lcd[50];
	// Initial PORT Used
	// Change pin 7 on bus E to an input by changing bit 7 to zero
	DDRE &= ~(1 << PIN4);
	// Defining a pull-up resistor to to pin 7 on bus E
	// to prevent input floating
	PORTE |= (1 << PIN4);
	DDR_D.b0 = 1;
	DDR_D.b1 = 1;
	DDR_D.b2 = 1;
	DDR_D.b3 = 1;
	DDR_D.b4 = 1;
	DDR_D.b5 = 0; //button input
	
	DDRA = 0xFF;
	lcd_init();
    lcd_cmd(0x01);
	lcd_string("RPM Meter");
	lcd_cmd(0xC0);
	//lcd_string("by Riko");

	Wait();
	Wait();
	Wait();
	Wait();

	//Init INT4
	//MCUCR|=(1<<ISC01 );  
	EICRB = 0x02; //Falling edge on INT4 triggers interrupt.
	EIMSK = 0x10;
	

	//Timer1 is used as 1 sec time base
	//Timer Clock = 1/1024 of sys clock
	//Mode = CTC (Clear Timer On Compare)
	TCCR1B|=((1<<WGM12)|(1<<CS12)|(1<<CS10));
	//Compare value=976

	OCR1A=976;

	TIMSK|=(1<<OCIE1A);  //Output compare 1A interrupt enable

	//Enable interrupts globaly
	sei();

	//LED Port as output
	DDRD|=(1<<PD7);


	lcd_string(0x01);

	
	while(1)
	{   
		lcd_cmd(0x01);
		sprintf(rpm_lcd,"%.2f",rpm);
		sprintf(rps_lcd,"%.2f",rps);
		lcd_xy(0,0);
		lcd_string("RPM =");
		lcd_string(rpm_lcd);
		/*
		lcd_xy(1,0);
		lcd_string("RPS =");
		lcd_string(rps_lcd);
         */
		if(PINE & (1<<PE4))
		{
			PORTD|=(1<<PD7);
		}
		else

		{
			PORTD&=(~(1<<PD7));
		}

		Wait();
	}

}

ISR(INT4_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	count++;
    //lcd_cmd(0x01);
    //lcd_string("Interrupt4");
	
}

ISR(TIMER1_COMPA_vect)
{   char rps_lcd_int4[50]; 
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	sprintf(rps_lcd_int4,"%.2f",rps);
	rpm=rps*60;
	count=0;
	//lcd_xy(1,0);
	//lcd_string("RPS =");
	//lcd_string(rps_lcd_int4);
	//lcd_cmd(0x01);
	//lcd_string("Timer1");
	
	
}
    //lcd_string("Interrupt4");
	
}

ISR(TIMER1_COMPA_vect)
{   char rps_lcd_int4[50]; 
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	sprintf(rps_lcd_int4,"%.2f",rps);
	rpm=rps*60;
	count=0;
	
	
}

is it because of I haven't put a pull up resistor ?
10K isn't it ?

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

You are getting there !
All that you need is:

ISR(INT4_vect)
{
   //CPU Jumps here automatically when INT4 pin detect a falling edge
   count++;
} 

ISR(TIMER1_COMPA_vect)
{
   //CPU Jumps here every 1 sec exactly!
   rps=count;
   count=0;
}

In main(), you simply display the current value of rps. You should make this atomic because 'rps' is 16-bit and could change at any time. e.g.

   cli();
   uint16_t current_rps = rps;
   sei();
   sprintf(...);
   lcd_string(...);

Only rps (and count) need to be volatile.
You can derive rpm from current_rps.

Note that both ISR()s are very short. Even the 'atomic copy' is only an extra CLI + SEI instruction i.e. 2 cycles.

It does not matter how slow sprintf() or lcd_string() is. Or whether an interrupt occurs half-way through your calculations.

I am sure that there are tutorials for this sort of thing. Both on this website and all over the internet. It is rather a waste of my time to start typing from scratch when professional technical authors covered all this when Adam was in short trousers.

David.

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

Just remember the rule that was mentioned for interrupts. Don't call functions from the handlers.

Imagecraft compiler user

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

Thank you very very much David for your time and explaination....I'll give a shot

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

Ok Bob, I will not call function from interrupts handler...I'll make the handle a short as possible...only raise a flag or something

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

This is the code I have written,
I got RPS = 0.00 and haven't picked up / counted properly, keep on 0.00

I wrote as you suggested me, any advices, perhaps I put a wrong configuration for timer or interrupt ?

Thanks a lot

while(1)
	{
		 cli();
		 uint16_t current_rps = rps;
		 sei();
		
	        sprintf(rps_lcd,"%.2f",current_rps);
		lcd_xy(0,0);
		lcd_string("RPS =");
		lcd_string(rps_lcd);
  }

ISR(INT4_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	count++;
   
	
}

ISR(TIMER1_COMPA_vect)
{   //char rps_lcd_int4[50]; 
	//CPU Jumps here every 1 sec exactly!
	rps=count;
	
	count=0;
	
	
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, I am surprised that you get anything. You are sending an uint16_t to a f-p format. current_rps is only an integer. Try:

           sprintf(rps_lcd,"%u",current_rps);
           sprintf(rpm_lcd,"%.2f", current_rps / 60.0);

I am still amazed that someone so competent with hardware should struggle with 'software' documentation. After all, you must have read plenty of hardware docs!

David.

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

yes I need to read a lot of software documentation...and learn..

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

David,

for RPM, is it supposed to be

sprintf(rpm_lcd,"%.2f", current_rps * 60.0);

?

I got RPS = 1 and RPM = 60, but if I put the real pulse...it's not yet stable the output, did I put the timer incorrectly ?

//Compare value=976
OCR1A=976;

I use pre scaler 1:1024 and clock = 16Mhz,

thanks

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

This the circuit for input into INT4,
It's from photo transistor and op-amp

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

You have never said what range of RPM you are expecting.

For slow RPM, you measure the period of one cycle.
For faster RPM, you count pulses.

For a bicycle, you want to display as soon as you have a revolution. Subsequent revolutions are added to the data, and hence you get a more realistic speed value.

OCR1A = (F_CPU / 1024 / Hz) - 1;  // i.e. 975.56 for 1MHz and 1 second.

No, I have not looked at your schematic. You need to have a bounce-free signal for INT4 to count. I would debounce in software. You would probably use a low pass filter and schmidt.

My bicycle has relatively low RPS. If you are only using integer math, you get lousy results.

David.

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

so in my clock 16Mhz

I need to put 15624 on OCR1A

(16Mhz/1024/1Hz)-1 = 15624

I put low pass filter already with the cut off 1/2pi RfCf...

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

It does a correct counting already but, as you say,

My bicycle has relatively low RPS. If you are only using integer math, you get lousy results.

So will I use float and convert to integer ? since I need to convert to string to display it on LCD ?

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

1 / (2*3.14*68K*1uF) = 2.34 Hz..

Probably like this, for the debounce you're talking about ?

ISR(INT4_vect)
{
	//CPU Jumps here automatically when INT4 pin detect a falling edge
	_delay_ms(150);
	count++;
 	
}
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Don't put a delay in the interrupt handler. In fact, you could do the whole darn program without using interrupts. Its just a loop that reads the input, when you see it change, you read the timer, do the calc and display it, clear the timer.

Imagecraft compiler user

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

Oh, ok, what do you mean by :
Its just a loop that reads the input, when you see it change, you read the timer, do the calc and display it, clear the timer.

Any references or links for it ?
How can I put the input without INT4 ? just use PIN input ? and count it ?
Thank you

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

Tell me what pin the input is on, what avr, what xtal, and I'll take a whack at it.

Imagecraft compiler user

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

Currently I'm using PE4 (INT4) as input pin,
the chip is ATMEGA128, and the crystal is 16Mhz,

Thanks very much for helping

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

What is your problem?
What is your speed range?
What actually generates your input signal?

Electric motors may be 1000 - 20000 RPM (16 - 333 RPS)
Diesel engines may be 500 - 3000 RPM (8 - 50 RPS)
My pushbike is considerably slower. (1 - 10 RPS)

You appear to have low-pass filters with time-constants of 68ms. So you will degrade any signal faster than 14 RPS.

For a hardware person, you seem to have difficulties with numbers!

Most mechanical switches will bounce for a milisecond or three. I suppose that some might bounce for 68ms. They would not be very suitable for revolution counting.

Unless you are measuring very fast rotation, you probably want better resolution than units of "60 RPM". So you count revolutions over a longer period than one second. e.g. 10 seconds or 60 seconds. If you want to display a 'result' before the 10 seconds have elapsed, you calculate from the period.

With a 16MHz AVR and short ISR()s you really don't need to worry about 3us latency. However you can get 60ns precision with the ICP pin.

David.

Last Edited: Tue. Sep 24, 2013 - 12:30 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm going to count my heart beat that's the purpose...
Heartbeat's about 1 to 3 RPS I guess...It's not easy to be 4....

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

Life would have been a lot simpler if you said this in the first place. Just look at any regular blood pressure machine. It counts several seconds worth of heartbeats.

Your stategy would give you a result of 0, 60, 120, 180, ...
My pulserate is about 66. You would register 60 !
Someone with a pulserate of 59 would register as zero.

Let's just hope you are not in the medical profession.

David.

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

mine is about 57, how can I calculate 57 ?
Yes you're right I'm not in medical profession..

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

Count microseconds between pulses and divide by a million?

Imagecraft compiler user

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

Quote:
If you want to display a 'result' before the 10 seconds have elapsed, you calculate from the period.

Read through some of your 444 posts. I guess they have had at least 444 replies. Most threads have given you excellent advice within the first few posts. You just need to read the replies!

As a silly question: Why do you want to know your pulse?

Without the blood pressure it is fairly useless.

David.

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

Quote:

Without the blood pressure it is fairly useless.

There speaks a man who hasn't played GTA V yet. I just did the mission where you torture someone. The BPM figure alone is fairly crucial to the process ;-)

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

Quote:

how can I calculate 57 ?

Again.. Pick out paper and pencil. Can you do the calculation by hand?

No? Then there are no hopes of you coding for it.

Yes? Let's talk..

As of January 15, 2018, Site fix-up work has begun! Now do your part and report any bugs or deficiencies here

No guarantees, but if we don't report problems they won't get much of  a chance to be fixed! Details/discussions at link given just above.

 

"Some questions have no answers."[C Baird] "There comes a point where the spoon-feeding has to stop and the independent thinking has to start." [C Lawson] "There are always ways to disagree, without being disagreeable."[E Weddington] "Words represent concepts. Use the wrong words, communicate the wrong concept." [J Morin] "Persistence only goes so far if you set yourself up for failure." [Kartman]

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

Of course, yes, what's the formula ? befor I code it,thanks for the clue

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

JohanEkdahl wrote:
Quote:

how can I calculate 57 ?

Again.. Pick out paper and pencil. Can you do the calculation by hand?

No? Then there are no hopes of you coding for it.

Yes? Let's talk..

Please tell me how to calculate 57 ?
is related with variable becoming float ?
thanks

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

david.prentice wrote:
Quote:
If you want to display a 'result' before the 10 seconds have elapsed, you calculate from the period.

Read through some of your 444 posts. I guess they have had at least 444 replies. Most threads have given you excellent advice within the first few posts. You just need to read the replies!

As a silly question: Why do you want to know your pulse?

Without the blood pressure it is fairly useless.

David.

because I want to know my resting heart beat...

Pages