{{'' ===========================================================================
'' Appl" TicTacToe Dimmer control with AutoPlay ver 1.25
''   This was derived from the small channel boxes using the Prop Mini
''   Purpose..
..   Recieve DMX channel values and mulitplex outputs using 595 latches
''   Zero crossing detector and PWM lighting.
''   dimmers come in 8 channel banks. Each bit of 8 when a 1 represents that a
''   dimmer channel is on is wired to
''''  . serial 74HC595 latches which chain upto 4 latches.
''      for 32 outputs. (only 24 wired in box)
     There are enough outputs on the
     the proto board or quickstart to handle 6 chains or 192 channels.

     Not sure that the dimmer routine can handle that many.

''
''   Programmer - Rick G. Asche (Aging Beaver)
''   Author.....
            -- see below for terms of use
''   E-mail.....
''   Started.... 10 Sept  2022
''   Updated.... 15   sept
''   tested .....
'    ''
''   Dimmers are triac based - any 4-6A triac uill do,
''     Uses a MOC3023 for the gate trigger
''     180 ohm resister in gate.  Opto couplers are driven directly from
..     the 74595 latches.
''
''  AutoPlay will play a game if no DMX is present and allowed by enable input
''     (dip 10)  formally "Test"
''

         ver 1.01 derived from 32 chanel using 595...ver11_15....2.0
         ver 1.25 got the address routine working (shorted wires) now work on
                  output routine.
'' =============================================================================
}}
CON

  _clkmode = xtal1 + pll16x
  _xinfreq = 5_000_000

  CLK_FREQ = ((_clkmode - xtal1) >> 6) * _xinfreq
  MS_001   = CLK_FREQ / 1_000

  debug = 1' set to 1 to enable the serial terminal program

  UNIT_ADDR    = 1       'sets the default first address of DMX channels

                       ' Dimmer Constants
  DmxRange  = 512       'Number of Dimmers (NOTE: multiples of 32 only!)
                          'this is the number DMX values to read to the buffer
                          'this could be as high as 512, but only tested upto 256
  DMXrx         = 0       'DMX receive pin
  DMXrxLED      = 7 '17    DMX acitivity LED (GREEN)
  Hi = 255                'Dmx on
  Low = 0                 'Dmx off
  xline = "-"             '196  character
  yline = "|"             '124

 'for the dimmer pak.
  CHANNELS = 24   'physical channels on outputs of 74HC595 (12 channels)

  NumOfDimmers  = 224     ' Number of Dimmers (NOTE: multiples of 32 only!)

  LAST_CH  = CHANNELS
  zerocrosspin      = 8
  firstchanpin      = 19

  offstr  = %11111111111111111111111111111111
  onstr =   %00000000000000000000000000000000
                         ' the DMX address from serial shift register
  ClkSerialpin = 5       '13   '74HC165 pin 2  .
  LoadSerialpin = 6      '14   '165 pin 1
  SerialDatapin = 4      '15   'last 165 pin 9
  numSerialInputs = 16   'in blocks of 8.   ie two 74HC165 chips are present

  outDatapin = 1              '595 pin 14 serial data pint
  outShiftpin = 2             '595 pin 11 shift
  outLoadpin = 3              '595 pin 12 store

VAR

  long  cog                             ' cog running driver

  long  zcpin                           'zero crossing detector input pin
  long  ch0pin                          ' io pins for AC
  long  brpntr                          ' pointer to brightness array
  long  hcpntr                          ' pointer to hctix
  long  hctix                          'cnt ticks in half cycle
  long  shiftaddr                   'the addr of the shifdata register
  long  DimmerShiftData             'to hold output data (same as shiftdate)

  long sumDMX                       'DAM data stream summation
  long  DMXptr                         ' DMX buffer pointer
  long games                        'number of current game

  long  dummy                  'the addr of the shiftdata register in shift routie
  long  view                     'view of data
  long  loopscnt                 'iterations of no data
  long  OUT_CH[32]
  long Stack[150]                          'stack for DMX input buffer

  long  playStack[200]           'the game cell order random array (upto 200 unique gamesariable(


  word  StartAddr                      ' DMX start Address

  byte  playTTTflag             'flag to switch to autoplay methods
  byte  leadchar             ' the leadchar  in game, 1 is O 2 is X 0 is blank
  byte  dumby                 'byte length dummy value
  byte  brightness[32]               ' dimmer levels array, 0 to 255
  byte  firstcell
  byte  secondcell
  byte  thirdcell
  byte  fourthcell
  byte  fifthcell
  byte  sixthcell
  byte  seventhcell
  byte  eigthcell
  byte  ninthcell
  byte   autoplay                     'is the tenth dip switch on
  byte  tempDmxOut[32]                  'temporary array to hold inputs

  byte  IntsBffr[DmxRange+2]       'byte array with current intensity values

OBJ

  dmx   : "jm_dmxin"
  ser   : "74hc165 serial in"
  PST   : "Parallax Serial Terminal"
  o595: "Simple_74HC595"    '_RGA"  'this is imbedded in the code.
' -------------------------code from propconntroller site ------------------------------------------

PUB Main | idx1, ch0val,    chan,indx, ok3  ,y ,j  ,firstpin ,i

      firstpin := firstchanpin

      start(firstpin)            'start the Dimmer methods and ASM code

      bytefill(@brightness,255,Channels)   'clear the brightness array (255) will be dark

       ' bytefill(@IntsBffr, 0, DmxRange)
      bytefill(@IntsBffr, 0, Channels )


'start the serial terminal
        dira[17]~~               'turn on the the DMX activity pin for 2 sec at startup
        dira[16]~~
        dira[23]~~
        outa[17]~~
        delay(2000)
        outA[17]~
     ' if debug                      'if the debug flag is 1 then start terminal
        PST.start(57600)           'start the terminal at 56000 buad
        delay(500)
        PST.clear                 'clear the terminal

     StartAddr := UNIT_ADDR          'default DMX Start Address goes here
    'get the dip switch channel number by intiating the Serial method to read from
    'shift register connected to defined pins. 3 lines control the register.
                      'read the unit addree from dip switchs
    ser.init(ClkSerialpin,LoadSerialpin,SerialDatapin,numSerialInputs)
    delay(1000)     ' let register stablize
    autoplay := 0  ' clear the test mode flag

    StartAddr := ser.in_out(0)         'calls the read serial shift register
       if StartAddr > 511
         StartAddr := StartAddr - 512    'Dip SW 10 is on. and Test mode is set
         autoplay := 1           'in the new controller, this enables autoplay
       else
         autoplay := 0

 ' Setup DMX input source for dimmer data
      dmx.init(DMXrx,DMXrxLED,0,0)   'starts the DMX input cog
      DMXptr := dmx.address          '(dmx_in_pin,act_led_pin,start_byte,intial level)

      delay(100)
                   'stop lights from going to full at start

  'Setup the serial output cog.                             'sample data string

      delay(1000)

      o595.init(outShiftpin,outLoadpin,outDatapin)  'start the 74hc595 method

      shiftaddr := o595.DataPtr  'get the shiftdata addr

   'intsBFFR is the DMX values received buffer for the entire DMX universe
   'Brightness is the output channel intensity values for dimmer output
   '
   'turn off all outputs on startup.

    delay(1000)
    '         start the autoplay game cog
    'ok3 := cog := cognew(playgame,@playStack)
 '=================================================================================
    'This is the loop that continuously updates the intensity data received
    'in the DMX buffer and loads it to the dimmer buffer

  repeat          'Main Program Loop
            ' repeat indx from StartAddr to StartAddr + CHANNELS  -1 'this is the program loop that
      'j := 0     'inital clear this index on startup

       StartAddr := ser.in_out(0)         'calls the read serial in.
             if StartAddr > 511
                StartAddr := StartAddr - 512    'Dip SW 10 is on. and Test mode is set
                 autoplay := 1           'in the new controller, this enables autoplay
             else
                 autoplay := 0

       repeat indx from 1 to DmxRange    'DmxRange  'this is the program loop that
             j := indx - StartAddr     'start at array index zero.
             IntsBffr[indx] := dmx.read(indx) 'fills the inputbuffer

            if indx => StartAddr               'greater or equal
                 if indx =< StartAddr + CHANNELS -1      'less than
                     sumDMX := (sumDMX + IntsBffr[indx])   'build summation of DMX values
                      if autoplay 'check the manual advance PB
                         brightness[j] := 200

                      else                         'normal execution
                           brightness[j] := IntsBffr[indx] 'move the value from the DMX
                           o595.Out(DimmerShiftData)
                           if debug
                              PST.dec(250)
                              PST.str(string(" addr "))
                              PST.dec(StartAddr)
                              PST.str(string(" indx = "))     'write info to screen
                              PST.dec(indx)
                              PST.str(string(" j = "))
                              PST.dec(j)

                              PST.PositionX(35)
                              PST.str(string("Buffer value =  "))
                              PST.dec(IntsBffr[indx])
                              PST.str(string("  bright = " ))
                              PST.dec( brightness[j])
                               PST.newline
                               PST.PositionX(20)

                               PST.str(string(" sumdmx= "))
                               PST.dec(sumDMX)
                               PST.str(string("plyflg = "))
                               PST.dec(playTTTflag)
                               PST.str(string(" shftdat "))
                               pst.dec(shiftaddr)
                               pst.str(string("  dum "))
                               pst.bin(dummy,32)
                               PST.newline
                               pst.str(string("dummy addr "))
                               pst.dec(@dummy)
                               PST.newline
                               PST.str(string("dimmer shift data "))
                               PST.bin( DimmerShiftData,32)
                               PST.newline
                       j  := j'    'dummy statement to take to next channel

                         if sumDMX < 100          'check for no DMX data
                             loopscnt := loopscnt + 1 <# 300   'increment zero loop counter  limit 100
                         if loopscnt > 240
                             playTTTflag := 1    'set the play Tic Tac Toe flag
                             OUTA[16] :=  1      'game play led
                             OUTA[23] := 0
                            ' o595.setbit(3,1)
                            ' o595.setbit(4,0)
                            ' o595.setbit(5,1)
                         if sumDMX > 200       ' resume normal operation
                             playTTTflag := 0   'reset the play Tic Tac Toe flag
                             loopscnt := 0      'reset the zero loop counter
                             OUTA[16] := 0
                             OUTA[23] := 1       'normal play led
                             'o595.setbit(2,1)
                             'o595.setbit(3,1)
                             'o595.setbit(4,1)
                             'o595.setbit(5,1)
                             'o595.setbit(6,1)
                             'o595.Out(ShiftData)
                             'delay(2000)
                             'o595.Out(offstr)          'offstr
                             'delay(1000)

        sumDMX := 0
              'j:= 0     'outside of range  - reset'load the outputs with the outData data frommer dimmers



 ' END of main loop



pub Delay(msec)                  'delay wait in Msec
     waitcnt(clkfreq / 1000 *msec + cnt)
  return

{{
 '' ======================================================================

''   File .......RGA_24Chan_Dimmer_Pak_VerA
''   based on  . the Four Channel dimmer - jm_ac_dimmer.spin
''   Purpose.... AC dimming using phase-angle modulation from zero-cross
''   Author .... Rick Asche
''   Suggested by Author..... Jon "JonnyMac" McPhalen
''               Copyright (c) 2012 Jon McPhalen
''               -- see below for terms of use
''   E-mail..... rasche@opusnet.com
''               jon@jonmcphalen.com
''   Started....
''   Updated.... orig by McPhalen - 30 MAR 2012
''               Revision by Asche - Oct 8, 2013
''  date         comment
''               all 24 channels are now working for dimmers. in two cogs
''  10/8/13G     remove some of the fluff (extra demo routines that aren't used
''               this simple demo walks thru all 24 channels for testing
''               Take out the Main routine and include this as obj in a DMX
''               receiver program
''               to make a full featured 24Channel DMX controller.
''  10/20/13     set range to NumOfDimmer instead of hard coded limit for buff
'' ============================================================================


  This code relies on an active-high zero-cross input.  Line frequence is
  determined from this signal, allowing the code to work in 50Hz and 60Hz
  systems without any modifications.

  Phase delay is done by adding $FFFFFF00 to level value and then adding 1.
  If the carry flag sets, the channel turns on (255 +1) = carry.
  This repeats for each channel and the 256 increments in a half cycle.

  Typical ZC detetion circuit (tested with this code)


    Outputs to channels start at Pin 1 thru Pin 24.
    SSR's arc triac based and use ULN2803 drivers.  A High on the output turns
    the channel ON.

 If you don't have a H11AA1, You can use a bridge rectifier in front of the
 Opto Coupler and use a standard optocoupler such as a 4N25 or 4N32 or similar.
 RGA

}}


pub start(ch0)   | zc

'' Start 4-channel AC dimmer
'' -- user responsible for correct pin assignments
''    * no error checking!
'' -- assumes ch0..ch23 are contiguous and in ascending order

    return startx(ch0)


pub startx(ch0) | ok ,ok2
'' Start 4-channel AC dimmer
'' -- user responsible for correct pin assignments
''    * no error checking!

  stop                                           ' stop if running now

  'longmove(@ch0pin, @ch0,1)                     ' copy pins
  brpntr := @brightness                          ' point to levels array
  hcpntr := @hctix                               ' point to half-cycle ticks value

  ok := cog := cognew(@dimmer, @ch0pin) + 1       ' start the dimmer cog

  return ok


pub stop

'' Stops AC dimmer; frees a cog

  set_all(0)                                              ' all off

  if (cog)                                                ' if running
    cogstop(cog - 1)                                      ' stop
    cog := 0                                              ' mark stopped


pub set(ch, level)

'' Sets channel to specified level

  if ((ch => 0) and (ch =< LAST_CH))                   ' valid channel?
    brightness[ch] := 0 #> level <# 255                ' limit to byte value

  return brightness[ch]                                ' return actual ch level


pub set_all(level)

'' Set all channels to same level

  level := 0 #> level <# 255
  bytefill(@brightness[0], level, CHANNELS)

  return level


pub high(ch)                              ' keep for testing purpose only

'' Sets channel to on

  if ((ch => 0) and (ch =< LAST_CH))
    brightness[ch] := 255


pub address

'' Returns hub address of brightness array

  return @brightness


pub hc_ticks

'' Returns cnt ticks in 1/2-cycle of AC

  return hctix



dat

                        org     0

dimmer                  xor    outa,outa           'clear output
                        mov     t1, par            ' start of structure

                        OR     dira, OutMask12     'Output mask

                        mov     zcmask, #1
                        shl     zcmask,#8

                        andn    dira, zcmask       ' make input - mask is inverted then anded

                        add     t1, #4
                        rdlong  hubch0, t1         ' read hub addr of levels array

                        add     t1, #4
                        rdlong  hubhc, t1           ' read hub addr of hctix


' This code debounces the ZC input to prevent noise from
' fouling the frequency detection

waitlo1                 mov     t1, #10                   ' wait for ZC low
:loop                   test    zcmask, ina     wc
        if_c            jmp     #waitlo1
                        djnz    t1, #:loop

waithi1                 mov     t1, #10          ' wait for leading edge of ZC
:loop                   test    zcmask, ina     wc
        if_nc           jmp     #waithi1
                        djnz    t1, #:loop

                        neg     period, cnt          ' start period measurement

waitlo2                 mov     t1, #10            ' wait for falling edge of ZC
:loop                   test    zcmask, ina      wc
        if_c            jmp     #waitlo2
                        djnz    t1, #:loop

waithi2                 mov     t1, #10             ' wait for 2nd leading edge
:loop                   test    zcmask, ina     wc
        if_nc           jmp     #waithi2
                        djnz    t1, #:loop

                        add     period, cnt        ' stop timing
                        sub     period, #4         ' remove overhead

                        wrlong  period, hubhc      ' save to hub
                        shr     period, #8         ' divide into 256 segments

waitzc0                 mov     t1, #10            ' wait for ZC low
:loop                   test    zcmask, ina     wc
        if_c            jmp     #waitzc0
                        djnz    t1, #:loop

waitzc1                 mov     t1, #10           ' wait for leading edge of ZC
:loop                   test    zcmask, ina     wc
        if_nc           jmp     #waitzc1
                        djnz    t1, #:loop

                        mov     outa, #0           ' outputs off
                        mov     timer, period      ' start levels timer
                        add     timer, cnt

getlevels               mov     t1, hubch0         ' point to levels (in hub)

                        add     t1,#0               'add bytes to skip over channels
                        rdbyte  ch0level, t1        ' read channel
                        add     ch0level, HxFFFF_FF00   'adjust to use C flag

                        add     t1, #1                  'point to next level
                        rdbyte  ch1level, t1
                        add     ch1level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch2level, t1
                        add     ch2level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch3level, t1
                        add     ch3level, HxFFFF_FF00

                        add     t1, #1                  'increment pntr
                        rdbyte  ch4level, t1
                        add     ch4level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch5level, t1
                        add     ch5level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch6level, t1
                        add     ch6level, HxFFFF_FF00

                        add     t1, #1                  'channel 8
                        rdbyte  ch7level, t1
                        add     ch7level, HxFFFF_FF00

                        add     t1,#1
                        rdbyte  ch8level, t1         ' read channel
                        add     ch8level, HxFFFF_FF00  ' adjust to use C flag

                        add     t1, #1                ' point to next level
                        rdbyte  ch9level, t1
                        add     ch9level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch10level, t1
                        add     ch10level, HxFFFF_FF00

                        add     t1, #1
                        rdbyte  ch11level, t1
                        add     ch11level, HxFFFF_FF00

                        mov     idx, #255

dimloop                 add     ch0level, #1    wc      ' inc ch level
        'if_c            or      outa, #$02       'Pin 1  if carry, turn on
         if_c            or      outData,#$01   'output 1 on 595

                        add     ch1level, #1    wc
          if_c            or      outa, #$04           'Pin 2
         if_c            or      outData, #$02   'output 2

                        add     ch2level, #1    wc
        'if_c            or      outa, #$08           'Pin 3
        if_c            or      outData,#$04     'output 3

                        add     ch3level, #1    wc
        'if_c            or      outa, #$10           'Pin 4
        if_c            or      outData, $08      'output 4

                        add     ch4level, #1    wc
        'if_c            or      outa, #$20           'Pin 5
        if_c            or      outData, $10       'output 5

                        add     ch5level, #1    wc
        'if_c            or      outa, #$40           'pin6
        if_c            or      outData, $20       'output 6

                        add     ch6level, #1    wc
        'if_c            or      outa, #$80           'pin 7
        if_c            or      outData, #$40       'output 7

                        add     ch7level, #1    wc
       ' if_c            or      outa, #$0100         'pin 8
        if_c            or      outData, #$80      'output 8

                       add     ch8level, #1    wc
        'if_c            or      outa, Hex200         'Pin 9
        if_c           or      outData, #$0100    'output 9

                        add     ch9level, #1    wc
        'if_c            or      outa, Hex400         'Pin 10
        if_c            or      outData, Hex200     'output 10

                        add     ch10level, #1   wc
        'if_c            or      outa, Hex800         'pin 11
        if_c            or      outData, Hex400     'output 11

                        add     ch11level, #1   wc
        'if_c            or      outa, Hex1000         'pin 12
        if_c            or      outData, Hex800     'output 12

                        mov   outData,#056
                        mov  listout,outData   ' to the output hold register
                        mov  dmrOuts,par
                        add  dmrOuts , #20  '  the location of Shift data in hub Ram

                        WRLong listout,dmrOuts   'put listout into shiftdata location

                         'mov  dmrOuts,par
                        ' add  dmrOuts , #16
                        ' RDLONG sftaddr,dmrOuts  ' read the shiftdata addr from Hub RAM
                        ' WRLong listout,sftaddr   'write outputs to shiftdata register

                         waitcnt timer, period    ' let this period finish

                        djnz    idx, #dimloop      '

                        jmp     #waitzc1          ' next cycle
'------------------------------------------------------------------------------


' ----------------------------------------------------------------------------
listout           res     1                                     ' channel levels
outData           res     1
sftaddr           res     1
dmrOuts           res     1                  'the dimmer output moved to ex
HxFFFF_FF00       long  $FFFF_FF00
'OutMask12         long  $0000_1FFE    '%0000_0000_0000_0000 _0001_1111_1111 1110
OutMask12         long  $000f_0000    '%0000_0000_1111 _0000_0000_0001 1110
Hex200            long  $0000_0200           'constants  pin masks
Hex400            long  $0000_0400
Hex800            long  $0000_0800
Hex1000           long  $0000_1000            'pin 11
Hex2000           long  $0000_2000            'pin 12
Hex4000           long  $0000_4000
Hex8000           long  $0000_8000
Hex10000          long  $0001_0000
Hex20000          long  $0002_0000
Hex40000          long  $0004_0000
Hex80000          long  $0008_0000
Hex100000          long  $0010_0000
Hex200000          long  $0020_0000
Hex400000          long  $0040_0000
Hex800000          long  $0080_0000
Hex1M              long  $0100_0000
Hex2M              long  $0200_0000
Hex4M              long  $0400_0000
Hex8M              long  $0800_0000
Hex10M             long  $1000_0000
Hex20M             long  $2000_0000
Hex40M             long  $4000_0000
Hex80M             long  $8000_0000


ch0mask           res     1                   ' pin mask for channel 0
zcmask            res     1                  ' pin mask for zero cross
hubch0            res     1                  ' hub address of brightness array
hubhc             res     1                   ' hub address of hctix
ternal
period            res     1                  ' 1/256th of half-cycle
timer             res     1                  ' for period timing
         'channel levels


ch0level          res     1                  ' channel levels
ch1level          res     1
ch2level          res     1
ch3level          res     1
ch4level          res     1                   ' channel levels
ch5level          res     1
ch6level          res     1
ch7level          res     1
ch8level          res     1                   ' channel levels
ch9level          res     1
ch10level         res     1
ch11level         res     1
                             ' channel levels
ch12level          res     1                  ' channel levels
ch13level          res     1
ch14level          res     1
ch15level          res     1
ch16level          res     1                   ' channel levels
ch17level          res     1
ch18level          res     1
ch19level          res     1
ch20level          res     1                   ' channel levels
ch21level          res     1
ch22level          res     1
ch23level          res     1

ch24level          res     1                  ' channel levels
ch25level          res     1
ch26level          res     1
ch27level          res     1
ch28level          res     1                   ' channel levels
ch29level          res     1
ch30level          res     1
ch31level          res     1

idx               res     1
t1                res     1
t2                res     1

                  fit     496                       'fit within Cog memory


DAT
playdk  long 985671234,256913847,123789564,678912543,789132456,891345267,918345672
        long 123456789,234567891,345678912,456789123,567891234,678912345,789123456,891234567,912345678
        long 134567892,345678921,456789213,567892134,678921345,789213456,892134567,921345678
        long 365789142

{{

  MIT License

  Permission is hereby granted, free of charge, to any person obtaining a copy of this
  software and associated documentation files (the "Software"), to deal in the Software
  without restriction, including without limitation the rights to use, copy, modify,
  merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  permit persons to whom the Software is furnished to do so, subject to the following
  conditions:

  The above copyright notice and this permission notice shall be included in all copies
  or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

}}