Interrupts

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

Post Reply
blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Interrupts

Post by blackcattech » Mon Mar 26, 2012 1:21 pm

I've read the various help documentation on interrupts in Swordfish but I'm still a little confused when it comes to context saving. I must admit I would sort of expect a high level compiler to deal with this automatically by default - I can see the benefits to being able to optimise it but the point of Basic is to be simple to use...

Anyway, are there any general guidelines as to what needs saving? I have a fairly long ISR but it doesn't call any functions but it does use a few IFs and Select..Cases. The only math is addition and subtraction. It does reference quite a few global variables including an array and some Consts. Is this likely to need any additional context saving?

The program seems to work at the moment but I'd like to make sure it is robust before letting it loose in the field...

User avatar
Senacharim
Posts: 139
Joined: Tue Aug 10, 2010 5:19 pm
Location: Ventura, CA

Post by Senacharim » Mon Mar 26, 2012 3:01 pm

There are actually some fairly comprehensive posts & responses in the forum here...
Surviving Member
Bermuda Triangle Battalion
from 2026 to 1992

Voted "Most likely to time travel"--Class of 2024.

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Mon Mar 26, 2012 3:12 pm

I must have missed them when I searched, I'll try again....

As I said before though, in Basic all this sort of thing should really be taken care of automatically...

User avatar
Senacharim
Posts: 139
Joined: Tue Aug 10, 2010 5:19 pm
Location: Ventura, CA

Post by Senacharim » Mon Mar 26, 2012 3:19 pm

At some point we all go thru similar experiences when trying to learn how advanced features work.

Here's one:
http://www.sfcompiler.co.uk/forum/viewt ... hlight=isr

And, as always, one of the most important things is to have and know the processor specifications. I've been over the data sheets for the 18F2620 so many times my copy is dog-eared, but comprehending the information contained within those pages is essential to mastering PIC programming.
Surviving Member
Bermuda Triangle Battalion
from 2026 to 1992

Voted "Most likely to time travel"--Class of 2024.

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Post by Jerry Messina » Mon Mar 26, 2012 5:55 pm

As I said before though, in Basic all this sort of thing should really be taken care of automatically...
Some compilers handle this automatically for you and some don't. Even then, what they do can depend on which version you're using (like C18). While it might be nice for it to be completetly automatic, at least you have control over it.

There's no hard and fast rules, so the safest thing to do is to take a look at the asm output. It can be a surprise.

Here are some things to look out for:

- math operations (other than integer add and subtract) will use library calls and the System variables. You can identify these as the ones starting with 'SB_SVxxx' in the asm output. To protect these, add a 'save(0)' call which will protect those 25 locations along with the FSR0, FSR1, and the PROD registers. FSR2 isn't protected, but I don't think it's used by any system library except USB.

- arrays and strings will likely use at least one of the FSR registers, and CONST versions of these will use the TABLEPTR register as well. Simple CONST variables are usually safe (since they typically end up being inserted directly into the code), along with array access that uses a const index (ie 'array(1)') since the compiler will be able to figure out the address instead of having to compute it at runtime.

- floating-point is inherently unsafe (and probably a bad thing to have in an isr anyway)

