[TUT][SOFT] The traps when using interrupts

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

Intro
Interrupts seem to have some confusion and ignorance associated with them. I hope to remove some of this and replace it with understanding so one can use interrupts with confidence.

What is an interrupt?
Interrupts were devised as a means of signalling the processor that a hardware device wanted attention. Without interrupts, the software would have to regularly ask the device(s) if they were ready and then service them if they were. Between this 'polling', you could do some real work. The problem was some devices need fast attention, so if you didn't poll the device fast enough, data would be lost. Enter the 'interrupt'. An interrupt is effectively a subroutine call activated by a hardware signal. The processor at its simplest fetches an instruction then executes it. Repeat ad-infinitum. With interrupts, the processor checks to see if the interrupt signal is active. If it is active, it will execute then means needed to call the interrupt service routine (ISR). Otherwise, the processor will test for interrupt, fetch the instruction, execute it, repeat.....

AVR Interrupts
With the AVR, we have many sources of interrupts - timer, external, uart etc. Each source has a 'vector' associated with it. This is simply the address of the ISR to be called when the requisite interrupt is activated. We also have enable flags and interrupt active flags for each of the interrupt sources. This means we can enable/disable each interrupt source as we see fit. The AVR also has a global interrupt enable flag that is set/reset with the SEI (enable interrupts) and CLI (disable interrupts) instructions.

Multiple Interrupt at one time
Since we have multiple interrupt sources that we can enable, what happens if a number of them are active at one time? Priority. The AVR has a fixed priority scheme to sort out what source it will service. Lower priority sources will have to wait their turn. Many of the devices have an interrupt active flag associated with them. In the case of the timers, each source of interrupt has a flag that gets set when a timer event occurs. This flag stays set until the AVR actually calls the ISR for that interrupt source. so with this, an event won't get missed but the response may be delayed. This is a good reason to make all your ISRs lean and mean. Get the job done and get out - no sitting in loops or wasting time.

In a nutshell, the AVR will scan for all active interrupt sources, the highest priority wins, disable all further interrupts by clearing the I bit in the status register then it will get the vector (address of the ISR) for that source then call that ISR. Simple. When you do a RETI instruction (in 'c' the compiler does this for you at the end of an ISR), the I bit is set so other interrupts can be serviced.

The down side of interrupts

So we've had an interrupt and the AVR has called our ISR, what next. Because we've interrupted other code and the ISR may change registers etc, we need to save the SREG (status register) and any other registers we modify. This is called 'preserving the processor state'. Once we've done this, we can alter the registers as we please. Once our ISR has completed, we need to restore the saved registers and do a RETI (return from interrupt instruction). In 'C' the compiler does this for us.

So far so good.

For most programs, we need to share variables between the ISR and the rest of our code. This is where problems can arise. We need to share our variables carefully, otherwise an ISR might occur at any time and change something when we're not looking.

Atomicity
Certain operations need to be done without interruption - 'atomically' which means 'as one' or 'indivisable'. This needs to happen when:

1. Reading/writing variables that are larger than one byte. The AVR being an 8 bit cpu, reads/writes in 8bit chunks. so to read/write a int variable (in C), we need to r/w in two chunks, the low byte and the high byte. This takes a few processor instructions to achieve this. What happens if an interrupt occurs between these two operations and the ISR modifies the variable we're accessing? We get half the unmodified variable and the other half modified. Of course this occurs randomly and most likely very infrequently as both interrupt and the variable access have to happen at the right time. This causes the worst kinds of bugs - so very hard to track down. So how do we get around this problem? Disable interrupts, if reading a shared variable copy the shared variable into another variable, if writing, perform the write, then re-enable interrupts. If any interrupts occur whilst we have them disabled will be processed after we reenable them. In 'c' the code might look like this:

volatile unsigned int shared_var;

in main code:
cli();
copy_of_shared_var = shared_var;
sei();
// use copy of shared var

Note that we have declared the variable 'volatile'. With optimising compilers, they try to keep used variables inside the AVR registers for performance. The problem is if an interrupt comes along and modifies the variable that is stored in ram, next time the main code reads the variable (that the compiler has stashed in the registers), we're not reading the current value of that variable. Declaring a variable 'volatile' tells the compiler not to stash a copy away in the registers but to read/write the variable to/from memory each time it is accessed.

