; PWM port (in w), pin, duty
; Generates a bit stream of 256 1s and 0s with the specified
; duty cycle. If this bit stream is run through a simple RC
; integrator, the result is an analog voltage output of
; (duty/255) * (PIC supply voltage). For example, if duty is
; 100 and the supply is 5 volts, then the integrated output of
; pwm would be (100/255) * 5 = 1.96 volts. In many applications,
; pwm output should be buffered with a unity-gain op-amp
; circuit. In those cases, the pwm output pin should be set up
; as an output when Pwm is called, then immediately switched to
; input when Pwm is done. This prevents the steady-state output
; of the pin from affecting the voltage set by Pwm.

	device	pic16c55,xt_osc,wdt_off,protect_off
	reset	start

	org	8
duty	ds	1	; Pwm duty cycle.
acc	ds	1	; Pwm accumulator.
index	ds	1	; Temporary counter for pwm.
pin	ds	1	; Pin number to pulse (0-7).

; Device data and reset vector
	org	0

; Table to convert pin number (0-7) into bit mask (00000001b to 10000000b).
Pinz	jmp	pc+w
	retw	1,2,4,8,16,32,64,128

start	mov	!ra, #0 	; All outputs.
	clr	ra		; Start with LED off.
	clr	duty		; Initial brightness = 0.
	clr	index		; Clear loop counter for pwm.
:loop	mov	pin,#2		; Pin 2.
	mov	w,#0		; of port ra.
	call	pwm		; Send pwm to LED.
	inc	duty		; Turn up brightness.
	jmp	:loop		; Endless loop
; Upon entry, the desired pin must already be set up as an output.
; Variable "pin" contains the pin number (0-7). The w register contains a
; number representing the output port (0-2) for RA through RC. The variable
; duty contains the desired duty cycle from 0 to 255.

Pwm	mov	fsr,w		; Point to the port number.
	add	fsr,#RA 	; Add offset for port RA.
	mov	w,pin
	call	Pinz		; Get bit mask from the table.
	mov	pin,w		; Put the mask into pin.
:loop	add	acc,duty	; Let acc = acc + duty

	mov	w,pin
	snc			; IF carry THEN pin = 1
	OR	indirect,w	; ELSE pin = 0.
	mov	w,/pin
	sc
	AND	indirect,w
	djnz	index,:loop	; Repeat 256 times.
	ret

