;;; 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