Stay Informed!

If you'd like to receive periodic updates on this project, enter your name and email address below. Your information is kept strictly private.

Project Introduction

--Click Here--

Sidebar: Halloween!

Hacking the Garden Lights

Pixels For Sale

Online Store Rave Reviews

Other Useful Bits

Half a Pixel Point Source Pixel Six Channel LED dimmer + R/C Servo Control Pixel Programmer Pixel String Driver System

New Design for 2007

Overview, Design Philosophy & Project Updates Pixel System FAQ Pixel Schematic Microcontroller Source Code

Media

Video Clips

The 2005 RGB Christmas Light Project

Overview & Design Philosophy The MAX/MSP Jitter Patch Pixel Schematic Pricing Details Build one Yourself Final Testing & Project Photos Video Clips Original 2005 Blog

Other Links

Main ES Website

 

The 2007 RGB Christmas Light Project: PIC Source Code

Below you'll find the source code. It's so far proven to be very stable. But, as with all things free, it comes with neither guarantee nor warranty.

It's being distributed under a Creative Commons License, the full text of which can be found here:

http://creativecommons.org/licenses/by-sa/2.5/deed.en_US

It's free for non-commercial use.

If you feel this is valuable, consider throwing a few bucks our way to help with ongoing project development. Donations are gratefully accepted.


If you look carefully, you'll notice that this particular code snippet does not:

* Check to see if the start code is valid

* Recover well if DMX is lost mid-stream and then re-applied.

 

; -------------------------------------------------------------
; JECPWM_3.asm											
; (c) 2007 John Chapman, Engineering Solutions Inc
; Inspired somewhat by Microchip's application notes
; for receiving DMX and generating software PWM.
; Note: The reference designs for these pixels are distributed 
; under a Creative Commons license Attribution-ShareAlike 2.5

; 
;
; 16F688 Chip Connections
;
; PORT		PIN		CONFIGURATION
; ==================================
; A0        13      PGM Header 
; A1        12      PGM Header 
; A2        11      N/C
; C0        10      Red Drive
; C1        9       Green Drive
; C2        8       Blue Drive
; C3        7       N/C
; C4        6       N/C
; C5        5       DMX512 RX
; MCLR      4       Vcc w/4.7K pullup
; OSC2      3       N/C
; OSC1      2       N/C
; GND       14		GND
; VCC       1		Vcc
;

	list      p=16F688        ; list directive to define processor
	#include < p16F688.inc >   ; REMOVE THE EXTRA SPACES BETWEEN < and > - processor specific variable definitions
	errorlevel  -302          ; suppress message 302 from list file

; Here include Configuration settings

; OSC = INTOSCIO
; Watchdog = Disabled
; Power up Timer  = Disabled
; MCLR = Reset
; Brownout = Enabled, SBODEN disabled
; Switchover = Disabled
; Failsave = Disabled
; Code = As needed
; EEPROM = As needed
 	
	CBLOCK 0x20

	wsave	
	ssave	
	psave	
	redval
 	greenval
	blueval
	pwmpins
	rednew
	greennew
	bluenew
	IntCount
	dmxhighbyte
	dmxlowbyte
	skiphigh	
	skiplow	
	temp

	ENDC

	#define		BANK0	bcf		STATUS,RP0
	#define		BANK1	bsf		STATUS,RP0

	ORG 0x000				; Main Program

	clrf PCLATH
	goto start

	ORG 0x004				; Interrupt handler goes here

pwmdrive
 	movwf	wsave				; Store the usual bits when interrupting
 	swapf	STATUS, w
 	clrf	STATUS
 	movwf	ssave
 	movf	PCLATH, w
 	movwf	psave

; ------- Actual Interrupt Routine Here --------------------

 	movlw 	D'223'			; TMR0 will roll over every 40.0 uS
 	movwf 	TMR0			; which allows for 97.65 Hz PWM frequency
					; Nice thing here is that even if we're receiving
					; full-on DMX (44, 512-byte frames per second)
					; the PWM routine can easily keep up.  So some 
					; exciting effects should be possible.

DecIntCount						
	decfsz	IntCount,F		; Decrement IntCount register.  If it's zero, reset 
	goto	redcheck		; the PWM routine.  Otherwise, check each PWM value

