2
\$\begingroup\$

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;

  1. Initialize stack (Probably not required on systems using boot loader).
  2. Determine what cause the system to start.
  3. Turn off clocks that aren't being used by app.
  4. Setup pre-scaler on 16 Mhz system to accommodate 1ms triggers on TIMER0
  5. Initialize watchdog for 16 ms hits.
  6. Execute main body of application that probably has other initializations.
  7. 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
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.