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

