spinix-hive/lerner/cserial.spin

585 lines
21 KiB
Plaintext

'******************************************************************************
' Serial Driver for the C Function Library in Spin
' Author: Dave Hein
' Copyright (c) 2010
' See end of file for terms of use.
'******************************************************************************
'******************************************************************************
' Revison History
' v1.0 - 4/2/2010 First official release
'******************************************************************************
{{
This is a modified version of Ghip Gracey's Full-Duplex Serial Driver. This
serial driver has the following new features:
- Multiple serial ports may be started from any object or cog
- Any serial port is accessable from any object or cog using a device handle
- Transmit and receiver buffers can be different sizes
- Buffer sizes are defined by calling parameters
- Mode bit 4 enables the use of a lock to make the transmit multi-cog safe
The original FullDuplexSerial routines are retained for compatibility. A
handle is maintained locally so that the caller does not have to provide the
buffers or handle for the first serial port.
The enhanced methods use a handle, which is a pointer to a memory buffer that
contains the serial port's state information and transmit and receive buffers.
The size of the memory buffer is equal to header_size + rxsize + txsize in
bytes. The buffer must be long aligned. The transmit and receive buffer
sizes must be a power of 2. They can range anywhere from a value of 2 up to
the size of the available memory.
}}
CON
' Data structure byte offsets
' long variables
tm_seconds = 0
tm_clkticks = 4
bit_ticks = 8
' word variables
rx_head = 12
rx_tail = 14
tx_head = 16
tx_tail = 18
rx_buffer = 20
tx_buffer = 22
rx_mask = 24
tx_mask = 26
' byte variables
cog = 28
lock = 29
rx_pin = 30
tx_pin = 31
rxtx_mode = 32
header_size = 36
' Data structure storage for the default serial port
DAT
handle1 long 0
data_struct long 0[(header_size+16+16)/4]
''
''*****************************************************************************
''Original FullDuplexSerial routines for compatibility
''*****************************************************************************
PUB rxflush
'' Flush receive buffer
rxflush1(handle1)
PUB rxcheck
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
return rxcheck1(handle1)
PUB rxtime(ms)
'' Wait ms milliseconds for a byte to be received
'' returns -1 if no byte received, $00..$FF if byte
return rxtime1(handle1, ms)
PUB rx
'' Receive byte (may wait for byte)
'' returns $00..$FF
return rx1(handle1)
PUB tx(txbyte)
'' Send byte (may wait for room in buffer)
tx1(handle1, txbyte)
PUB str(stringptr)
'' Send string
str1(handle1, stringptr)
PUB dec(value)
'' Print a decimal number
dec1(handle1, value)
PUB hex(value, digits)
'' Print a hexadecimal number
hex1(handle1, value, digits)
PUB bin(value, digits)
'' Print a binary number
bin1(handle1, value, digits)
''
''*****************************************************************************
''Enhanced routines
''*****************************************************************************
PUB kbhit
return kbhit1(handle1)
PUB kbhit1(handle)
return word[handle+rx_tail] <> word[handle+rx_head]
PUB rxflush1(handle)
'' Flush receive buffer
word[handle+rx_tail] := word[handle+rx_head]
PUB rxcheck1(handle) : rxbyte | rx_tail1, rx_buffer1
'' Check if byte received (never waits)
'' returns -1 if no byte received, $00..$FF if byte
rxbyte--
rx_tail1 := word[handle+rx_tail]
if rx_tail1 <> word[handle+rx_head]
rx_buffer1 := word[handle+rx_buffer]
rxbyte := byte[rx_buffer1+rx_tail1]
word[handle+rx_tail] := (rx_tail1 + 1) & word[handle+rx_mask]
PUB rxtime1(handle, 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 := rxcheck1(handle)) => 0 or (cnt - t) / (clkfreq / 1000) > ms
PUB rx1(handle) : rxbyte
'' Receive byte (may wait for byte)
'' returns $00..$FF
repeat while (rxbyte := rxcheck1(handle)) < 0
PRI txchar(handle, txbyte) | tx_mask1, tx_buffer1, tx_head1, tx_head2
'' Send byte (may wait for room in buffer)
tx_mask1 := word[handle+tx_mask]
tx_buffer1 := word[handle+tx_buffer]
tx_head1 := word[handle+tx_head]
tx_head2 := (tx_head1 + 1) & tx_mask1
repeat until (word[handle+tx_tail] <> tx_head2)
{
if (byte[handle+rxtx_mode] & %100000) and (txbyte == 10)
txbyte := 13
}
byte[tx_buffer1+tx_head1] := txbyte
word[handle+tx_head] := tx_head2
if byte[handle+rxtx_mode] & %1000
rx1(handle)
PUB tx1(handle, txbyte) | uselock, locknum
'' Send byte - Use lock if mode bit 4 is set
uselock := byte[handle+rxtx_mode] & %10000
if (uselock)
locknum := byte[handle+lock] - 1
repeat until not lockset(locknum)
txchar(handle, txbyte)
if (uselock)
lockclr(locknum)
PUB str1(handle, stringptr) | uselock, locknum, value
'' Send string - Use lock if mode bit 4 is set
uselock := byte[handle+rxtx_mode] & %10000
if (uselock)
locknum := byte[handle+lock] - 1
repeat until not lockset(locknum)
repeat strsize(stringptr)
txchar(handle, byte[stringptr++])
if (uselock)
lockclr(locknum)
PUB dec1(handle, value) | i
'' Print a decimal number
if (value < 0)
tx1(handle, "-")
if (value == NEGX)
tx1(handle, "2")
value += 2_000_000_000
value := -value
i := 1_000_000_000
repeat while (i > value and i > 1)
i /= 10
repeat while (i > 0)
tx1(handle, value/i + "0")
value //= i
i /= 10
PUB hex1(handle, value, digits)
'' Print a hexadecimal number
value <<= (8 - digits) << 2
repeat digits
tx(lookupz((value <-= 4) & $F : "0".."9", "A".."F"))
PUB bin1(handle, value, digits)
'' Print a binary number
value <<= 32 - digits
repeat digits
tx((value <-= 1) & 1 + "0")
PUB gethandle1
'' Get the local handle
return handle1
PUB dbprintf0(fmtstr)
dbprintf(fmtstr, @fmtstr)
PUB dbprintf1(fmtstr, arg1)
dbprintf(fmtstr, @arg1)
PUB dbprintf2(fmtstr, arg1, arg2)
dbprintf(fmtstr, @arg1)
PUB dbprintf3(fmtstr, arg1, arg2, arg3)
dbprintf(fmtstr, @arg1)
PUB dbprintf4(fmtstr, arg1, arg2, arg3, arg4)
dbprintf(fmtstr, @arg1)
PUB dbprintf5(fmtstr, arg1, arg2, arg3, arg4, arg5)
dbprintf(fmtstr, @arg1)
PUB dbprintf6(fmtstr, arg1, arg2, arg3, arg4, arg5, arg6)
dbprintf(fmtstr, @arg1)
PUB dbprintf(fmtstr, arglist) | arg, val, digits
arg := long[arglist]
arglist += 4
repeat while (val := byte[fmtstr++])
if (val == "%")
digits := 0
repeat
case (val := byte[fmtstr++])
"d" : dec(arg)
"x" :
ifnot digits
digits := 8
hex(arg, digits)
"b" :
ifnot digits
digits := 32
bin(arg, digits)
"s" : str(arg)
"0".."9":
digits := (digits * 10) + val - "0"
next
0 : return
other: tx(val)
quit
arg := long[arglist]
arglist += 4
elseif (val == "\")
case (val := byte[fmtstr++])
"n" : tx(13)
"r" : tx(10)
0 : return
other: tx(val)
else
tx(val)
PUB settime(seconds, clkticks) | handle
handle := handle1
long[handle + tm_seconds] := seconds
long[handle + tm_clkticks] := clkticks | $80000000
repeat while long[handle + tm_clkticks] & $c0000000
PUB gettime(pclkticks) | handle
handle := handle1
long[handle + tm_clkticks] := $40000000
repeat while long[handle + tm_clkticks] & $c0000000
long[pclkticks] := long[handle + tm_clkticks]
return long[handle + tm_seconds]
''
''*****************************************************************************
''Original FullDuplexSerial routines for compatibility
''*****************************************************************************
PUB start(rxpin, txpin, mode, baudrate)
'' 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
'' mode bit 4 = use lock
'' mode bit 5 = convert LF to CR on tx
return start1(@data_struct, rxpin, txpin, mode, baudrate, 256, 16)
''
''*****************************************************************************
''Enhanced routines
''*****************************************************************************
PUB start1(handle, rxpin, txpin, mode, baudrate, rxsize, txsize) : 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
'' mode bit 4 = use lock
'' mode bit 5 = convert LF to CR on tx
if (handle1)
if (handle1 == handle)
stop1(handle)
else
handle1 := handle
wordfill(handle+rx_head, 0, 4)
long[handle+tm_clkticks] := clkfreq + cnt
long[handle+tm_seconds] := 0
byte[handle+rx_pin] := rxpin
byte[handle+tx_pin] := txpin
byte[handle+rxtx_mode] := mode
long[handle+bit_ticks] := clkfreq / baudrate
word[handle+rx_buffer] := handle + header_size
word[handle+tx_buffer] := handle + header_size + rxsize
word[handle+rx_mask] := rxsize - 1
word[handle+tx_mask] := txsize - 1
if (mode & %10000)
okay := byte[handle+lock] := locknew + 1
if (okay == 0)
return 0
okay := byte[handle+cog] := cognew(@entry, handle) + 1
PUB stop1(handle) | cog1
'' Stop serial driver - frees a cog
cog1 := byte[handle+cog]
if cog1
cogstop(cog1 - 1)
longfill(handle, 0, header_size >> 2)
DAT
'***********************************
'* Assembly language serial driver *
'***********************************
org
'
'
' Entry
'
entry mov t1, par
add t1, #rx_pin
rdbyte t2,t1 'get rx_pin
mov rxbitmask,#1
shl rxbitmask,t2
add t1,#1 'get tx_pin
rdbyte t2,t1
mov txbitmask,#1
shl txbitmask,t2
add t1,#1 'get rxtx_mode
rdbyte rxtxmode,t1
mov t1, par
add t1, #bit_ticks 'get bit_ticks
rdlong bitticks,t1
mov t1, par
add t1, #tm_seconds 'get tmseconds
rdlong tmseconds,t1
mov t1, par
add t1, #tm_clkticks 'get tmclkticks
rdlong tmclkticks, #0 'get clkfreq
rdlong tmclkticks,t1
mov t1, par
add t1, #rx_buffer 'get rx_buffer ptr
rdword rxbuff,t1
add t1,#2 'get tx_buffer ptr
rdword txbuff,t1
add t1,#2 'get rx_mask
rdword rxmask,t1
add t1,#2 'get tx_mask
rdword txmask,t1
test rxtxmode,#%100 wz 'init tx pin according to mode
test rxtxmode,#%010 wc
if_z_ne_c or outa,txbitmask
if_z or dira,txbitmask
mov txcode,#transmit 'initialize multitasking addresses
mov clcode,#clock
'
'
' 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 rxbitmask,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 rxbitmask,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
mov t1, par
add t1, #rx_head
rdword t2, t1 'save received byte and inc head
add t2,rxbuff
wrbyte rxdata,t2
sub t2,rxbuff
add t2,#1
and t2,rxmask
wrword t2,t1
jmp #receive 'byte done, receive next byte
'
'
' Transmit
'
transmit jmpret txcode,clcode 'run a chunk of clock code, then return
mov t1,par 'check for head <> tail
add t1,#tx_head
rdword t2,t1
add t1,#2
rdword 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,txmask
wrword 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,txbitmask
if_nz muxnc dira,txbitmask
add txcnt,bitticks 'ready next cnt
:wait jmpret txcode,clcode 'run a chunk of clock 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
clock jmpret clcode,rxcode 'run the receive task
mov t2, par
add t2, #tm_clkticks
rdlong t1, t2
shl t1, #1 wc
if_c jmp #setclock
shl t1, #1 wc
if_c jmp #getclock
mov t1, cnt
sub t1, tmclkticks
shl t1, #1 wc
if_c jmp #clock
add tmseconds, #1
rdlong t1, #0
add tmclkticks, t1
jmp #clock
setclock shl t1, #3
shr t1, #4
rdlong tmclkticks, #0
add tmclkticks, cnt
sub tmclkticks, t1
mov t2, par
add t2, #tm_seconds
rdlong tmseconds, t2
mov t2, par
add t2, #tm_clkticks
wrlong t1, t2
jmp #clock
getclock rdlong t1, #0
mov t2, cnt
add t2, t1
sub t2, tmclkticks
mov t3, par
add t3, #tm_seconds
wrlong tmseconds, t3
mov t3, par
add t3, #tm_clkticks
wrlong t2, t3
jmp #clock
'
'
' Uninitialized data
'
t1 res 1
t2 res 1
t3 res 1
rxtxmode res 1
bitticks res 1
rxbitmask res 1
rxbuff res 1
rxdata res 1
rxbits res 1
rxcnt res 1
rxcode res 1
rxmask res 1
txbitmask res 1
txbuff res 1
txdata res 1
txbits res 1
txcnt res 1
txcode res 1
txmask res 1
tmclkticks res 1
tmseconds res 1
clcode res 1
fit $100
{{
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ TERMS OF USE: 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 NONINFRINGEMENT. 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. │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
}}