## Timer0 and _delay_ms

37 posts / 0 new
Author
Message

I am modifying an RFID project on an old ATmega128.  I added Timer0 with an interrupt and ISR to toggle a pin every 64 clock cycles to generate a 125kHz square wave.  In another place in the code I have a _delay_ms(1000.0); so the tag reader part of the code updates the tag number and time and date on an LCD every second if it successfully reads a tag.  With the Timer0 and ISR added the _delay_ms(1000) updates the display every 3 seconds instead of every 1 second.  I am really surprised the Timer0 business slows down _delay_ms() by a factor of three.  If the ISR is empty and doesn't do anything the slow down in _delay_ms() is a factor of two.  Is this something to do with the old architecture of the ATmega128, or should I expect it with a more modern mega.  Does this seem obvious to everyone else because it really surprised me.

Basically, _delay_ms cannot expect an accurate time on a system that uses interrupts.

_delay_ms counts the number of clocks on the assumption that there are no interrupts.

If there is an interrupt, the number of clocks cannot be counted during that time, so the time will increase.

As kabasan already said the delay functions just burn clock cycles. at compile time a calculation is done on how many clocl cycles would cause the specific delay, not taking into account any interrupts.

The AVR is designed such that when an interrupt is triggered it will always cause the processor to save the current program counter position on the stack, then jump to the corresponding interrupt vector address in the interrupt vector table and from there there is most likely a jump to the ISR routine location. This might be nothing more than a reti, but that will cause the program to load the program counter from the stack again and then normal operation is resumed.

I am not sure but IIRC there are datasheets that explained in more detail what happens when an interrupt is actually fired, but I have seen so many datasheets of different processors (not only atmel) that I do not recall which one it actually is.

You should however be able to see in the list file how the empty ISR is handled. Is there a reti in the interrupt vector table or a jmp/rjmp to the ISR location and then there just a reti or is it actually pushing and popping registers too(which I do not expect)

This is what the empty ISR looks like:

000007f0 <__vector_15>:
7f0:	1f 92       	push	r1
7f2:	0f 92       	push	r0
7f4:	0f b6       	in	r0, 0x3f	; 63
7f6:	0f 92       	push	r0
7f8:	11 24       	eor	r1, r1
7fa:	0f 90       	pop	r0
7fc:	0f be       	out	0x3f, r0	; 63
7fe:	0f 90       	pop	r0
800:	1f 90       	pop	r1
802:	18 95       	reti

So it is not 'empty', but takes 16 clock cycles at least (push and pop is 2 cycles I took 1 for the rest) so that will make your delay longer.

But why not do the time keeping in the ISR too? have a uint16 incremented on every interrupt and then when a number of interrupts are passed you have had 1 second.

meslomp wrote:
But why not do the time keeping in the ISR too? have a uint16 incremented on every interrupt and then when a number of interrupts are passed you have had 1 second.

I am not so good working at the machine code level.  I do this usually when I want to have a timer and have the interrupt thrown every millisecond instead of using _delay_ms():

ISR(TIMER1_COMPA_vect)
{
if (uiTime_counter_1A > 0)
{
uiTime_counter_1A--;
}
}

I was surprised how many cycles the empty ISR takes.  When I add the toggle of the port pin it becomes:

000007f0 <__vector_15>:
7f0:	1f 92       	push	r1
7f2:	0f 92       	push	r0
7f4:	0f b6       	in	r0, 0x3f	; 63
7f6:	0f 92       	push	r0
7f8:	11 24       	eor	r1, r1
7fa:	8f 93       	push	r24
7fc:	9f 93       	push	r25
7fe:	95 b3       	in	r25, 0x15	; 21
800:	81 e0       	ldi	r24, 0x01	; 1
802:	89 27       	eor	r24, r25
804:	85 bb       	out	0x15, r24	; 21
806:	9f 91       	pop	r25
808:	8f 91       	pop	r24
80a:	0f 90       	pop	r0
80c:	0f be       	out	0x3f, r0	; 63
80e:	0f 90       	pop	r0
810:	1f 90       	pop	r1
812:	18 95       	reti