Note that the above applies also to arrays and collections of data that you expect to be cohesive.

2. When testing and modifying a variable. A common operation that needs to be protected is a 'test and set'. If the value is 0, we set the variable to a non-zero value. Here we have two operations that might get interrupted and cause and unwanted condition. Again, we must temporarily disable interrupts, do the test and set and then re-enable interrupts.

So, we need to remember that interrupts can happen at any time and ISRs can modify shared variables whilst the main code is performing operations on them. These are called 'critical sections'. At the assembler level, it is obvious to the programmer that there are a number of instructions involved in a particular operation, whereas in'c' and other higher level languages it is not so obvious that one line of code may generate a number of assembler instructions.

The basic rules are:
1/ Understand the pitfalls of shared variables
2/ minimise the use of shared variables
3/ Implement 'critical sections' where necessary

This article highlights what can go wrong when ignoring the above:
http://courses.cs.vt.edu/~cs3604...

Pre-emptive RTOS
The term RTOS seems to have some magic associated with it. 'Real time Operating System' is what it stands for. so what is 'real time'? Historically it means that the operating system will activate a task within a given time. The actual time varies, but is usually taken to be 'as fast as possible' - so a faster cpu will respond faster in most instances. The 'operating system' in many cases (especially with AVR) is simply a task switcher not something like Windows(c).

Task Switching
There are two major type of task switching:
1/ co-operative
2/ Pre-emptive

Co-operative is the simplest - each of your tasks must perform its duty then return. Then the next task runs etc. As such there is only one 'thread' of execution.
Each task must 'co-operate' otherwise other tasks won't get a chance of running.

Pre-emptive is a little more complex. Pre-emptive means that a running task may be interrupted and suspended at any point in its execution. This is done with interrupts. The operating system keeps a stack for each of the tasks and when a task swap is needed, it swaps to the required stack and restores the previous processor state. The main downside of this is due to the number of stacks, this consumes an amount of memory which may be in short supply in a small system like the AVR.

Usually a timer is set up to give a regular interrupt that calls the operating system scheduler. The scheduler code determines what task should run next. Interrupts from other sources may call the scheduler. The design of the scheduler determines if the system is 'real time' or not. Windows and Linux are examples of pre-emptive operating systems but they are not real-time as certain tasks may continue to run until they relinguish control back to the scheduler.

There may be some conjecture as to my description of real time as the term has been misused so much that its meaning has been lost in the mire. I base my description on the historical use of the term.

With a pre-emptive operating system real time or not we again have the problem of sharing variables. Many operating systems give you system calls to take care of this - flags, queues etc. Use there where possible as the operating system takes care of any sharing issues (well at least it should!). Sometimes we still need to share variables between tasks so the issue with atomicty is the same - use critical sections to ensure your operations are atomic. With a co-operative system, variable sharing isn't a problem (except with ISRs) as each task runs to completion.

Old tricks
There's usually a question that pops up on the forums a lot on how to create a 'software interrupt'. Some processors have this facility built it but the AVRs don't. There's a variety of ways of making this happen - set up a port pin for external interrupts and toggle the port pin in software is a popular one. What the person is wanting though is a task switcher. In this instance, the person should evaluate the design of the code to eliminate this requirement or go to a pre-emptive o/s like freertos to provide a more general solution to the problem. Personally, I avoid using a pre-emptive task switcher on AVR class projects as most applications don't need it. Clever use of a timer interrupt and careful design can yield a solution using a co-operative method. Using a co-operative method avoids a lot of potential pitfalls associated with pre-emptive strategies. Call it 'problem avoidance'. By avoiding methods that might introduce tricky problems in debugging means you have a better chance of writing defect free code - which should be a 'good thing'.

Switches and Interrupts
It seems like a perfect marriage, switches activating a pin change or external interrupt - but there are dangers lurking. These are:

