Update 6/21/2016: I’ve added a binary build below!
Last year in a group buy we bought some LED strobe lights. They self-randomize, but after testing them, we found out they don’t randomize for quite some time. When they first turn on, they all flash in unison and slowly change based on the random delay length.
This wouldn’t fit well with what we were planning for our strobes (short use at certain features parts of songs) so I had to come up with something. I didn’t want to use left-over Renard channels because of things like $/channel and lack of controllers in certain areas. For example, our mega tree has 16 strands, next year we’ll have a star, and we want 10-12 strobes. Instead of using more than 1 renard controller (to have a strobe per channel for the random start we want) I came up with a cheaper solution. Albeit, not much cheaper, I was able to do some learnin’ which was also a motivating factor for me to go the selected route.
DirkCheap SSR
Most 4-channel SSRs circuits in the DIY community cost around $8 each when purchasing a few through a group buy. An SSR is something that switches high voltage loads for your lights. They’re typically grouped by 4 channels and use a Cat5 cable with RJ45 connectors to control the individual channels. They’re typically driven by a Renard, DMX, or other controler that doesn’t have onboard SSRs. Part of the cost of most typical SSR circuits is the triac which with the right part, can handle quite a lot of current. The DirkCheap SSRs don’t use a triac because they use an optoisolator that can handle up to 1A of a 120V load. This eliminates one of the more expensive parts of an SSR and the cost of a DirkCheap is along the lines of $4 per SSR when purchasing a few through a group buy. The stick is, you can only switch 1A or less rather than a typical SSR which can be a couple to a few amps per channel. In my case the strobes are not even near an amp.
DIGWDF 675
Instead of using individual channels and sequencing software to randomize each strobe, I figured I’d try working something out to use only 1 channel. This would make it much easier in sequencing to turn on 1 channel for strobes, instead of multiple. This lead me to the DIGWDF675 which was designed to run some sort of pre-determined sequence for 4 channels when powered with 5V. This means you can somewhat multiplex channels. With the help of teh tubes I learned enough PIC assembly to be dangerous, as well as accomplish my task. I adapted code for a few things. I wrote subroutines for randomization (mostly from an example on the web). I wrote code to take the least significant 4 bits as flags for 4 different outputs. I wrote code to delay 10ms (largely adapted from examples on the web). Lastly I wrote the main firmware which read the PIC12F675’s internal EEPROM to seed the random value generator, wrote the next generated value back to EEPROM, then randomly turned on output pins. This meant that the LEDs would all come on at slightly different times and in random order. That combined with the LED’s on-board randomization gives the effect we want.
Here’s the code that is used:
firmware.asm
;****************************************************************************** ; * ; Filename: firmware.asm * ; Date: 2012/11/14 * ; * ; Author: Mathew Mrosko * ; * ;****************************************************************************** ; * ; Files required: P12F675.INC * ; * ;****************************************************************************** ; * ; Notes: Main firmware file to wrap functionality. * ; Pseudo code of what this does: * ; * Sets up PIC12F675 * ; * Reads EEPROM * ; * Seeds random generator with EEPROM value * ; * Calls random subroutine * ; * Writes new random value to EEPROM * ; * Randomly turns on 1 of 4 output pins * ; * ;****************************************************************************** ;------------------------------------------------------------------------------ ; PROCESSOR DECLARATION ;------------------------------------------------------------------------------ LIST P=12F675 ; list directive to define processor #INCLUDE; processor specific variable definitions ; Functions from other files EXTERN delay10_R EXTERN blink_R EXTERN turn_on_R EXTERN random_R ;------------------------------------------------------------------------------ ; ; CONFIGURATION WORD SETUP ; ; The 'CONFIG' directive is used to embed the configuration word within the ; .asm file. The lables following the directive are located in the respective ; .inc file. See the data sheet for additional information on configuration ; word settings. ; ;------------------------------------------------------------------------------ ; __CONFIG _CP_OFF & _CPD_OFF & _BODEN_OFF & _MCLRE_OFF & _WDT_OFF & _PWRTE_ON & _INTRC_OSC_NOCLKOUT __CONFIG _INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_ON & _MCLRE_OFF & _CP_OFF & _CPD_OFF ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ;------------------------------------------------------------------------------ ; example of using Shared Uninitialized Data Section UDATA_SHR sGPIO RES 1 ; Shadow register for GPIOs GLOBAL sGPIO ; Make it accessible elsewhere RANDOM RES 1 GLOBAL RANDOM d1 res 1 d2 res 1 ;------------------------------------------------------------------------------ ; RESET VECTOR ;------------------------------------------------------------------------------ RESET CODE 0x0000 ; processor reset vector GOTO START ; go to beginning of program ;------------------------------------------------------------------------------ ; SUBROUTINE VECTORS ;------------------------------------------------------------------------------ delay10 ;pagesel delay10_R goto delay10_R GLOBAL delay10 ; Make this global so others can use the vectors blink ;pagesel blink_R goto blink_R turn_on ;pagesel turn_on_R goto turn_on_R random ;pagesel random_R goto random_R ;------------------------------------------------------------------------------ ; MAIN PROGRAM ;------------------------------------------------------------------------------ MAIN_PROG CODE START ;------------------------------------------------------------------------------ ; PLACE USER PROGRAM HERE ;------------------------------------------------------------------------------ errorlevel -302 movlw b'00001100' ; set everything to output (except GP3) banksel TRISIO movwf TRISIO movlw b'00000100' ; disable digital input buffer (set AN2 it to analog) banksel ANSEL movwf ANSEL movlw b'10001000' ; set right justified and select AN2 to be read into ADRESH banksel ADCON0 movwf ADCON0 movlw b'00000011' ; Comparator configuration banksel CMCON movwf CMCON ;movlw b'00111111' ; make them all high (LEDs wired to light when pin is low) movlw b'00000100' ; make them all low (LEDs wired to light when pin is high) movwf GPIO movwf sGPIO errorlevel +302 ;-----------ADC CODE----------- ;seed_random ; bsf ADCON0,GO ;Start A/D conversion ;read_adc_wait ; btfsc ADCON0,GO ;Wait for conversion to complete ; goto read_adc_wait ; movf ADRESH,w ;save analog read to w ; movwf RANDOM ;write A/D result to RANDOM ;-----------ADC CODE----------- ;-----------MANUAL SEED CODE----------- ; movlw b'10101110' ; movwf RANDOM ;-----------MANUAL SEED CODE----------- ;-----------EEPROM CODE----------- ;read random errorlevel -302 movlw .0 banksel EEADR movwf EEADR bsf EECON1,RD movf EEDATA,w banksel RANDOM movwf RANDOM call random movf RANDOM,w ;save random banksel EEDATA movwf EEDATA ;move the random thing (now in W) to EEDATA for the write bsf EECON1,WREN bcf INTCON,GIE ;disable interrupts movlw 055h ; REQUIRED FOR WRITE movwf EECON2 ; REQUIRED FOR WRITE movlw 0AAh ; REQUIRED FOR WRITE movwf EECON2 ; REQUIRED FOR WRITE bsf EECON1,WR ; REQUIRED FOR WRITE bsf INTCON,GIE ;re-enable interrupts errorlevel +302 ;-----------EEPROM CODE----------- main_loop call Delay call Delay call Delay call Delay call Delay banksel RANDOM call random movf RANDOM,w call turn_on goto main_loop ; Delay = 0.01 seconds ; Clock frequency = 4 MHz ; Actual delay = 0.01 seconds = 10000 cycles ; Error = 0 % ; cblock ; d1 ; d2 ; endc Delay ;9993 cycles movlw 0xCE movwf d1 movlw 0x08 movwf d2 Delay_0 decfsz d1, f goto $+2 decfsz d2, f goto Delay_0 ;3 cycles goto $+1 nop ;4 cycles (including call) return loop_forever goto loop_forever END
turn_on.asm
;****************************************************************************** ; * ; Filename: turn_on.asm * ; Date: 2012/11/14 * ; * ; Author: Mathew Mrosko * ; * ;****************************************************************************** ; * ; Files required: P12F675.INC * ; * ;****************************************************************************** ; * ; Notes: Turns on one of 4 output pins based on * ; least significant bits. * ; * ;****************************************************************************** ;------------------------------------------------------------------------------ ; PROCESSOR DECLARATION ;------------------------------------------------------------------------------ #INCLUDE; processor specific variable definitions #define POSITIVE_OUTPUT ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ;------------------------------------------------------------------------------ GLOBAL turn_on_R EXTERN sGPIO ; example of using Shared Uninitialized Data Section UDATA_SHR led_val RES 1 CODE turn_on_R andlw b'00000111' ;Save the last bits for 0 movwf led_val check_1 decfsz led_val,F goto check_2 goto turn_on_1 check_2 decfsz led_val,F goto check_3 goto turn_on_2 check_3 decfsz led_val,F goto check_4 goto turn_on_3 check_4 decfsz led_val,F goto done goto turn_on_4 turn_on_1 movf sGPIO,w iorlw b'00100000' xorlw b'00100000' IFDEF POSITIVE_OUTPUT xorlw b'00100000' ENDIF movwf sGPIO movwf GPIO goto done turn_on_2 movf sGPIO,w iorlw b'00010000' xorlw b'00010000' IFDEF POSITIVE_OUTPUT xorlw b'00010000' ENDIF movwf sGPIO movwf GPIO goto done turn_on_3 movf sGPIO,w iorlw b'00000010' xorlw b'00000010' IFDEF POSITIVE_OUTPUT xorlw b'00000010' ENDIF movwf sGPIO movwf GPIO goto done turn_on_4 movf sGPIO,w iorlw b'00000001' xorlw b'00000001' IFDEF POSITIVE_OUTPUT xorlw b'00000001' ENDIF movwf sGPIO movwf GPIO goto done done retlw 0 END
random.asm
;****************************************************************************** ; * ; Filename: random.asm * ; Date: 2012/11/14 * ; * ; Author: Mathew Mrosko * ; * ;****************************************************************************** ; * ; Files required: P12F675.INC * ; * ;****************************************************************************** ; * ; Notes: Generates a pseudorandom number. * ; * ;****************************************************************************** ;------------------------------------------------------------------------------ ; PROCESSOR DECLARATION ;------------------------------------------------------------------------------ #INCLUDE; processor specific variable definitions ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ;------------------------------------------------------------------------------ EXTERN RANDOM GLOBAL random_R CODE ;;; Random number generator. random_R RLF RANDOM,W RLF RANDOM,W BTFSC RANDOM,4 XORLW 1 BTFSC RANDOM,5 XORLW 1 BTFSC RANDOM,3 XORLW 1 MOVWF RANDOM RETLW 0 END
Delay10.asm
;****************************************************************************** ; * ; Filename: xxx.asm * ; Date: * ; * ; Author: * ; * ;****************************************************************************** ; * ; Files required: P12F675.INC * ; * ;****************************************************************************** ; * ; Notes: * ; * ;****************************************************************************** ;------------------------------------------------------------------------------ ; PROCESSOR DECLARATION ;------------------------------------------------------------------------------ #INCLUDE; processor specific variable definitions ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ;------------------------------------------------------------------------------ GLOBAL delay10_R ; example of using Shared Uninitialized Data Section UDATA_SHR dc1 RES 1 dc2 RES 1 dc3 RES 1 CODE delay10_R banksel dc3 movwf dc3 dly2 movlw .13 movwf dc2 clrf dc1 dly1 decfsz dc1,f goto dly1 decfsz dc2,f goto dly1 decfsz dc3,f goto dly2 retlw 0 END
blink.asm
;****************************************************************************** ; * ; Filename: blink.asm * ; Date: 2012/11/14 * ; * ; Author: Mathew Mrosko * ; * ;****************************************************************************** ; * ; Files required: P12F675.INC * ; * ;****************************************************************************** ; * ; Notes: Debugging routine. Write a number to W and call the * ; blink subroutine and we will blink a test pin a number * ; of times equal to the value in W. * ; * ;****************************************************************************** ;------------------------------------------------------------------------------ ; PROCESSOR DECLARATION ;------------------------------------------------------------------------------ #INCLUDE; processor specific variable definitions ;------------------------------------------------------------------------------ ; VARIABLE DEFINITIONS ;------------------------------------------------------------------------------ GLOBAL blink_R EXTERN delay10 EXTERN sGPIO ; example of using Shared Uninitialized Data Section UDATA_SHR loop_ct RES 1 CODE blink_R banksel loop_ct movwf loop_ct ;store the number of blinks bcf STATUS,C ;set C=0 for multiply rlf loop_ct,F ;double it for the on/off movlw .1 ; w=1 addwf loop_ct,F ;add w to loop_ct for initial toggle blink_loop movf sGPIO,w ;restore our shadow register to w xorlw b'00100000' ;toggle w (with an xor) movwf sGPIO ;save back to shadow register movwf GPIO ;save to actual GPIO movlw .50 ;load decimal 50 for a 50ms delay ;pagesel delay10 ;call our delay function call delay10 ;pagesel loop_ct decfsz loop_ct,F ;decrement our loop counter and goto blink_loop ; skip our loop if 0 retlw 0 END
Binary
Strobe Firmware (558 downloads)DC SSR
I briefly tested the DIGWDF675 being driven directly from the output of the PIC16F688, but the current requirements of the 675 controller exceed the 16F688 pin capability, so I put a DCSSR in between the Renard 64 output and the DIGWDF675. This added to cost and complexity, but should (**knocks on wood**) work reliably for me throughout the show.
Problems
ADC
I tried using an ADC read to seed the random generator, but that wasn’t working properly. I likely had something configured incorrectly on the chip since the code seemed to function, but always gave me a 0 when reading. This is indicative of having ^MCLRE set, which originally I did, but I didn’t realize that would result in GP3 ADC reads of 0 before switching solutions and haven’t gone back to test. My solution for this problem was suggested by a member of DIYC. Don (maybe one of the most creative, think-outside-the-box people at DIYC) suggested the EEPROM read/write to seed the random generator. Don’s website can be seen here: http://www.eshepherdsoflight.com/
Testing
I developed the code for the PIC12F675 using a breadboard and a ChipClip. I finally had everything working (or so I thought) and put the µC in the board which was assembled a few weeks ago. Once I did this, I encountered 3 problems.
- Cat5 needed to be rotated 180° – Another stupid mistake I made when trying to save money by not using RJ45 connectors and directly soldering Cat5 between the two PCBs was having the wires 180° out of phase. This was a simple re-solder job fix.
- MCLR – My original firmware.asm file had _MCLRE_ON which meant if pin4 of the PIC12F675 wasn’t pulled high the chip would reset. The ^MCLR pin is high with my PICkit2/ChipClip solution, so my breadboard worked, the pin is floating on Dirk’s DIGWDF design, so I had undefined behavior. The chip would be in reset mode until my hand went close to the pin which apparently was enough to get it above some threshold. Originally I suspected cabling or a cold solder joint, but realized I didn’t even have to move the circuit, just get near it for it to start working. Once I made the change to the chip configuration to disable ^MCLR, the code worked as expected.
- Continuous Power – The Renard 64 was in storage, so I used a Simple Renard 32 I had more available as a method of driving the DC SSR circuit. I was using my hephaestus tester to send Renard protocol data of 100% intensity, yet the circuit seemed to be in reset mode. I could see the faintest flicker on the strobes. Turns out the firmware of the Simple Renard 32 has roughly a 5ms break in it’s power output which was enough to put the PIC12F675 into freak-out mode.
Enclosure
To save money, one of the other things I took into consideration was the enclosure. In addition to being cheap, the two boards I chose (DIGWDF675 and DirkCheapSSR) both fit easily into 1″ PVC. This became my enclosure which I sealed up using hot glue in case I need to service/troubleshoot the configuration.