which looks like a lot of cycles considering the interrupt is being thrown every 64 cycles to get 125kHz.  It doesn't leave much time for anything else to happen.  Now, at least, I understand what is going on.  Thanks for your help.  I am just disabling the Timer0 interrupt when I'm in the part of the code that reads a tag, and re-enable it when I want the 125kHz out to program a tag.  The two parts of the code never happen at the same time.  I was just mystified why the _delay was so slow, and never thought to look at the machine code because I never work at that level.

looks like a lot of cycles considering the interrupt is being thrown every 64 cycles to get 125kHz.

Yep.  Even an optimized assembly language ISR with nothing but a "reti" instruction at the vector location is going to take 8 cycles; 11 cycles with the usual "jmp" at the vector location.

8/64 or 11/64 is a good portion of the CPU!  For generating a 125kHz signal, you ought to be using the timer hardware...

My question is that if you are running some timer with interrupts why would you ever need _delay_ms() anyway? I agree that having a 8us interrupt means you do have to count a lot to reach 1000ms but perhaps you could run a second timer with an interrupt at something more sedate like 10ms (even 100ms) and then counting to 1000ms is not too much to handle.

Oh and this fast interrupt that simply toggles a pin - why is there an interrupt and IO accessing code anyway? Surely you can just have the timer toggle (one of) its output pin(s) ? True you are tied to which pins the timers own so this might involve a little re-wiring.

Last Edited: Tue. Feb 9, 2021 - 10:13 AM

clawson wrote:
My question is that if you are running some timer with interrupts why would you ever need _delay_ms() anyway?

It's really old code I am adding to that had the _delay before I knew much about timers.

clawson wrote:
you could run a second timer with an interrupt at something more sedate like 10ms (even 100ms) and then counting to 1000ms is not too much to handle.

I did that with Timer1, scalar 64, and OCR1A = 249, I can't run both timers at the same time or things get bogged down, but they are for the two different sections of the code, so I am disabling the interrupt for the one not being used.

clawson wrote:
Oh and this fast interrupt that simply toggles a pin - why is there an interrupt and IO accessing code anyway? Surely you can just have the timer toggle (one of) its output pin(s) ? True you are tied to which pins the timers own so this might involve a little re-wiring

Basically because I didn't think of it.  I have a ribbon cable that runs from the 128 to the board that turns the square wave into a 30Vpp or 13.5Vpp sine wave.  Two of the conductors in the ribbon run 74HC4066 switches to go back and forth between between the two output sine waves.  I need to be able to count cycles - so many at 13.5Vpp, then so many no output, then 128 cycles at either 30Vpp or 13.5Vpp to program low or high bits, and using the interrupt seemed at first glance an easy way to do it.  I knew I was getting into risky territory with the interrupt being thrown so often.  It looks like the timer pins are in PORTB, so I could move my switch definitions to that port.  That port also has the SPI pins, but that's not a problem.  I guess I could count cycles by polling that pin, but I am not sure how to turn off the pin cycling for a given number of cycles and be able to count cycles at the same time.  I'll think about it.  I guess I could use another of the 4066 switches to let the square wave through to the analog hardware.  The chip has 4 and I am only using 2.

westfw wrote:
For generating a 125kHz signal, you ought to be using the timer hardware...

So it seems.  I had no idea the interrupt took so many cycles, but I had a feeling I was going to get into trouble.

Thanks for the input guys.  I appreciate it.

Mark, here's some ancient code from a project I'm currently updating:

// clock times
static volatile uint8_t ticks;			// 192 of these make 1/50th of a second
static volatile uint8_t fiftieths;
static volatile uint8_t seconds;
static volatile uint8_t minutes;
static volatile uint8_t hours;			// we don't keep time of day, just time from start

SIGNAL (SIG_OUTPUT_COMPARE1A)
{
// the interrupt signal on a compare for the timer 1
// increment the clock
ticks++;
if (ticks == 192)
{
ticks = 0;
fiftieths++;
if (fiftieths == 40)
{
// turn led on
PORTD |= 0x80;
}
if (fiftieths == 50)
{
// turn led off
PORTD &= 0x7f;

fiftieths = 0;
seconds++;
if (seconds == 60)
{
seconds = 0;
minutes ++;
if (minutes == 60)
{
minutes = 0;
hours++;
}
}
}
}
}

