;******************************************************************************
;	Pulse Width Modulated 'Linear' LED Bar Graph Display  -  (c) 1998
;
;
;	Length: 87 bytes
;	Author:	Craig Webb
;	Written: 97/02/26
;
;	This program implements two virtual peripherals using interrupts.
;	It shows to read a potentiometer as an 8 bit value and pulse width
;	modulate a bar-graph of 16 LEDs arranged in a 4x4 matrix on port B
;	in order to provide a smooth 'sliding' signal effect by varying the
;	brightness of adjacent LEDs when the potentiometer 8 bit value lies
;	somewhere between them.
;	
;
;******************************************************************************
;
;******	Assembler directives
;
		DEVICE		pins18,pages1,banks8,stackx,optionx
		DEVICE		osc4mhz,turbo
		ID		'16 LEDs'
		RESET		Start			;set reset/boot address
;
;******************************* Program Variables ***************************
;
;******	Register definitions (bank 0)
;
		ORG	08h			;global variables 08-0Fh
reading		DS	1			;potentiometer reading
display		DS	1			;LED display output level
flags		DS	1			;program flags
;
; variables for LED interrupt routine
;
		ORG	10h			;bank 0 variables
mainbank	EQU	$
;
LED_bank	EQU	$			;(can be other than bank 0)
LED		DS	1			;holds which LED to light
cycle_count	DS	1			;pwm cycle count
pot_count	DS	1			;temporary pot timing count
clear_delay	DS	1			;delay period to clear cap.
sample_delay	DS	1			;delay period per sample
						; (reduces power consumption)
;
;******	Bit variable definitions
;
pot		EQU	RA.0			;potentiometer in RC (input)
triggered	EQU	flags.0			;status of pot. reading
clearing	EQU	flags.1			;hi while cap. is clearing
;
;******	Constants
;
sample_time	=	2			;time between pot. readings
clear_time	=	45			;delay value for clearing
						; the capacitor (>=2)
int_period	=	200			;interrupt period (based
						;          on RTCC counts)
IO_portA	=	00001111b		;Port A input/output setup
LEDs_off	=	0Fh			;RB value for LEDs=off
;
;************************* Interrupt Routines ****************************
;	
		ORG	0
;
;*****	Virtual Peripheral : Read potentiometer
;
; This routine reads the value of the potentiometer by clearing the
; capacitor in the RC timing circuit and then measuring the time it takes
; the capacitor to charge until the port input goes high. To avoid high
; current draws at low potentiometer values, the routine only resamples
; after (256*sample_time) interrupt cycles. The maximum potentiometer
; reading is 255.
;
;	Input variable(s) : none
;	Output variable(s) : reading
;	Variable(s) affected : pot_count, clear_delay, sample_delay
;	Flag(s) affected : clearing, triggered
;	Timing cycles (turbo) : 12-charging, 14-triggered, 14-clearing
;
		JNB	clearing,:charge	;are we clearing cap.?
		MOV	W,#11111111b		;get port mask (=done)
		DECSZ	clear_delay		;is count done?
		MOV	W,#11111110b		;no, get port mask (=clearing)
		TEST	clear_delay		;is count done?
		SNZ				;if not, skip ahead
		CLRB	clearing		;yes, reset clearing flag
		AND	W,#IO_portA		;get port setup byte
		MOV	!RA,W			;adjust pot port status
		CLR	pot_count		;clear timing count
		JMP	:done_pot		;jump past checking routine
:charge		JNB	pot,:adjust_count	;triggered yet?
		MOV	W,pot_count		;get timing count
		SB	triggered		;is this first trigger cycle?
		MOV	reading,W		;yes, store result
		SETB	triggered		;set trigger flag
:adjust_count	INCSZ	pot_count		;adjust reading counter
		JMP	:done_pot		;was counter at maximum?
		MOV	W,#255			;no, store max. value
		SB	triggered		;did we already get reading?
		MOV	reading,W		;no, so set it to max.
		SETB	triggered		; and flag that we got value
		DECSZ	sample_delay		;time for new sample?
		JMP	:done_pot		;if not, keep cycling
