''************************************
''*  Full-Duplex Serial Driver v1.1  *
''*  (C) 2006 Parallax, Inc.         *
''************************************
''
''
''  Added Notes from: Mike Green, lifted from: http://forums.parallax.com/forums/default.aspx?f=25
''
''  "FullDuplexSerial" is a full duplex serial driver. It uses only one cog to both transmit and receive.
''  You only need to call the start routine once to set up both directions. To use pin 0 for transmit and
''  pin 1 for receive at 9600 Baud you'd call "serial.start(1,0,%0000,9600)". That implies Rx not inverted,
''  Tx not inverted, not open drain on transmit, and no ignore echo on receive. This assumes that you declare
''  'OBJ serial : "FullDuplexSerial"'.
''
''  The return value is true if the driver was started ok, false if there were no free cogs available (rarely happens).
''
''  FullDuplexSerial is intended to provide a buffered high speed serial communications channel in both directions
''  at once using a single cog.
''
''  The actual UART function in the FullDuplexSerial object resides in a cog (for each full duplex channel). This
''  does the actual "bit-banging" and does the manipulation of the I/O pins. It communicates with the "interface"
''  routines written in SPIN by means of transmit and receive buffers declared in the FullDuplexSerial object.
''  The interface routines (like .tx, .rx, .rxcheck) can be called from any cog running SPIN although, if you try
''  to receive or transmit from more than one cog at a time, you'll get into trouble since both cogs will try to
''  put data into or get data out of the same buffer at the same time. The fix for this is to use the semaphores
''  (LOCKxxx). It would be unusual to have to do this. Normally, only one cog would make use of any one full duplex
''  channel.
''
''  The FullDuplexSerial routines should work to at least 384 kB
''
''
VAR

  long  cog                     'cog flag/id

  long  rx_head                 '9 contiguous longs
  long  rx_tail
  long  tx_head
  long  tx_tail
  long  rx_pin
  long  tx_pin
  long  rxtx_mode
  long  bit_ticks
  long  buffer_ptr


  ' transmit and receive buffers
  ' buffers need to be a power of 2; ie: 16 32 64 128 256 512
  ' Note: looks like the maximum size of the buffer can only be 512 bytes.

  byte  rx_buffer[256]                                     ' <----------- Change Buffer Size Here
  byte  tx_buffer[256]                                     ' <----------- Change Buffer Size Here


PUB start(rxpin, txpin, mode, baudrate) : okay

'' Start serial driver - starts a cog
'' returns false if no cog available
''
'' mode bit 0 = invert rx
'' mode bit 1 = invert tx
'' mode bit 2 = open-drain/source tx
'' mode bit 3 = ignore tx echo on rx

  stop                                        ' stop stops any existing running serial driver if say you reinitialized
                                              ' your program without previously stopping it.

  longfill(@rx_head, 0, 4)                    ' The longfill initializes the first 4 longs to zero
                                              ' (rx_head through tx_tail)

  longmove(@rx_pin, @rxpin, 3)                ' The longmove copies the 4 parameters to start to the next 4 longs in
                                              ' the table (rx_pin through bit_ticks)

  bit_ticks := clkfreq / baudrate             ' The assignment to bit_ticks computes the number of clock ticks for
                                              ' the Baud requested.

  buffer_ptr := @rx_buffer                    ' The assignment to buffer_ptr passes the address
                                              ' of the receive buffer (and the transmit buffer XX bytes further).

  okay := cog := cognew(@entry, @rx_head) + 1 ' The cognew starts the assembly driver and passes to it the starting
                                              ' address of this whole table which it uses to refer to the various
                                              ' items in the table (rx_head through buffer_ptr).


PUB stop

'' Stop serial driver - frees a cog

  if cog
    cogstop(cog~ - 1)
  'longfill(@rx_head, 0, 9)


PUB rxflush

'' Flush receive buffer

  repeat while rxcheck => 0


PUB rxcheck : rxbyte

'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte

  rxbyte--
  if rx_tail <> rx_head
    rxbyte := rx_buffer[rx_tail]
    rx_tail := (rx_tail + 1) & $FF                          ' <----------- Change Buffer Size Here


PUB rxtime(ms) : rxbyte | t

'' Wait ms milliseconds for a byte to be received
'' returns -1 if no byte received, $00..$FF if byte

  t := cnt
  repeat until (rxbyte := rxcheck) => 0 or (cnt - t) / (clkfreq / 1000) > ms


PUB rx : rxbyte

'' Receive byte (may wait for byte)
'' returns $00..$FF

  repeat while (rxbyte := rxcheck) < 0


PUB tx(txbyte)

'' Send byte (may wait for room in buffer)

  repeat until (tx_tail <> (tx_head + 1) & $FF)             ' <----------- Change Buffer Size Here
  tx_buffer[tx_head] := txbyte
  tx_head := (tx_head + 1) & $FF                            ' <----------- Change Buffer Size Here

  if rxtx_mode & %1000
    rx


PUB str(stringptr)

'' Send string

  repeat strsize(stringptr)
    tx(byte[stringptr++])

PUB dec(value) | i