void timer_init (void)
{
// we wake up the timer, preset the clock and uart variables, and enable the ocr1a interrupt
ticks = 0;
fiftieths = 0;
seconds = 0;
minutes = 0;
hours = 0;

TCCR1A = 0;					// ctc, all pins normal
TCCR1B = 9;					// ctc, no prescaler

// 833 = 0x341

OCR1AH = (3);
OCR1AL = (0x41);			// preset compare counter to 833

//	timer_enable_int(_BV(OCIE1A));
TIMSK1 = _BV(OCIE1A);		// allow interrupts on output mask a
}

With apologies for the cryptic magic numbers: the interrupt is arranged to happen 192 * 50 times a second (= 9600 - originally it was also a clock tick for a 2400 baud soft serial port) and everything in the SIGNAL section is just dividing that down into ticks, seconds, minutes, hours - a time since started rather than real time. Further apologies that the approved naming for interrupt handlers has probably changed since I wrote it too; I don't know the modern flavour!

A simplification of this would be simply to count convenient chunks of time as a tick - if you run into something which needs a timeout, for example, you note the ticks at the start and then

while (((now_tick - start_tick) < timeout_ticks) && (false == sought_event))
{
// look for event we're seeking
}

which means you don't have to have *any* blocking delay loops in your code.

You can even use both methods if you require HMS as well as arbitrary delays just by adding another variable - a uint32_t is probably long enough - to increment at each tick.

My current (ARM) code has a few examples of hard delays and a lot more off communications timeout delays; this mechanism allows me to use both with ease (and no machine code involved). The HAL code provides me a 1ms system tick and access to its value, which is what the code above allows. (There are other issues with the ARM system tick, but they're not relevant here).

Neil

p.s. please note I wouldn't write the code in quite the same style these days: in particular

if (fiftieths == 50)

constructs would all be written

if (50 == fiftieths)

in accordance with MISRA recommendations, for reasons which have recently been rehearsed elsewhere.

Note that if you can afford to clobber or reserve a register or two for your IRQ, then all the wasteful push/pops are not needed.

Also, while you generally must save & restore the SREG (upon entering & exiting the IRQ), if the IRQ does nothing effecting the sregister bits (such as carry, neg, zero, etc), then that also can be skipped.

For example, toggling a port pin does not affect the sreg at all, so if your irq only toggles a pin, the  IRQ can be quite efficient:

If a register is plentiful, it can be permanently assigned for this purpose:

.def pins2flip=r19

ldi pins2flip, 0b00001001

my_irq:  out PINB, pins2flip   ; toggle pins PB3 and PB0
reti     ; return from the timer IRQ 

If a free register is not avail, then the register must be set each time in the IRQ:

myirq:  push pins2flip

ldi pins2flip, 0b00001001

out PINB, pins2flip   ; toggle pins PB3 and PB0

pop pins2flip

reti     ; return from the timer IRQ

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Feb 11, 2021 - 09:15 AM

Hey Neil, that is some old code.  I don't think SIGNAL is even valid anymore, it being replaced with ISR.  I do something similar in #6, with a similar init function

avrcandies wrote:
For example, toggling a port pin does not affect the sreg at all, so if your irq only toggles a pin, the  IRQ can be quite efficient:

Candyman, I had assumed the simple ISR did something that efficient.  That is very cool.  I am at the disadvantage of never having learned an assembly language, but spent a lot of years doing algorithms in fortran, c, and c++(inadequately).  Your example piques my interest, but I'm not sure I'm up for learning a new language with all the frustrations involved at this point in my life.  It is interesting, though, and I am tempted.

I'm just going to let the timer hardware toggle the OC0 pin, poll it in software to count cycles, and use the 4066 switch on my board to null out the square wave going into the analog part when needed.

Or I could learn AVR assembler.

Or I could learn AVR assembler.

I wouldn't say there is so much to learn (especially if you have a programming mindset: loops, assignments, bits, ands, ors, etc). There are not really that many commands to look at (some have many variations, such as conditional branch types)...give it a few hours and you will be well on your way.

http://www.avr-asm-tutorial.net/...

I did mess up some years ago, with an error that took some time to track down---it should "obviously" work (but that is wrong)

inc RPM_low

adc RPM_high, zero   ;propagate any carry into high byte

I had a mental lapse-- when the low byte rolled over, that the high byte addition would capture the carry....problem is , the inc wraparound doesn't set the carry bit on the AVR (and on most other chips too).   I think there may be a few oddballs that do set it.

This caused some very strange results!

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Wed. Feb 10, 2021 - 08:33 PM

avrcandies wrote:

I wouldn't say there is so much to learn (especially if you have a programming mindset: loops, assignments, bits, ands, ors, etc). There are not really that many commands to look at (some have many variations, such as conditional branch types)...give it a few hours and you will be well on your way.

http://www.avr-asm-tutorial.net/...

Maybe a few hours for you.  I have the programming mindset of 30+ years of Fortran and C.  C++ was sort of a mystery to me when I came to it late in life so all my C++ code looked like C hacked into the whole object thing.  The tutorial all looks straightforward enough, and I see I can add a new assembly file to a project in AS7.  Maybe I will play around with it.  I already don't understand why your "out" command toggles those pins.  When I look at OUT in the tutorial it looks like it puts a register into a port, so frustration is setting in early.

Well, surely you have read the AVR data sheets at some point---this would be the same regardless of which language you use...the PIN register can be used to toggle many AVR chips ...newer chips may have some other considerations, since they have more IO control registers....so you need to know your chip better than your favorite uncle (Fester).

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Wed. Feb 10, 2021 - 11:08 PM

Certainly I have read many parts of many datasheets, usually for stuff I need to do.  I can't read one like a novel for just random information.  I know about PORTx, DDRx, and I use PINx to read a pin.  I never saw the highlighted portion of a datasheet because I never worried much about memory locations in C.  That's for the compiler.  In any case, that information was enlightening.  The OUT command from the tutorial is :

so I naturally assumed I needed to supply a port and a register, but I guess when you use OUT with the PINx port for p1 in the command you supply a value rather than a register for r1.  That was part of my continuing confusion.  Given that i knew putting a 1 to a PINx register pin would toggle the value, from the tutorial I would expect to provide OUT a register value r1 containing the 0b00001001 rather than the value itself.  These are the subtleties that make things difficult for a newbie, but are obvious to you.

avrcandies wrote:
Well, surely you have read the AVR data sheets at some point-
avrcandies wrote:

my_irq:  out PINB, 0b00001001   ; toggle pins PB3 and PB0

I don't understand how R9 got the magic value.  Surely someone saying "it is OBVIOUSLY better to do it this way" would give an example that would be correct.  [Yes, I realize that it may well assemble but will wager a cold one that R9 isn't intended.]