PWMReset				; We've been through 256 cycles of PWM, so it's time
	bcf	pwmpins,0		; to reset everything
	bcf	pwmpins,1		; pwmpins is a byte variable.  Bytes are cleared
	bcf	pwmpins,2		; here (turning off red, gren and blue drive pins)
	movf	pwmpins,w		; and the entire byte is pushed out to PORTC.
	movwf	PORTC
	movlw	D'255'			; Reset the counter to allow 255 values per channel 
	movwf	IntCount		; Also, since we're resetting, it's time to transfer
    	movf  	rednew,w		; the *new variables (which came from the DMX routine)
    	movwf 	redval			; to the working PWM variables.
    	movf  	greennew,w		; If the transfer takes place at any time *other* than
    	movwf 	greenval		; during a PWM reset, the LEDs flicker unflatteringly
    	movf  	bluenew,w		; We're guaranteed that new DMX data is made available
    	movwf	blueval			; to the PWM routine as soon as possible
    	goto	pwmexit			; Exit ISR

; Here we compare each of the duty cycles to the IntCount.  If the values
; match, the corresponding pin is driven high.  It's a bit counter-intuitive
; but it works.  Note that if a value of 255 is received, it won't work.  So 
; the DMX routine limits PWM values to [0 254].  Which is good enough.

redcheck
	movf	redval,w
	subwf	IntCount,w
	btfss	STATUS,Z	 	; Are they equal?
	goto	greencheck		; Note that the *val variables are used only within
	bsf	pwmpins,0		; the PWM routine.  The actual DMX data is stored	
greencheck				; in the *new variables, and transferred to these
	movf	greenval,w		; working variables only when the PWM rolls over
	subwf	IntCount,w			         
	btfss	STATUS,Z            	; If the values are equal, set the bit (which turns         
	goto	bluecheck		; on the corresponding drive pin)
	bsf	pwmpins,1

bluecheck
	movf	blueval,w			         
	subwf	IntCount,w		
	btfsc	STATUS,Z             
	bsf	pwmpins,2	
	movf	pwmpins,w		; move the pin variable out to PORTC
	movwf	PORTC			; which will drive / clear the LEDs as needed

pwmexit
 
 	BCF    INTCON,T0IF      	; Clear the TMR0 interrupt flag
 	movf	psave,w			; Restore whatever was happening prior
 	movwf	PCLATH			; to the interrupt and get back to
 	swapf	ssave,w			; gathering DMX data.
 	movwf	STATUS
 	swapf	wsave,f
 	swapf	wsave,w
 
 	retfie				; Back to gathering DMX data



; ---------- Main Program Starts Here ----------------------------

start
	
	call	chipinit		; Initialize pins, oscillator, etc
	call	pwminit			; Initialize TIMER0 and enable TMRO interrupts
	call 	rxinit			; Set up the EUSART to receive DMX at 250,000 baud

; ---------- Set DMX Start Address Here -------------------------
	movlw	D'0'			; Set the DMX Address here.  It's a 16 bit
	movwf	dmxhighbyte		; number in two 8-bit bytes.  Highbyte can be
	movlw	D'7'			; [0 2] and lowbyte can be [0 256].  Overall, then,
	movwf	dmxlowbyte		; the range is from [0 512].
; ---------- End DMX Start Address Programming -------------------

	BANK0

dmxcapture

	movf	dmxhighbyte,w		; Skipcounter is used to detmine how many
	movwf	skiphigh		; received data bytes are skipped before the RGB
	movf	dmxlowbyte,w		; data is collected.  Load skipcounter with
	movwf	skiplow			; the DMX address from above...

	movf  	skiplow,f		; ... then decrement it by one
  	btfsc	STATUS,Z		; so we know how many channels to ignore before the
   	decf  	skiphigh,f		; useful data arrives.  We'll see more of the 
  	decf  	skiplow,f		; skipcounter a bit farther down the page.

waitbreak
	btfsc	PIR1,RCIF		; Here we're waiting to see if a break occurs
	movf	RCREG,w			; in the data signal.  Since we're *only*
	btfss	RCSTA,FERR		; receiving DMX, anything which generates a 
	goto	waitbreak		; framing error in the EUSART will count as a break.
	movf	RCREG,w			; If a byte is received correctly, dump it and loop				
					; back until we get the error we need
					; without being able to synchronzie to the break signal
					; there's no way to extract valid DMX data