1/ Switch bounce - mechanical switches 'bounce' as in giving multiple on/off actions. This usually happens in the range of 5 - 50mS. So hooked up to an interrupt, each press of the switch can give you a random amount of interrupts for each press.

2/ Picking up EMI. Say we had a switch connected by 10M of wire to our AVR with an interrupt. Without protection the wire may pick up unwanted radiation from a mobile phone or other unit. This would give us a burst of interrupts in fast succession - so fast that our poor AVR might be hard pressed to keep up. The net result is our application on the AVR doesn't work as we would like. Even without a long length of wire, we still have the potential to pick up unwanted interference.

Put simply ,we have little control of the interrupts which may introduce some unreliability into our unit. So how do we avoid this?

Use a timer interrupt to read the switch input(s) and apply a debounce algorithm. Even if the input is not a mechanical switch, it always pays to apply some form of filtering to remove transient spikes etc from affecting the rest of our code.

If you want the switch to power up the AVR, use an interrupt here, but disable it once you've powered up then use a timer interrupt to read the switch.

Where possible, minimise or eliminate the use of external interrupts. I'm not saying 'never do it', but if you have to do it, be aware of the potential problems and take steps to minimise them.

If you've got this far hopefully you've been able to understand my waffle. If something is not too clear or you need clarification, drop a message and I'm try to correct/explain it.

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

Nice article, thanks!

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

Very nice explanation, I've added a link on the Wiki to this post.

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

Thanks, cleared up a lot for me, and gave me some good ideas

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

Kartman,
Very nice tutorial - thanks for putting it together.

I took the freedom of creating a PDF of your tutorial (attached)

Nagi

Attachment(s): 

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

i am using two interrupts sources and i have connected them to the int0 and int1 (any logical change should trigger int).

Almost every time (not every time), there is a chance of these interupts being triggered at the same time

so could i avoid the second interrupt by clearing the flag of the second interrupt at the last statement of isr of the first interrupt?

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

thankyou

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

I am using USART_RXC interrupt, when when an '1' arrives to the UDR it's saves in a register and then return to the cycle loop where It's compared where if its 0, sending zeros to the port c, if it's 1, go will to the label start and starts the adc, when the conversion ends interrupt and go to ISR end_conversion, and returns to where the flag ADIF is polls so if the UDR comes a new date of information it receives in the ISR of RXC is executed but fails to return to the cycle of comparison but again the cycle of poll flag ADIF, Which I can do to solve this?

This is a my code

.cseg
.include "m8535def.inc"

.EQU fq=4000000 ; Xtal frequency
.EQU baud=9600 ; Baudrate
.EQU bdpresc=(fq/(16*baud))-1 ; Baud-Divider

.DEF c=R30 ; char.

;VECTOR DE INTERRUPCIONES****************************************************

.ORG 0x0000
rjmp RESET
rjmp EXT_INT0
rjmp EXT_INT1
rjmp TIM2_COMP
rjmp TIM2_OVF
rjmp TIM1_CAPT
rjmp TIM1_COMPA
rjmp TIM1_COMPB
rjmp TIM1_OVF
rjmp TIM0_OVF
rjmp SPI_STC
rjmp UART_RXC
rjmp UART_UDRE
rjmp UART_TXC
rjmp fin_de_conv; interrupt end of conversion
rjmp EE_RDY
rjmp ANA_COMP

EXT_INT0: reti
EXT_INT1: reti
TIM2_COMP: reti
TIM2_OVF: reti
TIM1_CAPT: reti
TIM1_COMPA: reti
TIM1_COMPB: reti
TIM1_OVF: reti
TIM0_OVF: reti
SPI_STC: reti
UART_UDRE: reti
UART_TXC: reti
EE_RDY : reti
ANA_COMP: reti

UART_RXC:

in c, udr

OUT UDR, c

reti

fin_de_conv:

cbi adcsra, adie

in r18, adch

out portc, r18

reti

Reset:

ldi r26, low (RAMEND)
out spl, r26
ldi r26, high (RAMEND)
out sph, r26

ldi r26, 0xff ; port c outputs
Out ddrc, r26

ldi r26, 0x00 ; Port a inputs
out ddra, r26

;Configuración USART *****************************************************

