The canonical way to set the interrupt bit on AVR is the
sei (enable interrupt) instruction, but there are many creative and devious ways to set a bit.
Before you click more, see if you can think of every possible way to set the
I bit in
SREG, then read on to see if I missed any!
From the AVR datasheets…
The instruction following SEI will be executed before any pending interrupts.
sei instruction does is set the interrupt enable bit in the status register. As far as I can tell, there is no mention of this delayed interrupt handling for any other method of setting the
I bit. Whenever the documentation is ambiguous, we are left to experiment to find the answer!
We will need to test every case, but what are the cases?
So Many Ways to Set a Bit
Here are the four cases I could think up (with a little help from my friends)…
SEI (base case)
Super simple. By the book.
Obvious. Even your grandma got this one. No points awarded.
in r16,0x3f // Get SREG ori r16,128 // Set I bit out 0x3f,r16 // Save back to SREG
Did you know that you can map IO registers to data space by adding 0x20 to the address? You get a point!
lds r16, 0x5f // Get SREG ori r16,128 // Set I bit sts 0x5f, r16 // Put SREG in though the back door
(hat tip to Nerd Ralph for this one!)
Did you remember indirect addressing? Give yourself another point!
clr r29 // Clear Y pointer high byte ldi r28,0x5f // Set Y low byte to point to SREG (0x3f+0x20) ld r16, Y // Get SREG ori r16,128 // Set I bit st Y,r16 // Put SREG in the Doug Henning way
Did you remember that the
I bit is always set upon return from an interrupt? From datasheet section 7.7…
The I-bit is automatically set when a Return from Interrupt instruction – RETI – is
So here is possibly the slowest and most indirect way to execute an
sei. If you get paid by the KLOC, feel free to use this in all your code!
ldi r28,low(retitarget) // push the high and low bytes of the target address push r28 ldi r28,high(retitarget) // on stack so return will end up there push r28 reti // jump off... retitarget: // ...and land here! (and implicitly set I bit)
(Hat tip to Zevv for this one!)
For all of the above cases, it seams that the processor does always allow the instruction following the setting of the
I to run, even if there is a pending interrupt. Not a surprising result, but good to know for sure!
Here are some cases (some suggested by others) that turn out to not be alternate ways to flip the bit. Some even downright crash & burn!
STD is really just
ST in disguise. Let’s look at the op-codes..
10q0 qq1r rrrr 1qqq std (r=register, q=offset)
1001 001r rrrr 1100 st (r=register)
See the resemblance?
st is just
std with a 0 offset.
It turns out that
bset! They are one and the same! Take a look at the op-codes…
1001 0100 0sss 1000 bset (s=bit in SREG)
1001 0100 0111 1000 sei
The interrupt enable
I bit is bit 7 (111 in binary) in
sei is just an easier to read and remember way to say “
This one is a weaselly hack. We set the stack pointer to the memory mapped address of the
SREG register, and then we
push the value we want to end up in
SREG onto the stack.
If you thought of this one then you are an AVR zen master (and a sneaky bastard).
clr r29 // ZERO out 0x3e, r29 // Stack high ldi r28,0x5f // pointer to memory mapped SREG out 0x3d, r28 // Stack now points to SREG! in r16, 0x3f // Get SREG value ori r16,128 // Set I bit push r16 // Store back to SREG. What? Yep, that just happened.
Unfortunately, it just doesn’t work. The
I bit never gets set. As far as I can tell the pushed byte does not get pushed anywhere. Not into the SREG register, not into memory, nowhere. It just disappears forever in apparent violation of the laws of god and science (ok, not really).
I even tried pointing the stack at the General Purpose I/O Register register (GPIOR1). This register is in the same IO space as
SREG, but is completely free and unencumbered- avoiding any complications that might be related to trying to update
SREG. Even pushing to
GPIOR1 seems to have no effect at all other than updating the stack pointer.
What is going on here? My guess is that the microcode for stack operations does not go though the memory mapping translator path, so everything melts. This is fair, since no one should ever really be trying to do what we are trying to do, so why add extra hardware just to make it work correctly?
So here is the (psychotic) thinking behind this hot mess of a bit flip…
CALL instruction works like a
PUSH except that instead of pushing the contents of a register onto the stack, it pushes a return address onto the stack.
So… if we can contrive our code so that the address of the
CALL in program memory is such that the return address has a bit set in the right place, when the
CALL is executed then it will push the return address with the set bit into
I know, I know – it is just so crazy that it might work.
Here is the crazy-ass code…
clr r28 // ZERO out 0x3e, r28 // Stack high ldi r28,0x5f // pointer to memory mapped SREG (so when we CALL, the low byte of the PC will end up in 0x5f) out 0x3d, r28 // Stack now points to SREG! jmp addresswithIbit .org 0b10000000-3 // This nasty org is really an address designed to have the top bit set // so when it gets pushed to the stack (which will point to SREG), then // the I bit in SREG will get set. Get it? addresswithIbit: call callTarget // push PC (which has I bit set) onto stack (which points to SREG). rjmp loop // Lock up, but we really should never end up here calltarget: // When we get here, the address with the I bit set has just been pushed into SREG, enabling interrupts sbi PORTB,5 // LED on loop: jmp loop // Infinate loop
To my horror and relief, this does not work at all. The processor does jump to the call targte and
SP is decremented, but the return address is lost forever. When the
ret is executed, we fly off into la-la land of non-existing flash address. Don’t try this at home.
This almost certainly doesn’t work for the same reason that
push doesn’t work – the hardware apparently does not support dereferencing a stack pointer into memory mapped IO register space.
According to the datasheet entry on XCH…
Memory access is limited
to the current data segment of 64KB.
…which seems to imply that it will not work on memory mapped io memory. But there is only one way to find out- test on actual hardware.
But wait! I can not find a single chip where this instruction is actually implemented in the silicon! (and I have a big collection of AVR chips!) It is definitely not supported on any of the normal chips like the ATTINY’s or ATMEGA’s. Looks like it is probably on some XMEGA’s, but not all versions and it varies from rev to rev.
I can’t even find any Atmel documentation listing which chips support these instructions. Can you?
If you can find a chip that can do it, please test and let me know what happens!
(Same goes for
DebugWire for extra credit
(Again, suggested by Nerd Ralph)
Ignore this part unless you are a pathological AVR trivia buff since it can never ever be of any practical use. (or could it?)
The idea here is to blow the DebugWire fuse on our AVR, and then use the DebugWire connection to discretely reach directly into it’s mind and set the
I without any using any code on the chip whatsoever. No instructions are executed in this setting of the
Will the AVR still dutifully put off a pending interrupt and execute the next instruction even if the
I is set when no one is looking? Heck, will it even process the interrupt if the
I is set without an instruction taking place?
This was a hard test to do and involved breaking out a Dragon board and wiring it up to our poor little chip.
After lots of A/B testing and single stepping, I am 90% sure that the instruction that is pointed to by PC when you manually set the
I bit over DebugWire is, in fact, always executed even if there is a pending interrupt.
I also tested to see if the current instruction even sees the
I bit as being set after it is changed via DebugWire. I could imagine some buffer somewhere that holds the new value and does notice the change until the clock ticks. To do this, I stopped the processor right before an instruction that read SREG into a normal register, then I later checked that register and it did, indeed, have the
A: This issue actually came up on a project long ago where I wanted to save a cycle by enabling interrupts and clearing a flag at the same time. I only tested the case I needed and it worked, so I forgot about it. When I saw the same question pop up on Stack Exchange today, I figured it was a good excuse to finally answer the question conclusively!
Q: What happens if I think of a case that you missed?
A: Then you win an all expense paid lunch at any restaurant you want in NYC! (transportation and lodging not included)
Q: Ha! You idiot! You forgot
A: Not so fast, the
SBI instruction only works on IO registers 0-31 and
SREG is all the way up at slot 63. Nice try, though!