;;; LEDDRVR.ASM
;;; Copyright (c) 2002, TundraWare Inc., All Rights Reserved
;;;
;;; Program to drive homemade 4-digit LED display board
;;;
;;; $Id: leddrvr.asm,v 1.13 2002/03/25 16:55:26 tundra Exp tundra $
list p=16F84A
include <P16F84A.INC>
errorlevel -302 ;suppress bank selection messages
__config 3ff1h ;xt osc, WDT off
__idlocs 1234
;;;;;;;;;;
;;; Device Constants
;;;;;;;;;;
tmr2 equ 00H ; TMR0 prescaler constants
tmr4 equ 01H
tmr8 equ 02H
tmr16 equ 03H
tmr32 equ 04H
tmr64 equ 05H
tmr128 equ 06H
tmr256 equ 07H
;;;;;;;;;;
;;; Circuit Constants
;;;;;;;;;;
num_led equ 4 ; total number of LEDs
led_sel_mask equ 0f0H ; bits not used for LED == 1
led_sel equ PORTA
seg_sel equ PORTB
;;;;;;;;;;
;;; Program Contants
;;;;;;;;;;
;;; TMR0 related stuff
;;;
;;; With a 4MHz clock, the basic cycle type is 1/4 this or 1us.
;;; The TMR0 interrupt interrupt interval is thus:
;;;
;;; 1us * prescaler factor * tmr0_count
;;;
;;; TMR0 interrupt every 1ms
tmr0_count equ D'256'-D'250' ; set for 1ms interrupt interval
tmr0_pre equ tmr4 ; prescaler factor
;;; wait delays: delay time = value(s) below * TMR0 interrupt rate
count_delay equ D'100' ; 100ms delay between counts
;;;;;;;;;;
;;; Register File Assignments
;;;;;;;;;;
CBLOCK 0cH
_fsr ; context save area
_pclath
_status
_w
dsply_val:2 ; array of values to display
; 2 LEDs per byte
current_led ; currently displayed LED
; 0 <= current_led < num_led
wait_count ; time delay counter variable
temp ; temp storage during calculation
ENDC
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PAGE
;;;;;;;;;;
;;; Power-On Entry Point
;;;;;;;;;;
org 00H
goto main
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;
;;; Interrupt Handler
;;;;;;;;;;
org 04H
movwf _w ; save context
movfw STATUS
bcf STATUS,RP1
bcf STATUS,RP0
movwf _status
movfw FSR
movwf _fsr
movfw PCLATH
movwf _pclath
clrf PCLATH
; Figure out who interrupted and service it
btfsc INTCON,T0IF ; did we get a TMR0 interrupt?
goto svc_tmr0 ; yup
goto restore_context ; nope
; TMR0 ISR
svc_tmr0:
bcf INTCON,T0IF ; clear the interrupt condition
movlw tmr0_count ; prime the counter
movwf TMR0
clrf INTCON ; Enable *just* this interrupt
bsf INTCON,T0IE ; to maintain timing accuracy
bsf INTCON,GIE
; Update the wait timer if it is in use
; Only do this on TMR0 interrupts so timing is predictable
movfw wait_count
btfss STATUS,Z ; non-zero count means 'in use'
decf wait_count,F ; decrement current count
; not in-use, fall through to next code
call display_led ; then display it
goto restore_context ; goto here (instead of just falling
; through) allows other ISRs
; to later be added immediately
; following
restore_context:
movfw _pclath ; restore entry context
movwf PCLATH
movfw _fsr
movwf FSR
movfw _status
movwf STATUS
swapf _w,F ; get W back w/o changing Z flag
swapf _w,W
retfie ; all done
; End of interrupt handler
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PAGE
;;;;;;;;;;
;;; Main program logic
;;;;;;;;;;
main:
call init ; initialize the system
run:
call incr_count
movlw count_delay
call wait ; slow down displayed count
goto run
;;; End of main logic
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PAGE
;;;;;;;;;;
;;; Data & Tables
;;;;;;;;;;
;;;;;;;;;;
;;; Hex Digit-to-Segment lookup table
;;;;;;;;;;
hex_7seg addwf PCL,F
DT 03fH ; 0
DT 006H ; 1
DT 05bH ; 2
DT 04fH ; 3
DT 066H ; 4
DT 06dH ; 5
DT 07dH ; 6
DT 007H ; 7
DT 07fH ; 8
DT 067H ; 9
DT 077H ; A
DT 07cH ; B
DT 058H ; C
DT 05eH ; D
DT 079H ; E
DT 071H ; F
;;;;;;;;;;
;;; Table used to translate LED position to equivalent led_sel
;;; bit pattern for that LED. It is assumed that any routine using
;;; this table will properly preserve higher-order bits not needed for
;;; LED selection.
;;;;;;;;;;
led_lookup:
addwf PCL,F
DT 01H ; LED0 == LSD
DT 02H ; LED1
DT 04H ; LED2
DT 08H ; LED3
;;; End of 'led_lookup'
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
PAGE
;;;;;;;;;;
;;; Supporting Subroutines
;;;;;;;;;;
;;;;;;;;;;
;;; Display LEDs, one per call, from MSD->LSD
;;; This effectively multiplexes across all the LEDs
;;; when called repeatedly.
;;;;;;;;;;
display_led:
; clear last displayed digit
movlw led_sel_mask ; clear out LED selection bits only
andwf led_sel,F
clrf seg_sel ; all segments off
; compute and set the segments to display for this digit
movfw current_led ; convert current_led into an
movwf FSR ; offset from base of display storage
bcf STATUS,C ; don't rotate C into calc below
rrf FSR,F ; /2 - two digits stored in each byte
movlw dsply_val ; base of array containing display vals
addwf FSR,F ; point to desired value
movfw INDF ; and get contents
movwf temp ; store temporarily
btfsc current_led,0 ; even or odd nibble
swapf temp,F ; odd==hi nibble -> low nibble
movfw temp ; get it
andlw 0fH ; mask off high nibble
call hex_7seg ; convert to equivalent seg pattern
movwf seg_sel ; and turn 'em on
; select the corresponding LED
movfw current_led ; offset into lookup table
call led_lookup
iorwf led_sel,F ; turn on these LED selection bits
; determine next LED to display
movfw current_led ; get LED just displayed
btfss STATUS,Z ; was it the LSD?
goto set_next ; nope
movlw num_led ; yup, start again w/MSD
movwf current_led
set_next:
decf current_led,F ; pickup next digit to right
return
; End of 'display_led'
PAGE
;;;;;;;;;;
;;; Increment current count by 1
;;;;;;;;;;
incr_count:
incfsz dsply_val,F ; count up
return
incf dsply_val+1,F
return
;;; End of 'incr_count'
PAGE
;;;;;;;;;;
;;; Initialize hardware and software at startup as needed.
;;; Returns with register bank 0 selected.
;;;;;;;;;;
init:
clrf INTCON ; all interrupts off, flags cleared
; setup control registers
bsf INTCON,T0IE ; unmask TMR0 interrupts
bsf STATUS,RP0 ; select register bank 1
clrf TRISA ; set PORTA to all outputs
clrf TRISB ; set PORTB to all outputs
clrf OPTION_REG ; TMR0 selected w/ internal clock
; don't care about RBPU, INTEDG
movlw tmr0_pre ; set TMR0 prescaler value
iorwf OPTION_REG,F
bcf STATUS,RP0 ; select register bank 0
; setup program variables
clrf dsply_val ; start count at 0
clrf dsply_val+1
movlw tmr0_count ; prime the counter
movwf TMR0
movlw num_led-1 ; start displaying with MSD
movwf current_led
movlw led_sel_mask ; clear out LED selection bits only
andwf led_sel,F
clrf seg_sel ; no segments to display yet
bsf INTCON,GIE ; enable unmasked interrupts
return
;;; End of 'init'
PAGE
;;;;;;;;;;
;;; Kill time - Outter loop constant passed in W.
;;;;;;;;;;
wait:
movwf wait_count ; this will be decremented in ISR
loop:
movfw wait_count ; get current count
btfss STATUS,Z ; see if we counted all the way down
goto loop ; not done, keep waiting
return ; all done
;;; End of 'wait'
PAGE
;;;;;;;;;;
;;; EEPROM Contents
;;;;;;;;;;
org 2100h
DE "Copyright 2002, TundraWare Inc. All Rights Reserved."
END