To avoid affecting SREG one would either need to dedicate a pre-loaded register as with your [I assume] intended advice, or if a sub-microsecond difference in timing is allowed and the PINx is in range of the instruction on that model, a pair of SBI would suffice.

Now, I gave a trick answer.  OP's device is a Mega128.  Not only are not all of the PINx in range in the I/O map of that model, but also there is no PINx toggle on that dinosaur.  To quote a famous commenter

avrcandies wrote:
Well, surely you have read the AVR data sheets at some point

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.

MarkThomas wrote:
but I guess when you use OUT with the PINx port for p1 in the command you supply a value rather than a register for r1.

No, you can't, ever.  And on that model, there is no toggle with a write to PIN.

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.

theusch wrote:

MarkThomas wrote:

but I guess when you use OUT with the PINx port for p1 in the command you supply a value rather than a register for r1.

No, you can't, ever.  And on that model, there is no toggle with a write to PIN.

I am only using this dinosaur because my very first RFID project was on it and I am just adding some stuff because it's convenient.  It wasn't so old back then.  I am using ATmega1284's these days that probably have the toggle.

So if I use the OUT command with a PORTx, I still use a value instead of a register containing what I want PORTx to become?  The documentation suggests it maps some chosen register to a port register, not a value, which is what is confusing me.  The document says   "Register => port  :  OUT(p1, r1)"  , so I should interpret r1 as a value and not a register containing a value??   What's a newbie to do?  I would think in that case the documentation would say "Value => port  :  OUT(p1, v1)"

Edit:  ATmega1284

Last Edited: Thu. Feb 11, 2021 - 02:53 AM

, so I should interpret r1 as a value and not a register containing a value??