- any library calls (which may be generated behind your back, so it's worth looking for them).

Of course, accessing any global variable from an ISR can be risky unless you take special precautions to protect it, esp if its type is larger than a single byte. This isn't really context related, but worth mentioning anyway.

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Mon Mar 26, 2012 7:20 pm

Thanks again, Jerry, you are a star on this forum.

Very useful info as to what to look out for - this really should be collated in to a FAQ somewhere as it is what people need to refer to when deciding what to do.

I suspect I will need to do some context saving - once I get the timer 1 oscillator working - as I do use Words and access an array in the ISR. I did have some floats in there but decided to change to setting a flag for the main program to do the math rather than have too much inside the ISR.

The ISR is fairly simple actually - I use Timer 3 running at about 200Hz to multiplex a three digit 7-seg display as well as a timeout counter, and also monitor the CCP module for a capture event and work out the elapsed time between events. I could probably move that out of the ISR as well as you aren't talking more than about 100Hz capture rate so plenty of spare processing power (even at 2MHz!) to deal with it before the next capture.

FWIW, I still feel the compiler could at least pop up a list of registers etc that are used in the ISR rather than you having to wade through the assembler. As I said before, you choose a BASIC compiler so you can program in BASIC, not assembler...

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Tue Mar 27, 2012 8:06 am

OK, I've had a look at the asm and there weren't any references to SB_SVxxx inside the ISR. There were plenty of Mx and Fx references - are these local variables?

I did notice:
MOVFF A1H,FSR0LH
MOVFF A1,FSR0L

Does this mean I need to save FSR0 and is there an easier way than using Save() - that seems wasteful if I don't need to save any system registers.

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Post by Jerry Messina » Tue Mar 27, 2012 10:08 am

Does this mean I need to save FSR0 and is there an easier way than using Save() - that seems wasteful if I don't need to save any system registers
The nice thing about 'save()' is that it can accept a few different arguments. You can use 0 to specify saving the SB_SVxx system registers used internally by the compiler (which also saves FSR0, FSR1, and PROD for you). You can directly specify a specific register to save (like FSR2), or finally you can specify a subroutine name and it'll save the local variables used by that routine. You can only have one save/restore pair, but you can mix and match the above saving as much or as little as you need to, so you could say any of the following

Code: Select all

save(0, FSR2)  			// saves system and FSR2
save(TABLEPTR)			// saves TBLPTRH and TBLPTRL registers
save(0, FSR2, mysub) 	// saves system, FSR2, and mysub()'s locals
save(FSR0, FSR1)		// saves FSR0 and 1 registers
Now, about saving FSR0...
I did notice:
MOVFF A1H,FSR0LH
MOVFF A1,FSR0L
That needs a little explanation. If you look through the ASM file one thing you'll notice is the use of a number of EQU statements that define names for the registers. There are a few in there that you won't see on the pic datasheet, such as A0, A1, and A2. If you look at the EQU value, what you'll find is that these are "alias" names for the FSR0, FSR1, and FSR2 registers!

Using that, we can translate the above to:

MOVFF FSR1H, FSR0H
MOVFF FSR1L, FSR0L

which is copying FSR1 into FSR0. If this is in the ISR, I'd protect both registers using save(FSR0, FSR1) if you don't already need to save the system regs.
There were plenty of Mx and Fx references - are these local variables?
The 'Mx' variables are the module-level static variables, while the 'Fx' ones are (usually) the local frame variables. Those are ones you want to be aware of because they're shared among subroutines, and they would be the ones you're trying to protect if you put the subroutine name into the save list. Static variables are safe from a context point of view, so the only thing you have to worry about screwing up the 'Mx' variables is you!.

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Post by Jerry Messina » Tue Mar 27, 2012 2:18 pm

For reference, a 'save(0)/restore' adds about 140 instruction cycles of overhead each, so that translates to about 28us @ 20MHz before and after your actual ISR code.

If you can handle the overhead and don't want to bother looking into the guts of the ISR, that's the easiest thing to do and will probably work 99% of the time.

Of course, if you're like me all your code will fall into that other 1%...

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Tue Mar 27, 2012 3:22 pm

As this application is battery powered, I'm trying to run the oscillator as slowly as possible... I've chosen 2MHz for now so that overhead would be more like 0.28ms so maybe a bit too high for comfort. I have Save(0) in at the moment so I'll try it out but I have a feeling I may need to optimise it.

That said... A quick calculation suggests I'll be seeing less than 30 capture events per second. I am running Timer 3 at 200Hz to refresh the LED display so that is a 5ms period, therefore 0.28ms is still fairly small compared to that...

I think testing and a bit of evaluation of the rest of the asm code in the ISR is called for to see how much idle time the processor has. I'd like to drop to 1MHz or even less if possible but I think I'd certainly have to reduce the context saving overhead for that.

User avatar
Senacharim
Posts: 139
Joined: Tue Aug 10, 2010 5:19 pm
Location: Ventura, CA

Post by Senacharim » Tue Mar 27, 2012 3:25 pm

A suggestion:
The 'sleep' command.

You can have a high/effective OSC, and configure to unit to wake via ISR/Timer/USART/Input pins/et al/etc/whatever fits your needs.

The PIC spends most of it's time sleeping--low power--and when awoken rapidly addresses the cause of it's awakening before going back to sleep.
Surviving Member
Bermuda Triangle Battalion
from 2026 to 1992

Voted "Most likely to time travel"--Class of 2024.

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Tue Mar 27, 2012 3:40 pm

I was considering something like that but it will have limited effect. Most of the time I need the processor running as it is driving an LED display. The LEDs will draw considerably more power than the processor, granted, but that is no reason not to get the processor power down as far as possible.

I'm having a staged timeout where the display gradually blanks if nothing is detected for a certain time which will help power saving. I'm considering putting the processor in sleep mode as the final stage of this, only waking to blink an LED every few seconds and if a capture event happens.

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Thu Mar 29, 2012 11:10 am

I've been thinking about this a bit more and wonder if someone could advise on the following:

The ISR does access a number of global variables. However, most of these are 'counters' which are only accessed outside the ISR when the program starts to initialise them.

Presumably I don't need to worry about saving those.

The other global variable it accesses will be processed outside the ISR based on a flag the ISR sets. This is the time since last capture and this is used immediately after the main routine detects the ISR has set the flag.

My feeling here is that there will be more than enough time between capture inputs for this calculation to complete without worrying about it being changed by the ISR.

There is a global array which is also processed outside the ISR. This sets the number to display. The ISR only reads this and the display refresh is fast enough that even if the ISR kicks in while it is being updated you will just get a glitch which will correct on the next refresh cycle.

Therefore, while there is a conflict here it is not one that will cause problems so it shouldn't be necessary to save these variables.

As a result, I reckon I can get away with just saving FSR0 and FSR1... Could there be anything I'm missing or anything else I need to consider?

Jerry Messina
Swordfish Developer
Posts: 1473
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Post by Jerry Messina » Thu Mar 29, 2012 1:43 pm

The issue with accessing shared global variables in an ISR and the main program isn't really one of having to save them in the ISR, but what would happen if the ISR runs while your main program is in the middle of accessing them.

Consider this contrived example:

Code: Select all

dim w as word

interrupt isr()

	if (w = $0100) then
		// do stuff
	endif

end interrupt


main:
	w = 0
	
	enable(isr)
	
	while true
		w = w + 1
		// do some other stuff
	end while

The issue comes about because incrementing w in the main while loop isn't done in a single asm instruction, so it can be interrupted.
If you take a look at the asm output, you see something like:

Code: Select all

?I000010_F000_000053_M000000 ; L#MK W = W + 1
        INFSNZ M11_U16,1,0			// increment low byte of w
        INCF M11_U16H,1,0			// increment high byte of w if low byte was 0
Most times, this works. However, whenever the low byte goes from the 255 -> 0 transition (every 256 steps), the high byte needs to be incremented too. If w was $01FF, at the asm level it actually goes through the steps $01FF -> $0100 -> $0200. If you get an interrupt between those two asm instructions, the ISR will incorrectly see w as $0100 instead as $0200, and fail.

The easiest way to prevent this is to disable the interrupt while you're performing this critical step.

Code: Select all

	while true
		disable(isr)
		w = w + 1
		enable(isr)
		// do some other stuff
	end while
Obviously, you don't want to disable the interrupt for any longer than you absolutely have to. There are other ways of doing this, but somehow you need to prevent the ISR from running/accessing variables during some critical sections.

The same type of problem can occur if the ISR modifies something you're using in the main program. If it doesn't matter (like your LCD update), then you can ignore it. If it's controlling the rods in a reactor core then that's another issue entirely.

blackcattech
Posts: 113
Joined: Mon Jan 11, 2010 10:39 pm
Location: Chesterfield

Post by blackcattech » Thu Mar 29, 2012 2:54 pm

Thanks, Jerry, this is bringing back stuff I remember from learning IAR C on the 8051 many moons ago...

I think the simplest thing is to try it and make sure I test it properly. I'm fairly confident there won't be any critical interaction but of course the problem with high-level languages is you don't know exactly what is going on so can't be 100% sure until you have tested.

The reason I'm trying to do this is so I can get the ISR as 'tight' as possible. Testing has shown the LED display is brighter than it needs to be, so to save battery life I want to reduce the 'on' period which in turn means I need to run the ISR more frequently. I still need to have something like 50Hz per digit refresh but now rather than each digit being on 1/3 of the time I'm looking to reduce this to 1/5 or even less. That means rather than calling the ISR 150 times a second it goes up to 250 or more so if I'm executing 280 instructions I don't need to each time it all adds up...

Post Reply