ldi r20, HIGH (bdpresc)
out UBRRH, r20
ldi r21, LOW (bdpresc)
out UBRRL, R21

ldi r16, 0b10011000 ; RXCIE, RX y Tx
out UCSRB, r16

ldi r16, 0b10000110 ;
out UCSRC, r16

;Configuración ADC *******************************************************

ldi r26, 0b01111100 ; Aref = vcc, adlar=1, adc4+, adc2-
out admux, r26

ldi r26, 0b10100101 ; prescaler = 32
out adcsra, r26

sei

loop:

Ldi r22, '1'
cpse c, r22
rjmp stop
rjmp start

stop:
cbi adcsra, adie
clr r17
out portc, r17
rjmp loop

start:

sbi adcsra, adsc

sbi adcsra, adie
fincon:

sbis adcsra, adif
rjmp fincon

rjmp loop

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

Stefanny: Wrong place to post. Please repost your question to the main AVR forum (since your question is about interrupts and the "language" is assembly, that seems to be the proper forum).

Also, please read the STICKY posting at the top of the list of threads about each forum's purpose. Posting to the wrong forum not only does not get your question answered, but also p*sses off folks who might answer.

Stu

Engineering seems to boil down to: Cheap. Fast. Good. Choose two. Sometimes choose only one.

Newbie? Be sure to read the thread Newbie? Start here!

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

Thanks a lot Kartman .. clear and nice tutorial

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

Great tutorial Kartman. Quick question:

Under Atomicity, you mention interrupts should be disabled then re-enabled after modifying shared variables. Later it says to use volatile variables so it reads/writes the variable to/from memory each time it is accessed. And further it says to minimize the use of shared variables.

Should we only disable/re-enable interrupts only when using shared/volatile variables? Or should it be done for all variables? (Does declaring volatile = "shared variable" in this context?)

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

Well it's true that a variable used in both an ISR and the mainline must be 'volatile' because it is shared but the access of it in main() only needs to also be deliberately made "atomic" if it's wider than 8 bits and cannot be atomically accessed with a single LDS opcode. This is because an interrupt may occur between the main code loading the low and high halves of a 16bit (for example) and that value itself might be updated in the interrupt. So the low half that was collected before the interrupt is now "stale". If it had been a counter just about to transition from 0x00FF to 0x0100 then the low value might be 0xFF and the high 0x01 so it appears to have been 0x01FF when it wasn't.

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

Just to sum up,
variables shared between an ISR and the main line of code must be declared volatile - WinAVR (gcc) will give you weird results if you don't. I got bitten the other night when porting some code!

Then we have the issue of atomicity of which one example of is described above by Cliff.

Say we have:

volatile char flag;
ISR(timer1_ovf)
{
flag++ ;
}

and the main code:
if (flag >10)
{
...
}
doesn't need any special consideration as it will be one byte read - it is atomic by default.

but if we do this:

flag |= 0x10;
we have a potential problem as the compiler will generate a read from the memory location, the 'or' operation then write the value back to memory. In the middle of all this an interrupt could occur with similar results to what Cliff describes.
so you would have to do this:

cli();
flags|=0x10;
sei();
thus ensuring no interrupt can butt in at the wrong time.

The reason behind minimising the use of interrupts and shared variables is that there is less opportunity for coding errors.

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

Quote:
so you would have to do this:

cli();
flags|=0x10;
sei();


But if interrupts were already disabled when this code was used it has the nasty side effect of then enabling interrupts. Far better is:

unsigned char current_SREG = SREG;
cli(); 
flags|=0x10; 
SREG = current_SREG;

which protects the access but also puts I back to its entry state.

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

so to use global variables that need to be shared between an ISR and main(), or some other routine external to the ISR, we declare our variables globally as volatile in the ISR file. How do we access these variables in our main() routine then? ie how and where do we declare them?

Persistence is bitter but its fruit is sweet

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

Quote:
How do we access these variables in our main() routine then? ie how and where do we declare them?

If the variables, ISRs and main() code are all in the same file then just use them "normally" as you would with any global variable. If you are splitting the ISRs and main() code into seperate files then pick one of the .c files and put the variables there:

