Shop OBEX P1 Docs P2 Docs Learn Events
DMX Enabled SX Halloween Prop Controller — Parallax Forums

DMX Enabled SX Halloween Prop Controller

MacgruberMacgruber Posts: 20
edited 2010-11-01 09:39 in Robotics
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:
' =========================================================================
'
'   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
Sign In or Register to comment.