;;; LEDDRVR2.ASM ;;; Copyright (c) 2002, TundraWare Inc., All Rights Reserved ;;; ;;; Program to drive homemade LED display board ;;; ;;; $Id: leddrvr2.asm,v 2.4 2002/05/02 19:25:21 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 8 ; total number of LEDs led_clk equ 0 ; bit used to clock led selection led_dat equ 1 ; bit used for led selection data led_strobe equ 2 led_ctrl equ PORTA ; intf to LED control shift registers ;;;;;;;;;; ;;; 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 interval equ D'250' ; basic (TMR0 interval/4) in us tmr0_count equ D'256'-interval ; set for 250us interrupt interval tmr0_pre equ tmr4 ; prescaler factor ;;; wait delays: delay time = value(s) below * TMR0 interrupt rate count_delay equ D'5' ; 5ms delay between counts ;;;;;;;;;; ;;; Register File Assignments ;;;;;;;;;; CBLOCK 0cH _fsr ; context save area _pclath _status _w dsply_val:(num_led/2) ; array of values to display ; 2 LEDs per byte current_led ; currently displayed LED ; 0 <= current_led < num_led led_seg ; segment to illuminate led_sel ; led selection pattern 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 DT 10H ; LED4 DT 20H ; LED5 DT 40H ; LED6 DT 80H ; LED7 ;;; 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: ; compute and set the segments to display for this digit ; this logic only works if 'num_led' is *even* !!!! 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 led_seg ; and save for shift out ; select the corresponding LED movfw current_led ; get the bit pattern that selects call led_lookup ; desired LED movwf led_sel ; and save for shift out ; now shift the current control bits out and latch them call led_wrt ; 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 ;;;;;;;;;; ;;; Shift the control bits in 'led_seg' and 'led_sel' into the ;;; LED controller shift registers. ;;;;;;;;;; led_wrt: ; first shift out LED select pattern LSB->MSB movlw 08H ; do this for all 8 bits shift_sel: bcf led_ctrl,led_dat ; assume output 0 btfss led_sel,0 goto clk_sel ; shift out a 0 bsf led_ctrl,led_dat ; shift out a 1 clk_sel: bsf led_ctrl,led_clk ; clock it into the bcf led_ctrl,led_clk ; shift register rrf led_sel,F ; pickup the next bit addlw -1 ; decrement repeat count btfss STATUS,Z goto shift_sel ; then shift out segment select pattern 'a'->'dp' movlw 08H ; do this for all 8 bits shift_seg: bcf led_ctrl,led_dat ; assume output 0 btfss led_seg,0 goto clk_seg ; shift out a 0 bsf led_ctrl,led_dat ; shift out a 1 clk_seg: bsf led_ctrl,led_clk ; clock it into the bcf led_ctrl,led_clk ; shift register rrf led_seg,F ; pickup the next bit addlw -1 ; decrement repeat count btfss STATUS,Z goto shift_seg ; now strobe to output latch bsf led_ctrl,led_strobe ; propagate changes bcf led_ctrl,led_strobe ; latch changes return ;;; End of 'led_wrt' PAGE ;;;;;;;;;; ;;; Increment current count by 1 ;;;;;;;;;; incr_count: incfsz dsply_val,F ; count up 8 digits worth return incfsz dsply_val+1,F return incfsz dsply_val+2,F return incf dsply_val+3,F return ;;; End of 'incr_count' PAGE ;;;;;;;;;; ;;; Kill time - Outer 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 ;;;;;;;;;; ;;; 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 movlw dsply_val ; initialize count storage to 0 movwf FSR ; this logic only works if movlw num_led/2 ; if 'num_led' is *even* !!! zero_count: clrf INDF ; zero the storage location addlw -1 ; decrement loop count btfsc STATUS,Z ; when zero, we're done goto prime_counter ; we're done incf FSR,F ; point to next storage location goto zero_count ; and do it again prime_counter: movlw tmr0_count ; prime the counter movwf TMR0 movlw num_led-1 ; start displaying with MSD movwf current_led bcf led_ctrl,led_strobe ; initialize control bits bcf led_ctrl,led_clk clrf led_seg ; all segments off clrf led_sel ; all LEDs off call led_wrt ; write out the changes bsf INTCON,GIE ; enable unmasked interrupts return ;;; End of 'init' PAGE ;;;;;;;;;; ;;; EEPROM Contents ;;;;;;;;;; org 2100h DE "Copyright 2002, TundraWare Inc. All Rights Reserved." END