:trig		CLRB	triggered		;yes, reset trigger flag
		SETB	clearing		;set flag to clear cap.
		MOV	sample_delay,#sample_time	;load sample and
		MOV	clear_delay,#clear_time	; clear delay time counts
:done_pot					;end of pot. reading routine
;
;
;*****	Virtual Peripheral : LED driver
;
; This routine drives the LED bar-graph array, providing 16 levels
; of brightness to allow an output slide effect between adjacent LEDs
; It must be called fairly often, otherwise the pulsing effect will
; become noticeable.
;
;	Input variable(s) : display
;	Output variable(s) : none
;	Variable(s) affected : cycle_count, LED
;	Timing cycles (turbo) : 21
;
;next instruction needed only if multiple variable banks are used
;
		MOV	W,<>display		;get input (nibble-swapped)
		AND	W,#0Fh			;keep high 4 bits (which LED)
		MOV	LED,W			;save it
		MOV	W,display		;get input level again
		AND	W,#00001111b		;keep lower 4 bits for PWM
		MOV	W,cycle_count-W		;calculate which LED to have on
:zero_point	SZ				;adjust zero baseline up one*
		SC				;next one up? If not skip ahead
		INC	LED			;yes, increment to next LED
		MOV	W,LED			;get LED number
		CALL	LED_Table		;fetch LED value
		MOV	RB,W			;light LED
		INC	cycle_count		;adjust PWM cycle
		SNB	cycle_count.4		;time to reset (16 cycles)?
		CLR	cycle_count		;yes, start new cycle
;
;*this instruction shifts the whole display range up by one, thus making the
;  first LED dimly lit on a reading of 0, and the last lit fully on a reading
;  of 255. If it's preferable that all LEDs be off on a reading of 0, this
;  instruction may be removed or commented out.
;
;
		MOV	W,#-int_period		;interrupt again after
		RETIW				;   'int_period' RTCC counts
;
;******************************* Subroutines *********************************
;	
;
;*****	Subroutine : LED_Table
;
; This is a look-up table that returns the output port value to light the LED
; contained in the W register. If W holds 0, then all LEDs are turned off.
;	
LED_Table	ADD	PCL,W			;get RB value for LED1-16
		RETW	0Fh			;LEDs all off
		RETW	1Eh			;LED1
		RETW	2Eh			;LED2
		RETW	4Eh			;LED3
		RETW	8Eh			;LED4
		RETW	1Dh			;LED5
		RETW	2Dh			;LED6
		RETW	4Dh			;LED7
		RETW	8Dh			;LED8
		RETW	1Bh			;LED9
		RETW	2Bh			;LED10
		RETW	4Bh			;LED11
		RETW	8Bh			;LED12
		RETW	17h			;LED13
		RETW	27h			;LED14
		RETW	47h			;LED15
		RETW	87h			;LED16
;
;
;********************************* Main Program *******************************
;
;*****	Initialization routine
;
Start		CLR	RA			;clear port A
		MOV	!RA,#IO_portA		;set up port A
		MOV	RB,#LEDs_off		;set all LEDs off
		MOV	!RB,#0			;configure port B as outputs
		CLR	FSR			;reset all ram starting at 08h
:zero_ram	SB	FSR.4			;are we on low half of bank?
		SETB	FSR.3			;If so, don't touch regs 0-7
		CLR	IND			;clear using indirect addressing
		IJNZ	FSR,:zero_ram		;repeat until done
		MOV	!OPTION,#10001000b	;enable interrupt on rtcc=xtal/1
		MOV	sample_delay,#sample_time	;load sampling period
;
;***** Main program loop
;
Mainloop
;
		MOV	display,reading		;copy pot. to LED ouput
;
;		<program code goes here>
;
		JMP	Mainloop		;keep looping
;
		END
