Newer
Older
pic-leddrvr / leddrvr.asm
;;; 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