volatile int fred;

then in the other .c file use:

extern volatile int fred;

just as you would with any shared global. In fact, like any extern declaration, you may choose to put it into a shared .h file. (You'd definitely want to do this if the variable was to be accessed in more than the two .c files)

Cliff

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

Just to put this back into the current page......

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

sir
i am getting the problem of bouncing switch. can u please tell how to use timer to read external interrupt and what is debounce algorithm?
i am doing project using atmega128.i have to submit it soon. So
please please please please please please ................. help !!!!!!!!!!!!!!!!!!!

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

thanx for the excellent article sir. i specially enjoyed reading the task switching and the switches and interrupts part. :)

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

Haven't read it completely but i really liked the way you explained an Interrupt, very simple thus easy to understand. Thanks Kartman!

Mark

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

Great Article.

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

thanks

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

Excellent. Been outta programming for a bit and had forgotten a few things, this allowed me to fix my current interrupt problem. Thanks Kartman. :)
(lol why is it when i hit the "smiles" button it says "Hacking attempt1" in an empty pop-up box? Using firefox)

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

Ender366 wrote:
Excellent. Been outta programming for a bit and had forgotten a few things, this allowed me to fix my current interrupt problem. Thanks Kartman. :)
(lol why is it when i hit the "smiles" button it says "Hacking attempt1" in an empty pop-up box? Using firefox)
Because the site was hacked ... a long time ago. The powers to fix it are focussed on other fish ...

Cheers,

Ross

Ross McKenzie ValuSoft Melbourne Australia

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

Just bumping this up.

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

Wonderful explanation. I have 3 questions.

1.  

Many of the devices have an interrupt active flag associated with them.

1. What if interrupt A source is not associated with interrupt flag (I am not sure which source one is not associated in AVR) and interrupt A condition is set when IBit is disabled inside another ISR of interrupt B? We may lose the interrupt even if it is of higher priority?

 

2. When does ISR clear the interrupt flag? At the beginning or at the end? If it is at the beginning and if multiple interrupt of same type occur, interrupt flag may be set again before completing ISR right? 

 

3. How to implement switch debounce using timers for external interrupt or PCINT?  Basically we need to turn the timer interrupt inside ext int ISR and come out of ISR and then wait for timer interrupt right?

 

 

 

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

1. AVRs do not have interrupt priority- so a higher priority cannot interrupt a lower priority as there is no priority. There is a fixed resolution priority - if multiple flags are set, then there is a fixed priority as to who gets serviced first.  The idea of having an interrupt flag is that is latches the interrupt request. When interrupts are enabled again, then the next isr to execute will be resolved from the currently active flags.

2. The datasheet explains this. Depending on the peripheral is the method it follows. Some flags are not reset by the isr. For the ones that are, the datasheet tells me the flag is reset on entry to the ISR. Yes, the interrupt flag may be set again. Sometimes you may wish to explicitly reset the flag before leaving the ISR.

3. Your method is one way to do this. You really have to ask yourself if using external interrupts is a good choice for switches. Sure, if you need to wake up the processor from sleep, otherwise why would you need microsecond response on a mechanical device that works in the realm of 10's of milliseconds.

 

If you have level rather than edge set for the external interrupt, the interrupt flag follows the input pin - it is not latched.

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

Thank you for the good info. Lot of things are getting cleared from this post. Datasheet in some cases  is not very clear. For Ex: datasheet says "The flag is cleared when the interrupt routine is executed". But does not say whether at the beginning or at the end.

Also according to datasheet with respect to (INTF1/INTF0),  "This flag is always cleared when INT1 is configured as a level interrupt". I initially got confused with this and I was thinking how interrupt is generated then. After reading your post, again I dug into datasheet and found another description which reads "When the external interrupt is enabled and is configured as level triggered, the interrupt will trigger as long as the pin is held low." and make sense.

Anyway I will not be using ext interrupts for switches. Just wanted to understand the concept.

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

joneewheelock wrote:
But does not say whether at the beginning or at the end.
for interrupt flags that are cleared when an interrupt handler is called it is "on the way in" that this happens.