'' Print a decimal number

  if value < 0
    -value
    tx("-")

  i := 1_000_000_000

  repeat 10
    if value => i
      tx(value / i + "0")
      value //= i
      result~~
    elseif result or i == 1
      tx("0")
    i /= 10


PUB hex(value, digits)

'' Print a hexadecimal number

  value <<= (8 - digits) << 2
  repeat digits
    tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))


PUB bin(value, digits)

'' Print a binary number

  value <<= 32 - digits
  repeat digits
    tx((value <-= 1) & 1 + "0")


DAT

'***********************************
'* Assembly language serial driver *
'***********************************

                        org
'
'
' Entry
'
entry                   mov     t1,par                'get structure address
                        add     t1,#4 << 2            'skip past heads and tails

                        rdlong  t2,t1                 'get rx_pin
                        mov     rxmask,#1
                        shl     rxmask,t2

                        add     t1,#4                 'get tx_pin
                        rdlong  t2,t1
                        mov     txmask,#1
                        shl     txmask,t2

                        add     t1,#4                 'get rxtx_mode
                        rdlong  rxtxmode,t1

                        add     t1,#4                 'get bit_ticks
                        rdlong  bitticks,t1

                        add     t1,#4                 'get buffer_ptr
                        rdlong  rxbuff,t1
                        mov     txbuff,rxbuff
                        add     txbuff,#256                ' <----------- Change Buffer Size Here

                        test    rxtxmode,#%100  wz    'init tx pin according to mode
                        test    rxtxmode,#%010  wc
        if_z_ne_c       or      outa,txmask
        if_z            or      dira,txmask

                        mov     txcode,#transmit      'initialize ping-pong multitasking
'
'
' Receive
'
receive                 jmpret  rxcode,txcode         'run a chunk of transmit code, then return

                        test    rxtxmode,#%001  wz    'wait for start bit on rx pin
                        test    rxmask,ina      wc
        if_z_eq_c       jmp     #receive

                        mov     rxbits,#9             'ready to receive byte
                        mov     rxcnt,bitticks
                        shr     rxcnt,#1
                        add     rxcnt,cnt

:bit                    add     rxcnt,bitticks        'ready next bit period

:wait                   jmpret  rxcode,txcode         'run a chuck of transmit code, then return

                        mov     t1,rxcnt              'check if bit receive period done
                        sub     t1,cnt
                        cmps    t1,#0           wc
        if_nc           jmp     #:wait

                        test    rxmask,ina      wc    'receive bit on rx pin
                        rcr     rxdata,#1
                        djnz    rxbits,#:bit

                        shr     rxdata,#32-9          'justify and trim received byte
                        and     rxdata,#$FF
                        test    rxtxmode,#%001  wz    'if rx inverted, invert byte
        if_nz           xor     rxdata,#$FF

                        rdlong  t2,par                'save received byte and inc head
                        add     t2,rxbuff
                        wrbyte  rxdata,t2
                        sub     t2,rxbuff
                        add     t2,#1
                        and     t2,#$FF                   ' <----------- Change Buffer Size Here
                        wrlong  t2,par

                        jmp     #receive              'byte done, receive next byte
'
'
' Transmit
'
transmit                jmpret  txcode,rxcode         'run a chunk of receive code, then return

                        mov     t1,par                'check for head <> tail
                        add     t1,#2 << 2
                        rdlong  t2,t1
                        add     t1,#1 << 2
                        rdlong  t3,t1
                        cmp     t2,t3           wz
        if_z            jmp     #transmit

                        add     t3,txbuff             'get byte and inc tail
                        rdbyte  txdata,t3
                        sub     t3,txbuff
                        add     t3,#1
                        and     t3,#$FF                    ' <----------- Change Buffer Size Here
                        wrlong  t3,t1

                        or      txdata,#$100          'ready byte to transmit
                        shl     txdata,#2
                        or      txdata,#1
                        mov     txbits,#11
                        mov     txcnt,cnt

:bit                    test    rxtxmode,#%100  wz    'output bit on tx pin according to mode
                        test    rxtxmode,#%010  wc
        if_z_and_c      xor     txdata,#1
                        shr     txdata,#1       wc
        if_z            muxc    outa,txmask
        if_nz           muxnc   dira,txmask
                        add     txcnt,bitticks        'ready next cnt

:wait                   jmpret  txcode,rxcode         'run a chunk of receive code, then return

                        mov     t1,txcnt              'check if bit transmit period done
                        sub     t1,cnt
                        cmps    t1,#0           wc
        if_nc           jmp     #:wait

                        djnz    txbits,#:bit          'another bit to transmit?

                        jmp     #transmit             'byte done, transmit next byte
'
'
' Uninitialized data
'
t1                      res     1
t2                      res     1
t3                      res     1

rxtxmode                res     1
bitticks                res     1

rxmask                  res     1
rxbuff                  res     1
rxdata                  res     1
rxbits                  res     1
rxcnt                   res     1
rxcode                  res     1

txmask                  res     1
txbuff                  res     1
txdata                  res     1
txbits                  res     1
txcnt                   res     1
txcode                  res     1