waitforstart
	btfss	PIR1,RCIF		; Now that a break signal is detected, 
	goto 	waitforstart		; loop until a new byte is received *without*
	btfsc	RCSTA,FERR		; a framing error.  If all is well AND the
	goto 	waitforstart		; new byte is zero (which means the start code
	movf	RCREG,w			; is also zero, it's okay to begin gethering channel
					; data

	; RIGHT NOW WE'RE NOT TESTING FOR A ZERO START CODE. THIS WILL BE CHANGED IN FUTURE
	; VERSIONS OF THE CODE.  BUYER BEWARE!

	movf	dmxhighbyte,1		; Here check to see if the highbyte is
	btfss	STATUS,Z		; zero. If it is,check to see if the	
	goto 	bytecapture		; lowbyte is 1.  If 1, grab the next three bytes
	movf	dmxlowbyte,w		; which come through.  If <> 1, go to the routine
	xorlw	D'1'			; which receives and discards bytes until the 
	btfsc	STATUS,Z		; DMX address has been reached.
	goto 	waitforred		

bytecapture	

	btfss	PIR1,RCIF		; If we're here, it's because the start address is
	goto  	bytecapture		; greater than one. Hover until a byte is received.
	movf	RCREG,w			; Then, capture & move to 'w'...	
								
 	movf  	skiplow,f		; ...decrement the skip counter... 
  	btfsc	STATUS,Z	 	; (all sixteen bits of it)
   	decf  	skiphigh,f
  	decf  	skiplow,f
					; ...and see if we've reached the start address.
	movf	skiplow,1		; If the skip counter now equals zero, we know
	btfss	STATUS,Z		; that we need to gather the next three bytes	
	goto 	bytecapture		; and save them as RGB data.  If the counter is
	movf	skiphigh,1		; still nonzero, loop back and do it again.
	btfss	STATUS,Z
	goto	bytecapture

waitforred
	btfss	PIR1,RCIF		; Wait until 'red' byte is received
	goto 	waitforred		; once it arrives, store it in 'temp'
	movf	RCREG,w			; and call the 'maxcheck'  routine.  Since the		
	movwf	temp			; PWM code only works for values between [0 254]
	call	maxcheck		; maxcheck will set levels of 0xFF to 0xFE
	movwf	rednew			; then store them in the proper bit bucket.
					; remember the the *new variables are converted
					; to *val variables when the PWM routine resets 
					; itself
waitforgreen	
	btfss	PIR1,RCIF		; process is repeated for green data
	goto 	waitforgreen
	movf	RCREG,w
	movwf	temp
	call	maxcheck
	movwf	greennew

waitforblue				; ...and for blue data
	btfss	PIR1,RCIF		; It is assumed that there will be enough DMX
	goto 	waitforblue		; channels available to capture three bytes
	movf	RCREG,w			; per pixel.  For this reason, we don't check to
	movwf	temp			; see if the DMX string has timed out anywhere.
	call	maxcheck		; Rather, once all three bytes have been received,
	movwf 	bluenew			; the code loops back and waits for a new start code.
					; Were we to 'run out' of channel data in here somewhere
					; the code may behave strangely.  Caveat Emptor!

	goto 	dmxcapture		; Got all three bytes?  Repeat Ad Absurdum.

 
; ----------- Routines for Starting the Chip ---------

chipinit
	BANK0				; Memory bank 0
	clrf	PORTC			; All PORTC Pins off
	clrf	CMCON0			; Comparators aren't used either

	BANK1				; Switch to memory bank 1
	bcf	TRISC,0			; Red Drive Pin
	bcf	TRISC,1			; Green Drive Pin
	bcf	TRISC,2			; Blue Drive Pin
	clrf	ANSEL			; Turn off A/D Converters
	bsf	OSCCON,6		; Set these three
	bsf	OSCCON,5		; bits to enable the
	bsf	OSCCON,4		; 8 MHz internal oscillator
	bcf	TRISA,0			; PORTA.0 and PORTA.1 were used
	bcf	TRISA,1			; for testing and debugging, so set as outputs
	return

rxinit
	BANK0
	bsf	TRISC,5			; PORTC.5 is input for DMX data to EUSART
	clrf	TXSTA			; Clear TXSTA register
	movlw 	B'10010000'		; Serial Port and continuous receive enabled
	movwf	RCSTA				
	movlw	D'1'			; for baud rate generator
	movwf	SPBRG			
	clrf	SPBRGH			; This combination assures 0% error when
	bsf	BAUDCTL,BRG16		; receiving DMX at 250,000 bits per second
	return				; the PLL makes it possible to grab such
					; high speed data without any error

pwminit
	movlw	B'10100000'		; Enable global and TMR0 interrupts
	movwf	INTCON
	BANK1
	clrf	OPTION_REG		; No prescaler for TMR0 needed
	BANK0	
	return



; ----------------- Other Subroutines Go Here

maxcheck

	; Processes value which is stored in W. 
	; New value is also in W when routine exits

	xorlw	D'255'			; Here we're checking to see if a received
	btfsc	STATUS,Z		; byte is greater than 254.  If it is,
	goto 	exit			; set it to 254.  If it's less than 254
	movf	temp,w			; leave it alone and the PWM routine will deal with
	return				; it shortly
exit
	movlw	D'254'
	return

	END