Reclaiming the Interrupt Vector Table!
If we look at the very top of our program output, we see a huge table taking up 42 bytes of prime real-estate memory…
00000000 <__vectors>: 0: 14 c0 rjmp .+40 ; 0x2a <__ctors_end> 2: 1b c0 rjmp .+54 ; 0x3a <__bad_interrupt> 4: 1a c0 rjmp .+52 ; 0x3a <__bad_interrupt> 6: 19 c0 rjmp .+50 ; 0x3a <__bad_interrupt> 8: 18 c0 rjmp .+48 ; 0x3a <__bad_interrupt> a: 17 c0 rjmp .+46 ; 0x3a <__bad_interrupt> c: 16 c0 rjmp .+44 ; 0x3a <__bad_interrupt> e: 15 c0 rjmp .+42 ; 0x3a <__bad_interrupt> 10: 14 c0 rjmp .+40 ; 0x3a <__bad_interrupt> 12: 13 c0 rjmp .+38 ; 0x3a <__bad_interrupt> 14: 12 c0 rjmp .+36 ; 0x3a <__bad_interrupt> 16: 11 c0 rjmp .+34 ; 0x3a <__bad_interrupt> 18: 10 c0 rjmp .+32 ; 0x3a <__bad_interrupt> 1a: 0f c0 rjmp .+30 ; 0x3a <__bad_interrupt> 1c: 0e c0 rjmp .+28 ; 0x3a <__bad_interrupt> 1e: 0d c0 rjmp .+26 ; 0x3a <__bad_interrupt> 20: 0c c0 rjmp .+24 ; 0x3a <__bad_interrupt> 22: 0b c0 rjmp .+22 ; 0x3a <__bad_interrupt> 24: 0a c0 rjmp .+20 ; 0x3a <__bad_interrupt> 26: 09 c0 rjmp .+18 ; 0x3a <__bad_interrupt> 28: 08 c0 rjmp .+16 ; 0x3a <__bad_interrupt>
This is the interrupt vector table. It tells the processor where to go when asynchronous events happy – stuff like getting reset or having a timer expire.
Each slot corresponds to a single type of event. The top slot is where the chip goes when it gets reset. The 2nd one happens when there is an External Interrupt #1 requested. The 10th one is for when the serial port is finished sending the last byte. All things that happen spontaneously when other code might be running.
When, say, the Universal Serial INterface overflows, the chip will load the program counter with the address of vector #17 (address 0x10) and then start executing. Typically this will be a “RJMP” instruction telling the chip where to goto to find the actual program code that needs to get executed.
In our case, we don’t use any interrupts or timers or counters or anything else fancy, so all of the vectors except for reset just point _bad_interrupt, which itself just points back the the reset vector. So why do we need all of these extra vectors if the chip is never going to jump to any of them? We don’t!
So we are left with just Vector #0, the RESET vector. RESET is basically like reboot for a normal computer and gets used anytime the chip powers up or the reset pin is toggled or stuff like that. Right now the reset vector points to __c_tors_end which is the beginning of the startup C code that we already figured out that we don’t need anyway. So, we could just point this vector directly to the beginning of our code and be done… but that vector would be using up 2 bytes just to tell the chip to jump 2 bytes forward. We can not condone this sort of thing.
Can we somehow get rid of the reset vector itself? It turns out that since the chip just does a simple jump to vector #0 on reset, the RJMP in that vector does not actually need to be an RJMP. It can be any instruction we want. It can even be the first instruction of our program! The trade off is that our code will now be overwriting the vectors that would be at location #2 and #4, but we know that these vectors will never happen so that is ok.
If we start our program at location zero, it will automatically start running when the chip gets reset with not a single byte of vector table in sight!
Ok, so now we’ve figured out that we can, in theory, get rid of each and every extra byte surrounding our code… but how do we actually make this happen in practice?
Tune in next week to find out how!