DMX Enabled SX Halloween Prop Controller
Hi All,
Just thought I would share my postmortem using an SX microcontroller as a prop controller for a Halloween display. Unfortunately I am at the office so I can't post the code and schematic until I get home later...
First, I must thank Jon Williams for his excellent tutorial in Nuts and Volts (Dimming the Lights Fantastic) and code contribution. Largely, all of my code is derived from Jon with only a few modifications. I highly recommend checking out his prop controllers at efx-tek. I wasn't able to use them because I needed DMX but he mentioned that they have something in the pipeline for the end of the year that can handle this!
The project consisted of using an SX clocked at 50MHz and using a 1.333us interrupt. The controller was responsible for actuating 4 500mA relays from radioshack (wired into various prop test switches), adjusting the intensity of 2 AC sources via a Q6015L5 Triacs with MOC3021 and another IC (name escapes me for now) for zero cross detection, 4 AC based props controlled using optically isolated SSRs and a UDN2981, 4 solenoids controlled using a ULN2803A darlington array. The UDN2981 was necessary cause I had an expansion board for the AC relays that required source drivers.
Ultimately, the highlight of the project involved actuating the solenoids which shot little paper mache ghosts up some beams using air pressure as well as opening and closing the lid to a coffin using a pneumatic cylinder. The coffin was filled with fog using a fog machine along with 2 additional DMX LED lights. The kids really got a kick out of it.
DMX was used along with some DMX control software to time the sequence of the coffin and the ghosts to fire with some lightning sounds as well as monster roars. DMX is a protocol that requires transmission at 250Kbaud so a decent amount of optimization was required in the code (thanks again Jon). The program used an interrupt of 1.333us so that the SX could sample the serial signal 3 times per bit period. This proved to be far more robust than trying to using the built in SX/B serial communication configured for 250Kbaud!
Using the interrupt was not only critical for the DMX communication but also for the manipulation of the AC lights using phase angle modulation. In brief, the program had to run a "dimmer service" every 32us (~25 interrupt cycles) and detect zero crossover or switch on the triacs (1/(60Hz*2))/256. Altogether, it worked quite well, I am sure that the dimming could be further optimized with more code adjustments to hit 32.55us exactly (right now I am more at 33.325 which produces a small amount of flicker).
The program had to use a kind of state machine to advance through the various IO updates because not everything could be accomplished in the interrupt (1.333us / 20ns per instruction) = ~66 instructions per interrupt cycle. For this reason, much of the code was written in assembly to ensure that additional clock cycles weren't lost and then advanced one per interrupt.
The whole circuit was addressable so that I can add to it in the years to come!
It worked very well but I had a small issue getting the solenoids to play nicely together. I'm still not quite sure why but when I ran the coffin solenoid ~150mA with the three ghost solenoids ~270mA each, I would notice that the solenoids would rapidly click on and off resulting in no real switching. I couldn't seem to work out getting a 270mA solenoid (even a single one) to fire at the same time as the 150mA solenoid??? I had the ULN2803 wired so that each channel ran in parallel (i.e. one IO pin on the SX would drive two inputs on the ULN2803 and then I tied the two outputs together to feed each solenoid) so I feel like I wasn't overloading the ULN2803! It never seemed to get hot??? And I believe that even with a high duty cycle it should have worked fine. Thoughts???
Attached are the code and PCB... (The PCB schematic is a little rough since I only used it as a reference for wiring my perfboard.)
Video:
http://www.youtube.com/watch?v=782tnT8n7PA
Code:
Just thought I would share my postmortem using an SX microcontroller as a prop controller for a Halloween display. Unfortunately I am at the office so I can't post the code and schematic until I get home later...
First, I must thank Jon Williams for his excellent tutorial in Nuts and Volts (Dimming the Lights Fantastic) and code contribution. Largely, all of my code is derived from Jon with only a few modifications. I highly recommend checking out his prop controllers at efx-tek. I wasn't able to use them because I needed DMX but he mentioned that they have something in the pipeline for the end of the year that can handle this!
The project consisted of using an SX clocked at 50MHz and using a 1.333us interrupt. The controller was responsible for actuating 4 500mA relays from radioshack (wired into various prop test switches), adjusting the intensity of 2 AC sources via a Q6015L5 Triacs with MOC3021 and another IC (name escapes me for now) for zero cross detection, 4 AC based props controlled using optically isolated SSRs and a UDN2981, 4 solenoids controlled using a ULN2803A darlington array. The UDN2981 was necessary cause I had an expansion board for the AC relays that required source drivers.
Ultimately, the highlight of the project involved actuating the solenoids which shot little paper mache ghosts up some beams using air pressure as well as opening and closing the lid to a coffin using a pneumatic cylinder. The coffin was filled with fog using a fog machine along with 2 additional DMX LED lights. The kids really got a kick out of it.
DMX was used along with some DMX control software to time the sequence of the coffin and the ghosts to fire with some lightning sounds as well as monster roars. DMX is a protocol that requires transmission at 250Kbaud so a decent amount of optimization was required in the code (thanks again Jon). The program used an interrupt of 1.333us so that the SX could sample the serial signal 3 times per bit period. This proved to be far more robust than trying to using the built in SX/B serial communication configured for 250Kbaud!
Using the interrupt was not only critical for the DMX communication but also for the manipulation of the AC lights using phase angle modulation. In brief, the program had to run a "dimmer service" every 32us (~25 interrupt cycles) and detect zero crossover or switch on the triacs (1/(60Hz*2))/256. Altogether, it worked quite well, I am sure that the dimming could be further optimized with more code adjustments to hit 32.55us exactly (right now I am more at 33.325 which produces a small amount of flicker).
The program had to use a kind of state machine to advance through the various IO updates because not everything could be accomplished in the interrupt (1.333us / 20ns per instruction) = ~66 instructions per interrupt cycle. For this reason, much of the code was written in assembly to ensure that additional clock cycles weren't lost and then advanced one per interrupt.
The whole circuit was addressable so that I can add to it in the years to come!
It worked very well but I had a small issue getting the solenoids to play nicely together. I'm still not quite sure why but when I ran the coffin solenoid ~150mA with the three ghost solenoids ~270mA each, I would notice that the solenoids would rapidly click on and off resulting in no real switching. I couldn't seem to work out getting a 270mA solenoid (even a single one) to fire at the same time as the 150mA solenoid??? I had the ULN2803 wired so that each channel ran in parallel (i.e. one IO pin on the SX would drive two inputs on the ULN2803 and then I tied the two outputs together to feed each solenoid) so I feel like I wasn't overloading the ULN2803! It never seemed to get hot??? And I believe that even with a high duty cycle it should have worked fine. Thoughts???
Attached are the code and PCB... (The PCB schematic is a little rough since I only used it as a reference for wiring my perfboard.)
Video:
http://www.youtube.com/watch?v=782tnT8n7PA
Code:
' ========================================================================= ' ' File...... DMX_Halloween_Dim-Relay.SXB ' Purpose... DMX-512 interface for 2 AC dimmers, 4 DC relays, 4 AC relays, ' and 4 Solenoids ' Author.... Justin Cuzens ' Copyright (c) 2010 Cuzens Consulting ' Some Rights Reserved ' -- see http://creativecommons.org/licenses/by/3.0/ ' E-mail.... jecuzens@gmail.com ' Started... ' Updated... 23 OCT 2010 ' ' ========================================================================= ' ------------------------------------------------------------------------- ' Program Description ' ------------------------------------------------------------------------- ' ' DMX-512 Halloween Prop Controller ' -- uses an RS-485 adapter to communicate with a DMX host (such as ENTEC ' open usb). Controls 4 ac loads via optically isolated SSRs, 2 AC ' dimmers via crossover detection, 4 solenoids driven by uln2803, and ' 4 DC relays for switching props on using the test switch. ' ------------------------------------------------------------------------- ' Device Settings ' ------------------------------------------------------------------------- ID "DMX-14x" 'DEVICE SX28, OSCHS1, BOR42 DEVICE SX28, OSCHS2, TURBO, STACKX, OPTIONX, BOR42 FREQ 50_000_000 ' ------------------------------------------------------------------------- ' IO Pins ' ------------------------------------------------------------------------- 'Props Prop1 PIN RB.1 OUTPUT Prop2 PIN RB.2 OUTPUT Prop3 PIN RB.3 OUTPUT Prop4 PIN RB.4 OUTPUT 'AC Relays ACRel1 PIN RC.0 OUTPUT ACRel2 PIN RC.1 OUTPUT ACRel3 PIN RC.2 OUTPUT ACRel4 PIN RC.3 OUTPUT 'Solenoids Sol1 PIN RC.4 OUTPUT Sol2 PIN RC.5 OUTPUT Sol3 PIN RC.6 OUTPUT Sol4 PIN RC.7 OUTPUT 'Dimmer Dimmer1 PIN RB.5 OUTPUT Dimmer2 PIN RB.6 OUTPUT 'Inputs ZCross PIN RB.7 INPUT NOPULLUP SCHMITT RX PIN RB.0 INPUT 'Address Addr PIN RA INPUT PULLUP ' board address, 0 - 15 ' ------------------------------------------------------------------------- ' Constants ' ------------------------------------------------------------------------- Yes CON 1 No CON 0 ' ------------------------------------------------------------------------- ' Variables ' ------------------------------------------------------------------------- flags VAR Byte ' (keep global) isrFlag VAR flags.0 rxReady VAR flags.1 ' has byte(s) in buffer relayState VAR Byte ' relay state (global) state VAR Byte ' program state dmxStart VAR Byte breakTmr VAR Byte ' to measure DMX break channel VAR Word ' current channel rxCount VAR Byte ' rx bit count rxDivide VAR Byte ' bit divisor timer rxByte VAR Byte ' recevied byte dimmerTix VAR Byte ' (ISR) divider for dimming idx VAR Byte arrChan VAR Byte(16) level1 VAR arrChan(0) level2 VAR arrChan(1) level3 VAR arrChan(2) level4 VAR arrChan(3) level5 VAR arrChan(4) level6 VAR arrChan(5) level7 VAR arrChan(6) level8 VAR arrChan(7) level9 VAR arrChan(8) level10 VAR arrChan(9) level11 VAR arrChan(10) level12 VAR arrChan(11) level13 VAR arrChan(12) level14 VAR arrChan(13) acc1 VAR arrChan(14) acc2 VAR arrChan(15) ' ------------------------------------------------------------------------- INTERRUPT NOCODE 750_000 ' 1.333 us ' ------------------------------------------------------------------------- ' -------- ' Mark ISR ' -------- ' Marker: ASM BANK flags SETB isrFlag ' mark for foreground ENDASM ' ------- ' RX UART ' ------- ' Receive: ASM JB rxReady, RX_Done ' skip if byte waiting MOVB C, RX ' sample serial input TEST rxCount ' receiving now? JNZ RX_Bit ' yes, get next bit MOV W, #9 ' no, prep for next byte SC MOV rxCount, W ' if start, load bit count MOV rxDivide, #5 ' prep for ~1.6 bit periods RX_Bit: DJNZ rxDivide, RX_Done ' complete bit cycle? MOV rxDivide, #3 ' yes, reload bit timer DEC rxCount ' update bit count SZ RR rxByte ' position for next bit JNZ RX_Done SETB rxReady ' alert foreground RX_Done: ENDASM ' ------- ' Dimmer updates ' ------- ' Check_Dimmer_Tix: ASM BANK dimmerTix ' (1) INC dimmerTix ' (1) update dimmer divider CJB dimmerTix, #25, Update_Relays ' (2/4) ready for service? CLR dimmerTix ' (1) yes, reset and go ' Dimmer service runs every 32.55 uS (256 levels x 120 Hz) ' -- inspired by code from Phil Short ' Dimmer_Service: BANK arrChan ' (1) JNB ZCross, Update_Triacs ' (2/4) skip if not at ZC Zero_Cross: AND rb, #%10011111 ' (1) switch off dimmer channels MOV acc1, level13 ' (2) reset dimming accumulators MOV acc2, level14 ' (2) Update_Triacs: INC acc1 ' (1) update accumulator SNZ ' (1/2) rollover? SETB Dimmer1 ' (1) yes, triac on INC acc2 ' (1) SNZ ' (1/2) SETB Dimmer2 ' (1) JMP Dimmer_Done Update_Relays: MOV W, relayState ' point to current channel JMP PC+W ' jump to it JMP Relay_1 JMP Relay_2 JMP Relay_3 JMP Relay_4 JMP Relay_5 JMP Relay_6 JMP Relay_7 JMP Relay_8 JMP Relay_9 JMP Relay_10 JMP Relay_11 JMP Relay_12 Relay_1: BANK arrChan MOVB Prop1, level1.7 ' output on if rollover JMP Dimmer_Done Relay_2: BANK arrChan MOVB Prop2, level2.7 ' output on if rollover JMP Dimmer_Done Relay_3: BANK arrChan MOVB Prop3, level3.7 ' output on if rollover JMP Dimmer_Done Relay_4: BANK arrChan MOVB Prop4, level4.7 ' output on if rollover JMP Dimmer_Done Relay_5: BANK arrChan MOVB ACRel1, level5.7 ' output on if rollover JMP Dimmer_Done Relay_6: BANK arrChan MOVB ACRel2, level6.7 ' output on if rollover JMP Dimmer_Done Relay_7: BANK arrChan MOVB ACRel3, level7.7 ' output on if rollover JMP Dimmer_Done Relay_8: BANK arrChan MOVB ACRel4, level8.7 ' output on if rollover JMP Dimmer_Done Relay_9: BANK arrChan MOVB Sol1, level9.7 ' output on if rollover JMP Dimmer_Done Relay_10: BANK arrChan MOVB Sol2, level10.7 ' output on if rollover JMP Dimmer_Done Relay_11: BANK arrChan MOVB Sol3, level11.7 ' output on if rollover JMP Dimmer_Done Relay_12: BANK arrChan MOVB Sol4, level12.7 ' output on if rollover JMP Dimmer_Done Dimmer_Done: BANK $00 INC relayState ' point to next channel CSNE relayState, #12 CLR relayState ENDASM RETURNINT ' ------------------------------------------------------------------------- ' Subroutine Declarations ' ------------------------------------------------------------------------- ' ========================================================================= PROGRAM Start ' ========================================================================= Start: New_Frame: ASM CLR state ' wait for break CLR breakTmr SETB rxReady ' disable UART CLR rxDivide ' reset UART ENDASM Read_Addr_Bits: ASM CLR dmxStart ' clear the channel pointer MOV dmxStart, Addr ' move Addr into dmxStart AND dmxStart, #%00001111 ' mask out the bits out of register ENDASM dmxStart = dmxStart << 4 ' multiply by 16 Check_Address: IF dmxStart = 0 THEN Start ' trap bad address low IF dmxStart > 240 THEN Start ' trap bad address high Main: ASM CLRB isrFlag JNB isrFlag, $ ' wait for flag ENDASM Handle_State: ASM MOV W, state JMP PC+W JMP Wait_4_Break JMP Break_Release JMP Skip_Start JMP Find_Target JMP Update_Channels ENDASM Wait_4_Break: ASM ' waiting for break INC breakTmr ' update break timer SNB RX ' in break state? CLR breakTmr ' no, reset timer CJB breakTmr, #65, Main ' wait for break INC state JMP Main ENDASM Break_Release: ASM JNB RX, Main ' abort if still in break CLR rxCount ' reset UART CLRB rxReady ' enable UART INC state JMP Main ENDASM Skip_Start: ' skip start code ASM JNB rxReady, Main ' exit if not ready CLRB rxReady ' re-enable UART MOV channel_LSB, #1 ' reset for channel search CLR channel_MSB INC state JMP Main ENDASM Find_Target: IF channel < dmxStart THEN ' still looking? IF rxReady = Yes THEN ' byte available? rxReady = No ' re-enable UART INC channel ' update channel pointer ENDIF GOTO Main ELSE \ CLR idx ' select first channel \ INC state ENDIF ' let it drop through Update_Channels: ASM JNB rxReady, Main ' abort if no byte ready MOV __PARAM1, rxByte ' get rxByte... MOV __PARAM2, idx ' get channel index MOV FSR, #level1 ' point to levels ADD FSR, __PARAM2 ' add index MOV IND, __PARAM1 ' dimmer(idx) = rxByte BANK $00 CLRB rxReady ' re-enable UART INC idx ' point to next channel CSE idx, #15 ' done? JMP Main ' no JMP New_Frame ' prep for next DMX frame ENDASM