I generally drive a status LED with various blink patterns, depending on the status of the system. Each blink pattern lasts 1 second.
To define the patterns, I break the 1 second interval into time slices. A mask of bits somewhere then defines whether the LED is supposed to be on or off for each time slice of each pattern.
This is not a critical task worthy of using interrupts for. My standard status LED module contains a routine that can be called periodically. It looks at the current 1 ms tick counter, and handles all timing accordingly. As long as this routine is called often relative to the blink time slices, everything works seamlessly. I usually call the routine from the main event loop when no other events were processed. In other words, this is about the lowest priority thing the processor does. That is completely opposite of using interrupts.
I do this often enough that I've got canned routines for this. On Microchip dsPIC processors, I use 48 slices per blink pattern. That's because program memory words are 24 bits wide, so 48 bits comes from using two words to define each blink pattern. That means each slice is about 21 ms long. The canned code uses whole ms ticks per time slice, but automatically varies between 20 and 21 ms to keep the whole pattern at 1 s in the long run.
Here is the template code for the STAT module on dsPIC processors. You copy and rename this module into your project. The QQn sequences get replaced with project-specific strings.
; ***************************************************************
; * Copyright (C) 2005, Embed Inc (http://www.embedinc.com) *
; * *
; * Permission to copy this file is granted as long as this *
; * copyright notice is included in its entirety at the *
; * beginning of the file, whether the file is copied in whole *
; * or in part and regardless of whether other information is *
; * added to the copy. *
; * *
; * The contents of this file may be used in any way, *
; * commercial or otherwise. This file is provided "as is", *
; * and Embed Inc makes no claims of suitability for a *
; * particular purpose nor assumes any liability resulting from *
; * its use. *
; ***************************************************************
;
; This module manages the status indicator LED.
;
; The status indicator LED is blinked with a pattern dependent on system
; status. This module determines the blink pattern from the system status,
; and performs the mechanics of blinking the LED accordingly.
;
; Exported routines:
;
; STAT_INIT
;
; Must be first call into this module.
;
; STAT_UPDATE
;
; Must be called periodically. It determines the system state, decides
; what pattern to display, tracks real time, and updates the LED
; accordingly. It is intended to be called from the main event loop as
; a low priority event.
;
; Configuration parameters:
;
; NAME
;
; Name of the /OUTBIT pin that controls the LED. Setting this line to ON
; is assumed to enable the LED, and OFF to disable it. The LED can be
; wired either way, as long as the polarity in the /OUTBIT command is set
; accordingly. The default is "ledstat".
;
; NSTBITS
;
; Number of bits in a pattern. The default is 48.
;
/include "qq2.ins.dspic"
;*******************************************************************************
;
; Configuration constants.
;
/const name string = "ledstat" ;name of output pin controlling the LED
/const nstbits integer = 48 ;number of slices in one-second display pattern
////////////////////////////////////////////////////////////////////////////////
//
// Macro PATTNUM
//
// Determine the number of the pattern to display. This macro leaves the 0-N
// pattern number in W0. W1-W3 may be trashed.
//
/macro pattnum
mov #0, w0
havepatt: ;W0 contains the 0-N number of the pattern to display
/endmac
//
////////////////////////////////////////////////////////////////////////////////
/include "(cog)src/dspic/stat.ins.dspic"
;*******************************************************************************
;
; Display patterns table.
;
; Each display pattern must be defined here. Patterns are defined
; sequentially starting with 0 at TBL_PATT.
;
; Each pattern is defined with the PATTERN macro. The parameter to this macro
; is a series of stars (*) or dashes (-). A star represents LED on, and a
; dash LED off. There must be exactly NSTBITS total characters in the
; pattern. Patterns are displayed in left to righ order as defined by the
; PATTERN macro parameter.
;
tbl_patt:
pattern ************************------------------------ ;0 - normal operation
.end
This is the only code that requires customization. Generally you only need to define the name of the output line that controls the LED, fill in the code that decides what pattern to display depending on system status, and the actual patterns.
Note heavy use of my PIC assembler preprocessor. For example, the PATTERN macro used at the bottom makes defining patterns very easy. The macro generates the actual .PWORD directives to define the pattern bits in program memory. You could do that directly, but the PATTERN macro provides a much more intuitive interface to defining blink patterns.
For completeness, here is the code in the include file. This is treated as a canned file and not modified per application:
; ***************************************************************
; * Copyright (C) 2005, Embed Inc (http://www.embedinc.com) *
; * *
; * Permission to copy this file is granted as long as this *
; * copyright notice is included in its entirety at the *
; * beginning of the file, whether the file is copied in whole *
; * or in part and regardless of whether other information is *
; * added to the copy. *
; * *
; * The contents of this file may be used in any way, *
; * commercial or otherwise. This file is provided "as is", *
; * and Embed Inc makes no claims of suitability for a *
; * particular purpose nor assumes any liability resulting from *
; * its use. *
; ***************************************************************
;
; Canned code for the STAT module. See the header comments in the
; QQQ_STAT.DSPIC file for a description of the possible configuration
; parameters.
;
;*******************************************************************************
;
; Configuration constants.
;
/if [not [exist "name"]] then
/const name string = "ledstat"
/endif
/if [not [exist "nstbits"]] then
/const nstbits integer = 48
/endif
;
; Derived constants.
;
/block
/var local ii integer
/var local r real
/var local s string
/const npattw integer = [div [+ nstbits 23] 24] ;N prog mem words per pattern
/set r [/ 1000 nstbits] ;ms ticks per display pattern slice
/const add1ms integer = [rnd [/ 65536 r]] ;accumulator increment per ms
/endblock
;*******************************************************************************
;
; Variables.
;
;*******************
;
; Global state.
;
.section .stat, bss
;*******************
;
; Local state.
;
alloc lastclock ;last 1 ms clock value updated to
alloc accslice ;overflows when time for next display slice
alloc slice ;0-N current display slice number
alloc pattn ;0-N number of pattern being displayed
.section .stat_code, code
;*******************************************************************************
;
; Subroutine STAT_INIT
;
; Initialize the hardware and software state managed by this module.
;
glbsub stat_init, regf0
mov tick1ms, w0
mov w0, lastclock ;init last clock value current with
mov #65535, w0
mov w0, accslice ;force slice update next STAT_UPDATE
mov w0, pattn ;init current pattern number to invalid
mov #[- nstbits 1], w0
mov w0, slice ;first update will start at start of pattern
leaverest
;*******************************************************************************
;
; Subroutine STAT_UPDATE
;
; This routine is intended to be called periodically by the main event loop.
; It determines the current system state, where it is within the current
; display pattern, and updates the display accordingly.
;
; Timing for the display is derived here from the global TICK1MS clock
; variable. This routine need not be called with any particular timing.
; Since elapsed time is detected in whole ms, calling it faster than that has
; no benefit, although it does no harm other than to take more excution
; cycles. Calling it less often than that will not cause time to be lost, but
; will cause the display to be updated in bursts so as to appear to "stutter"
; if too slow. Roughly calling this routine every 1 to 5 ms is recommended.
;
glbsub stat_update, regf0 | regf1 | regf2 | regf3
stupd_recheck: ;back here after a clock tick was processed
mov lastclock, w0 ;get last clock value updated to
mov tick1ms, w1 ;get the current clock
cp w0, w1
bra z, stupd_leave ;no new tick, nothing more to do ?
;
; A new clock tick has occurred.
;
add #1, w0 ;update last clock value we are now current with
mov w0, lastclock
mov accslice, w0 ;update slice time accumulator to this new tick
mov #[v add1ms], w1
add w0, w1, w0
mov w0, accslice
bra nc, stupd_recheck ;no new display slice this tick ?
;
; Advance to next display slice.
;
mov slice, w0 ;get the current 0-N slice number
add #1, w0 ;increment it
mov #[- nstbits 1], w1 ;get max slice number
cp w0, w1
skip_leu ;still within valid range ?
mov #0, w0 ;no, wrap back to 0
mov w0, slice
;
; The display will be updated.
;
; Determine the pattern to display.
;
pattnum ;set W0 to the 0-N number of pattern to display
;
; W0 contains the 0-N number of the pattern to display.
;
mov pattn, w1 ;get number of pattern currently displaying
mov w0, pattn ;update number of pattern to display now
cp w0, w1 ;compare new pattern to previous
bra z, stupd_hpatsl ;same pattern as last time ?
;
; The display pattern has changed. Reset to displaying the start of
; the pattern.
;
mov #0, w0
mov w0, slice ;set to first slice in pattern
mov w0, accslice ;set to start of this slice
stupd_hpatsl: ;PATTN and SLICE all set
;
; Update the display. PATTN is the number of the pattern to display, and
; SLICE is the 0-N slice to display within the pattern.
;
;
; Init W3:W2 to point to the start of the patterns table in program
; memory.
;
mov #tbloffset(tbl_patt), w2 ;init W3:W2 pointing to start of table
mov #tblpage(tbl_patt), w3
and #0xFF, w3
;
; Update W3:W2 to point to the start of the selected pattern.
;
mov pattn, w0 ;get 0-N pattern number
mov #[* npattw 2], w1 ;get program memory addresses per pattern
mul.uu w0, w1, w0 ;make address offset for start of pattern
add w2, w0, w2 ;make start address of this pattern
addc w3, w1, w3
;
; Skip over whole program memory words of the pattern to point W3:W2
; to the program memory word containing the bit to display.
;
mov slice, w0 ;get 0-N slice number within this pattern
stupd_pwslice: ;back here to skip whole prog mem words
cp w0, #24 ;compare to number of bits in a prog mem word
bra ltu, stupd_dpwslice ;done finding prog mem word of this slice ?
sub #24, w0 ;no, make 0-N number of bit within next word
add #2, w2 ;update address to next word
addc #0, w3
jump stupd_pwslice ;back to check slice within word again
stupd_dpwslice: ;W0 is 0-N bit within word at W3:W2
;
; W3:W2 is the address of the whole program memory word that contains
; the bit to display. W0 is the 0-23 number of the bit within that
; word.
;
mov w3, Tblpag ;set high bits of prog mem address to read from
cp w0, #16
bra geu, stupd_hword ;bit is in the high word ?
tblrdl [w2], w1 ;no, read the low word
jump stupd_hbits ;have bit pattern
stupd_hword: ;the bit is in the high word
tblrdh [w2], w1 ;read the high word
sub #16, w0 ;make bit number within this part of the word
stupd_hbits:
;
; W0 is the 0-N number of the bit within W1 to display.
;
lsr w1, w0, w1 ;move the selected bit into LSB of W1
btsc w1, #0 ;bit is off ?
jump stupd_don ;no, the bit is on
set_ledstat_off ;display off this slice
jump stupd_recheck
stupd_don: ;display on this slice
set_ledstat_on
jump stupd_recheck
stupd_leave:
leaverest
////////////////////////////////////////////////////////////////////////////////
//
// Macro PATTERN patt
//
// Create the table entry for one status display pattern. PATT must be a
// sequence of "*" and "-" characters. "*" lights the LED for that time slice
// and "-" makes it dark. There must be exactly NSTBITS characters in PATT.
//
// Patterns are displayed from left to right with a complete sequence lasting
// one second.
//
// This macro will emit .PWORD directives to define the data for the pattern
// in program memory.
//
/macro pattern
/var local patt string = [qstr [arg 1]] ;get the pattern string
/var local ind integer = 1 ;1-N index into pattern string
/var local pchar string ;single character extracted from PATT
/var local word integer ;current program memory word being built
/var local nbits integer ;number of bits set within program memory word
/var local ii integer ;scratch integer
/set nbits 0 ;init to no bits set in current word
/set word 16#FFFFFF ;init bits in current program memory word
/loop ;back here each new bit in the pattern
/if [> ind nstbits] then ;done all bits ?
/quit
/endif
/set pchar [sindx ind patt] ;get the pattern character for this bit
/set ind [+ ind 1] ;update PATT index for next time
/if [not [or [= pchar "*"] [= pchar "-"]]] then
/show 'Invalid character "' patt '" in display pattern'
.error "Patt char"
.end
/stop
/endif
/if [= pchar "-"] then ;set this bit to off ?
/set ii [shiftl 1 nbits] ;make mask for this bit within word
/set ii [~ ii] ;make AND mask for turning off this bit
/set word [and word ii] ;apply the mask to turn off this bit within word
/endif
/set nbits [+ nbits 1] ;count one more bit done in current word
/if [>= nbits 24] then ;have a whole word to write out ?
.pword 0x[chars [int word "fw 6 lz base 16 usin"]]
/set nbits 0 ;reset to start of new word
/set word 16#FFFFFF ;reset word to all bits on
/endif
/endloop ;back to do next input pattern bit
/if [> nbits 0] then ;there are unwritten bits in WORD ?
.pword 0x[chars [int word "fw 6 lz base 16 usin"]]
/endif
/endmac