My thinking is that with every application, there are a few fundamentals indicative of all interrupt driven systems. Contrary to popular opinion, assembly programs can be as maintainable as HLL's, just requires a little effort in compartmentalization. I believe this is a good place to start.
These are the points this code addresses;
- Initialize stack (Probably not required on systems using boot loader).
- Determine what cause the system to start.
- Turn off clocks that aren't being used by app.
- Setup pre-scaler on 16 Mhz system to accommodate 1ms triggers on TIMER0
- Initialize watchdog for 16 ms hits.
- Execute main body of application that probably has other initializations.
- Disable watchdog
Included with this example is a function similar to _delay_ms(), but it uses TIMER0 with a limited range of 1 to 255 ms and as implemented only works with 16 Mhz clock.
Code written using Atmel Studio 7, so definitions are in the definition file m328pdef.inc.
NOTE: Section & page references to ATMEL 8161D-AVR-10/09
.cseg ; ============================================================================================ jmp RESET ; Vector for POR or reset jmp No_Handler ; IRQ0 jmp No_Handler ; IRQ1 jmp No_Handler ; PCINT0 jmp No_Handler ; PCINT1 jmp No_Handler ; PCINT2 jmp No_Handler ; WatchDog ; Timer 2 jmp No_Handler ; Compare A jmp No_Handler ; Compare B jmp No_Handler ; Overflow ; Timer 1 jmp No_Handler ; Capture jmp No_Handler ; Compare A jmp No_Handler ; Compare B jmp No_Handler ; Overflow ; Timer 0 jmp No_Handler ; Compare A jmp No_Handler ; Compare B jmp TOVF0_ISR ; Overflow ; Serial jmp No_Handler ; SPI-STC Transfer complete jmp No_Handler ; RX complete jmp No_Handler ; Data register empty jmp No_Handler ; TX complete ; Misc jmp No_Handler ; ADC conversion complete jmp No_Handler ; EPROM ready jmp No_Handler ; Analog comparator jmp No_Handler ; TWI jmp No_Handler ; SPR (Store program ready). ; ============================================================================================ ; Probably not necessary for ISP systems that use OptiBoot or some other boot loader RESET: ldi R25, high (RAMEND) out SPH, R25 ldi R25, low (RAMEND) out SPL, R25 ; OptiBoot trashes this, but its included as a reminder that one may want to respond ; to four reset conditions 10.2 pg 46 in R25, MCUSR ; MCU Control Register 0x35 (0x55) clr R0 ; Should be NULL, but just incase out MCUSR, R0 ; Reset WDRF, BORF, EXTRF & PORF ; ... Switch Case will go here to handle desired reset conditions ; To alieviate unecessary load on MCU, were going to disable clocks not required by app. ldi R25, 0b01001111 ; Leave PRTWI & PRTIM0 enabled sts PRR, R25 ; 9.11.3 pg 45 ; Instead of implementing a delay analogous to delay_ms(), systems operating @ 16 Mhz ; lend themselves ideally to using TIMER0. ; Timer0 is enabled and disable in the routine implemented for delay_ms() equivalent. ldi R25, 3 ; Select clk/64 prescaler out TCCR0B, R25 ; 14.9.2 pg 109. Table 14-9 pg 110 ; Setup watchdog to fire without prescaler (every 16ms) as per example on pg 53. wdr ; Reset counter ; I've chosen this method, as unlike some other MCU's, WDTCSR is not in IO space on the ; 328P. Loading and writing R25 after timed sequence has started only leaves 1 tick, ; which might cause a problem. ldi R24, (1 << WDE) ; Watchdog System Reset Enable pg 55 lds R25, WDTCSR sbr R25, (1 << WDCE) | (1 << WDE); sts WDTCSR, R25 ; Timed sequence, update must be within 4 cycles sts WDTCSR, R24 ; Enable without prescaler ; ---> sei rcall Main ; Execute main body of application cli ; <--- ; As watchdog operates independant of "I" flag in SREG, it needs to be shutdown or else it ; will just continuously keep resetting, or whatever method has been set over and over again ; at "jmp PC". wdr clr R0 ; Will speed things up after timed sequence started. ; Normally all bits in MCUSR wouldnt be trashed, but app is terminating anyway. in R25, MCUSR cbr R25, (1 << WDRF) ; Re-set flag so we dont get inadvertant trip out MCUSR, R25 lds R25, WDTCSR sbr R25, (1 << WDCE) | (1 << WDE) sts WDTCSR, R25 ; Begin timed sequence sts WDTCSR, R0 ; Disable jmp PC ; Spin indefinately, Application terminated ; ============================================================================================= ; Default entry point for unhandled interrupts. No_Handler: reti ; ============================================================================================= ; As this software does not conform to any particular ABI, it is important to note R16 must ; not be altered in anyway, while Delay_Ms is polling register. ; ENTER: R16 = Interation count ; LEAVE: R16 = R16 - 1 ; FLAGS: Preserved ; --------------------------------------------------------------------------------------------- TOVF0_ISR: lds R25, SREG ; Preserve MCU Status register dec R16 ; Decrement Delay_Ms interation count. wdr ; Hit count so watchdog doesnt time out. sts SREG, R25 ; Flags must be restored reti ; ============================================================================================= ; The means by which to wait in 1 ms increments is very common, so Im going to include it ; with this proglogue code. ; ENTER: R25 = Delay in milliseconds in the range of (1 -> 255) ; LEAVE: R25 = Undefined ; R16 = Zero ; FLAGS: Undefined ; --------------------------------------------------------------------------------------------- Delay_Ms: mov R16, R25 ; R16 needs to be untouchable during delay ldi R25, (1 << TOIE0) ; 14.9.6 pg 111 Enable TIMER0 interrupt sts TIMSK0, R25 wdr ; Be sure we get a full count at first interation. ; Wait for hit from TIMER0, but other interrupts could wake us up, so R16 needs to be ; tested to see if interation count is exhausted. sleep ; Wait for TIMER0, but could be anything else too. tst R16 ; Interation count zero yet? brne PC-2 ; Keep spinning till zero sts TIMSK0, R16 ; Disable TIMER0 interrupts (TOIE0) ret ;*==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* *==* Main: ret