Not in assembler, you LOAD the register with a value and then OUT to the port (or STS to the port if the port is outside the OUT range) (can't write...)

ie

	ldi		r16,(1<<ctc2)|(1<<cs22)|(1<<cs21) ;Clear on compare, clk /256
out		tccr2,r16
ldi		r16,78				;Compare set for ~2.5mS (125ns*256*78=2.496mS)
out		ocr2,r16
ori		r16,(1<<OCIE2)			;Enable output compare
out		timsk,r16

and one more using STS because the 2 registers are outside the OUT range. Note that this is some old code before I discovered macros that will determine which instruction to use.

;Setup Watchdog, normal timeout period to 2 secs
cli
wdr
ldi		temp,(1<<wdce)|(1<<wde)		;Start WD change timed  sequence
sts		WDTCSR,r16
ldi		temp,(1<<wde)|(1<<wdp2)|(1<<wdp1)|(1<<wdp0)
sts		WDTCSR,temp
sei

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Last Edited: Thu. Feb 11, 2021 - 03:40 AM

js wrote:
Not in assembler, you LOAD the register with a value and then OUT to the port

John, that makes sense to me if the argument in the OUT is the register designation.  But avrcandies example was:

avrcandies wrote:

my_irq:  out PINB, 0b00001001   ; toggle pins PB3 and PB0
reti     ; return from the timer IRQ 

where 0b0000101 looks like value and not a register.  I'm confused.

The OUT instruction above is writing to the INPUT register, in some newer chips you can write to the input register to toggle bits but I have never done it that way so don't know if it works.

Off to piano lessons now

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

js wrote:
The OUT instruction above is writing to the INPUT register

OK.  What I dont understand is how I get that from the documentation.  The only reference to OUT I could find said:    "Register => port  :  OUT(p1, r1)".  So p1 is PINB (which is a port), and r1 is 0b0001001.  r1 isn't a register.  It's a value.  There is too much I don't know.

r1 isn't a register.  It's a value.

It's a register (GP register, R0-R31, not a system register like a timer register) previously LOADED up with a VALUE. The Mega128 does not have the facility to output to an input register so don't try to use it.

By the way you can only load a register with an immediate value (LDI) for registers R16-R31, you cannot load immediate a low register R0-R15.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

Oh yes my apologies!! ...you cannot supply an immediate value to OUT, it must be a register (but would be nice if you could)

FAIL!!

I went and edited my post, my compiler will catch such flubs in a real typing experience!

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Feb 11, 2021 - 09:19 AM

Ah, I knew I was going nutty, since I remember doing a compact IRQ toggle in the past. You don't use OUT, you can use sbi & the pin of your choice, if your AVR supports the PIN register toggle functionality (and if the PIN reg is reachable by sbi)

my_irq:  sbi PINB, 3   ; toggle pin PB3
reti          ; return from the timer IRQ 

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Thu. Feb 11, 2021 - 03:55 PM

Oh, Candyman, you were making me crazy.  I'm thinking of learning a new language and already the syntax makes no sense.  I'm happier now.  Next I need to figure out how my_irq gets invoked when the timer throws an interrupt.  I don't know anything.

js wrote:

It's a register (GP register, R0-R31, not a system register like a timer register) previously LOADED up with a VALUE. The Mega128 does not have the facility to output to an input register so don't try to use it.

By the way you can only load a register with an immediate value (LDI) for registers R16-R31, you cannot load immediate a low register R0-R15.

Thanks John.  I understand what you are saying about the Mega128 limitations.  I'm using it because my original RFID code from 2011 is on one, and I am just being lazy and adding to it instead of moving it all over to a new processor.  I bought 5 Mega128 boards in 2011 from Futurlec that are sort of like an Arduino, and I still have 4 left.  I hate to waste stuff, and they are nice little boards.  This is all theoretical at this point anyway.  It's sort of a "hello world" situation for me to get started with a new language.  I "learned" C++ by being hired to add features to a huge piece of C++ code.  Now I am looking for examples to learn assembler.  So far the frustration level is about what I expected.  I used to get paid to do this stuff, and have never found learning a new language that much fun.  But I needed the money.  Now it is a hobby.  Maybe I should take piano lessons instead.

how my_irq gets invoked when the timer throws an interrupt

A flag gets set by the timer  (overflow, compare etc. don't know which one is being used in your code)

The address where the processor is at is saved on the stack automatically.

Now the processor goes to the interrupt vector at the start of the Flash where YOU put the address of the ISR. These are all the vectors for the M128, I keep them in a file (same for other chips) that I add at the beginning of flash.

;Reset and Interrupt vectors Mega64 and Mega128

.CSEG

;
;0x0000
jmp 	RESET 		; Reset Handler
;0x0002
jmp 	ISR_EXT_INT0 	; IRQ0 Handler
;0x0004
jmp 	ISR_EXT_INT1 	; IRQ1 Handler
;0x0006
jmp 	ISR_EXT_INT2 	; IRQ2 Handler
;0x0008
jmp 	ISR_EXT_INT3 	; IRQ3 Handler
;0x000A
jmp 	ISR_EXT_INT4 	; IRQ4 Handler
;0x000C
jmp 	ISR_EXT_INT5 	; IRQ5 Handler
;0x000E
jmp 	ISR_EXT_INT6 	; IRQ6 Handler
;0x0010
jmp 	ISR_EXT_INT7 	; IRQ7 Handler
;0x0012
jmp 	ISR_TIM2_COMP 	; Timer2 Compare Handler
;0x0014
jmp 	ISR_TIM2_OVF 	; Timer2 Overflow Handler
;0x0016
jmp 	ISR_TIM1_CAPT 	; Timer1 Capture Handler
;0x0018
jmp 	ISR_TIM1_COMPA 	; Timer1 CompareA Handler
;0x001A
jmp 	ISR_TIM1_COMPB 	; Timer1 CompareB Handler
;0x001C
jmp 	ISR_TIM1_OVF 	; Timer1 Overflow Handler
;0x001E
jmp 	ISR_TIM0_COMP 	; Timer0 Compare Handler
;0x0020
jmp 	ISR_TIM0_OVF 	; Timer0 Overflow Handler
;0x0022
jmp 	ISR_SPI_STC 	; SPI Transfer Complete Handler
;0x0024
jmp 	ISR_USART0_RXC 	; USART0 RX Complete Handler
;0x0026
jmp 	ISR_USART0_DRE 	; USART0,UDR Empty Handler
;0x0028
jmp 	ISR_USART0_TXC 	; USART0 TX Complete Handler
;0x002A
;0x002C
jmp 	ISR_EE_RDY 		; EEPROM Ready Handler
;0x002E
jmp 	ISR_ANA_COMP 	; Analog Comparator Handler
;0x0030
jmp 	ISR_TIM1_COMPC 	; Timer1 CompareC Handler
;0x0032
jmp 	ISR_TIM3_CAPT 	; Timer3 Capture Handler
;0x0034
jmp 	ISR_TIM3_COMPA 	; Timer3 CompareA Handler
;0x0036
jmp 	ISR_TIM3_COMPB 	; Timer3 CompareB Handler
;0x0038
jmp 	ISR_TIM3_COMPC	; Timer3 CompareC Handler
;0x003A
jmp 	ISR_TIM3_OVF 	; Timer3 Overflow Handler
;0x003C
jmp		ISR_USART1_RXC 	; USART1 RX Complete Handler
;0x003E
jmp 	ISR_USART1_DRE 	; USART1,UDR Empty Handler
;0x0040
jmp 	ISR_USART1_TXC 	; USART1 TX Complete Handler
;0x0042
jmp 	ISR_TWI 		; Two-wire Serial Interface Handler
;0x0044
jmp 	ISR_SPM_RDY 	; SPM Ready Handler
;


so if you say are using overflow then the processor will go to ISR_TIM0_OVF which will point to the ISR.

Once the ISR is finished and you put a RETI at the end, the saved address on the stack is retrieved and the processor resumes from where it left off before the interrupt.

IMPORTANT REMEMBER: Save the SREG at the start of the ISR and restore it just before the RETI or bad things will happen.

Another file I use to cover all ISR, you just need to fill in the actual code to service the ISR, SREG is saved and restored. This trick I learned very early in the piece from another freak who hasn't posted for many years.

;Mega 64/128 Interrupt service routines
;REMEMBER to save any registers you may want to use here!
;Int. flags are automatically cleared.
;

.CSEG

ISR_EXT_INT0:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT1:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT2:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT3:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT4:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT5:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT6:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EXT_INT7:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM2_COMP:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM2_OVF:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM1_CAPT:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM1_COMPA:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM1_COMPB:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM1_OVF:

in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg		;Restore Status register
reti
;
ISR_TIM0_COMP:
in		save_sreg,sreg		;Save Status register
;Do something here
out		sreg,save_sreg		;Restore Status register
reti
;
ISR_TIM0_OVF:
in		save_sreg,sreg		;Save Status register
;Do something here
out		sreg,save_sreg		;Restore Status register
reti
;
ISR_SPI_STC:
in		save_sreg,sreg		;Save Status register
;Do something here
out		sreg,save_sreg		;Restore Status register
reti
;
ISR_USART0_RXC:
in		save_sreg,SREG		;Save Status register
;Do something here
out		SREG,save_sreg	;Restore Status register
reti
;
ISR_USART0_DRE:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_USART0_TXC:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_EE_RDY:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti			;
;
ISR_ANA_COMP:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM1_COMPC:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM3_CAPT:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti

ISR_TIM3_COMPA:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM3_COMPB:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TIM3_COMPC:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
ISR_TIM3_OVF:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_USART1_RXC:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_USART1_DRE:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_USART1_TXC:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_TWI:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;
ISR_SPM_RDY:
in		save_sreg,sreg	;Save Status register
;Do something here
out		sreg,save_sreg	;Restore Status register
reti
;

I use R15 exclusively as save_sreg

;Limited purpose register=R0 to R15
.def	save_sreg=r15		;Status reg store during ints.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

I use R15 exclusively as save_sreg

That's a nice efficient move, saves a lot of the pushing & popping (as long as you don't allow any nested IRQs, which would be a sudden wrench to the saving).  Nested IRQ's are where many birds have fallen.

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

John,

It is starting to make sense.  Thanks.  How do you put that into the beginning of flash?

Nested IRQ's are where many birds have fallen.

My programs don't require anything that evil.

How do you put that into the beginning of flash?

A typical beginning of the start file for a project.

;Mega64 start up code

.nolist
.include "m64def.inc"
.include "c:\avr\lib\ASCII_table.asm"
.include "c:\avr\lib\utility_macros.inc"
.include "ppint64_equ.asm"
.list

.dseg
ramstart:

;MUST be exactly in the same order as the EEPROM list
;Start of copy of EEP_paramters
;************************
Ram_parameters_start:

NEXT_MSG_TMR:	.byte	1       ;Next msg rotate time

SCROLL_TIMER:	.byte	1       ;Message scroll time

SCROLL_DWELL:	.byte	1       ;Scroll Msg dwell time

BLANK_TIMER:	.byte	1       ;Blank time before new msg

NUMSG_TIMER:	.byte	1       ;Added to new message scroll time

OBRLY_FLG:		.byte	1       ;Non zero=relay set on new message
;regardless of switch 2(1)

Ram_parameters_end:
;************************
;End of copy of EEP_paramters

led_timer:		.byte	1       ;Timer buffer for flashing led
;
PWRUP_STAT:		.byte	1
N_OF_MSGS:		.byte	1		;0=no msg, 1=1msg, \$80=multi msgs

;INBUFF:			.byte	BUFFLNG         ;Used only in dowload
CNTR:			.byte	1
YTEMP:			.byte	2
XTEMP:			.byte	2
BTEMP:			.byte	1
TMP1:			.byte	1       ;Used only in dowload
TMP2:			.byte	1		;Used only in dowload
TMP3:			.byte	1		;Used only in dowload
TMP4:			.byte	1		;Used only in dowload
PTR3:			.byte	1		;Used only in dowload
SHFTREG:		.byte	2		;Hex input shift register

NEW_MSG_FLG:	.byte	1		;0=rotate msg, 1 new msg
ROTATE_FLG:		.byte	1
SCROLL_FLG:		.byte	1
SCRL_FLG2L:		.byte	1		;2 line scroll flag
BLANK_FLG:		.byte	1		;Blank in progress flag

PRINT_PTR:		.byte	2

TIMER1:			.byte	1
TIMER2:			.byte	1
TIMER3:			.byte	1
pp_pulse_timer:	.byte	1		;Pulse for pocket pager timing

;Powerup_timer:	.byte	1		;Power up delay timer
Timer1_tic:		.byte	1		;H'ware timer 1 tic

;Control char for display OP1 and OP2, for line 1
;OP1A and OP2A for line 2 if required
OP1_BUFFER:		.byte	1       ;Control char for display OP1
OP2_BUFFER:		.byte	1       ;Control char for display OP2
OP1A_BUFFER:	.byte	1       ;Control char for display OP1A
OP2A_BUFFER:	.byte	1       ;Control char for display OP2A

PPAG_BUF:		.byte	181		;P/pager packet 08 buffer
PPAG_BUF_END:					;sized for 120 char+control
OTHER_PKT:		.byte	7		;Buffer for pkt type 28 and 05
PKT05_PTR:		.byte	2       ;Pointer to 05 pkt with 08 pkt
ASCII_BUF:		.byte	161     ;Max ASCII data 160+end of text
ASCII_INDEX:	.byte	2       ;Index to ascii buffer
ASCII_CNTR:		.byte	1       ;ascii bytes counter

TST_MSG_FLG:	.byte	1

sent_once_flg:	.byte	1       ;1=long message already sent once

cursor:			.byte	2		;Cursor position
CR_control_ptr:	.byte	2		;PKIO_DATA pointer for CR type packets
CRPK_N_BYT:		.BYTE	2       ;Number of bytes in CR packet (16 bit)
char_cntr_save:	.BYTE	2 		;Save number of chars in current msg
msg_bfr:		.byte	24		;

.set	ROT_TIM=TIMER1  		;Timer 1 for Msg Rotate timing
.set	PWRUP_TIM=TIMER2  		;Timer 2 for power up timer
.set	BLANK_TIM=TIMER2  		;Timer 2 also used for BLANK timer
.set	SCROLL_TIM=TIMER3 		;Timer 3 for Msg scroll timing

;Include Mega 64 vectors jump table
.include "c:\avr\lib\int_v_m64_m128.asm"   ;**************************

.CSEG

;Main program starts here

RESET:

as you can see I include some utility files with no real code in them at the start, followed by a .dseg RAM area, then I include the "int_v_m64_m128.asm" just before the main code, this file has

;Reset and Interrupt vectors Mega64 and Mega128

.CSEG

;
;0x0000
jmp 	RESET 		; Reset Handler

the beginning of the code segment at 0x0000 with a jump (or rjmp for smaller chips) to RESET in the main code.

A bit wasteful of flash, you can simply add the vector you want instead of all of them as long as you have the org for the vector in your code, the way I do it guarantees the correct location for the vectors even though it wastes a bit of flash.

On thing to remember is that Studio adds the .inc file for the processor used automatically nowadays, it wasn't the case 20 years ago, so you don't need to add it at the start of the main file.

This bit can be omitted in your project

.nolist
.include "m64def.inc"
.include "c:\avr\lib\ASCII_table.asm"
.include "c:\avr\lib\utility_macros.inc"
.include "ppint64_equ.asm"
.list

mainly because you don't have those file and don't really need them, but the framework I set up for my project looks that way and I have the nicety of not having to scroll past truckload of text before I get to the beginning of the code in the list file.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

A sample project using the above, see if it makes more sense.

## Attachment(s):

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly

You might like this old AVR intro I recently found (from when the AVR was born)---a lot of cool historical info:

https://courses.cs.washington.ed...

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

Last Edited: Fri. Feb 12, 2021 - 02:32 AM

Thanks John, that looks like what I need to get started, although I had to add the ATmegaDFP pack to my AS7 to open it all.  Thanks.  I will try to figure things out.  It's a start.  It's funny, the two included .asm file appear under dependencies in the project, and when click on them or I look at their properties they are of length 0, but if I add the files to the project, I see your definitions in the editor.  And you sort of explain that in the above post in a way I can sort of understand.  But it is an example specific to the Mega128 that I can try to figure out.  Thanks, I appreciate your efforts.

The code:

RESET:
inc r16
rjmp RESET

looks like an infinite loop that keeps incrementing the value in r16.  It enters at RESET, adds 1 to r16 and then jumps to RESET to start all over again.  This is going to be a challenge for me.  Maybe I should take piano lessons.  There is an infinite amount I don't know.

looks like an infinite loop that keeps incrementing the value in r16.

It is a standard new Studio assembler project, I just changed the labels from start to RESET to match my files, added the 2 files as .include and put a definition for save_reg otherwise you get a lot of errors building the project.

John Samperi

Ampertronics Pty. Ltd.

https://www.ampertronics.com.au

* Electronic Design * Custom Products * Contract Assembly