'' ================================================================================================= '' '' File....... Multiport_serial.spin2 '' Purpose.... Create multiple serial ports using a single cog '' Buffered serial communications using smart pins '' -- mostly matches jm_FullDuplexSerial.spin2 '' -- does NOT support half-duplex communications using shared RX/TX pin '' Authors.... Bob Sweeney '' -- based on work by Jon McPhalen '' -- based on work by Chip Gracey '' -- see below for terms of use '' E-mail..... rsdesigns1@att.net '' Started.... '' Updated.... 06 Jan 2021 '' '' ================================================================================================= {{ Run up to 8 serial ports at a time. Run 'AddPort' prior to 'Start' to configure smart pins. AddPort return value is the portID. Code is based on jm_fullduplexserial.spin2. More than 8 ports can be run by adjusting the MAX_PORT value and pasm array variable sizes to accomodate more ports Initial version does not contain all methods in jm_fullduplexserial.spin2 in order to simplify for intial testing purposes Note: Buffer size no longer has to be power-of-2 integer. The smart pin uarts use a 16-bit value for baud timing which can limit low baud rates for some system frequencies -- beware of these limits when connecting to older devices. Baud 20MHz 40MHz 80MHz 100MHz 200MHz 300MHz ------ ----- ----- ----- ------ ------ ------ 300 No No No No No No 600 Yes No No No No No 1200 Yes Yes No No No No 2400 Yes Yes Yes Yes No No 4800 Yes Yes Yes Yes Yes Yes }} con { timing } CLK_FREQ = 300_000_000 ' system freq as a constant ' MS_001 = CLK_FREQ / 1_000 ' ticks in 1ms ' US_001 = CLK_FREQ / 1_000_000 ' ticks in 1us BR_TERM = 115_200 ' terminal baud rate _clkfreq = CLK_FREQ ' set system clock con { fixed io pins } RX1 = 63 { I } ' programming / debug TX1 = 62 { O } SF_CS = 61 { O } ' serial flash SF_SCK = 60 { O } SF_SDO = 59 { O } SF_SDI = 58 { I } con { pst formatting } HOME = 1 CRSR_XY = 2 CRSR_LF = 3 CRSR_RT = 4 CRSR_UP = 5 CRSR_DN = 6 BELL = 7 BKSP = 8 TAB = 9 LF = 10 CLR_EOL = 11 CLR_DN = 12 CR = 13 CRSR_X = 14 CRSR_Y = 15 CLS = 16 con ' serial specific constants BUF_SIZE = 64 ' size of rx and tx buffers MAX_PORT = 8 ' set to number of ports to be used (1-8) ' SERIAL1 = 0 ' example using named ports vs numbers var long num_ports ' total number of open ports (0-7) long rxpins[MAX_PORT] ' rx pin array - cog rxbuff long txpins[MAX_PORT] ' tx pin array - cog txbuff long rxhub[MAX_PORT] ' cog p_rxbuf - ptr to rxbuf hub address long txhub[MAX_PORT] ' cog p_txbuf - ptr to txbuf hub address long p_rxhd[MAX_PORT] ' cog ptr to rxhead[] hub address start long p_txhd[MAX_PORT] ' cog ptr to txhead[] hub address start long p_txtl[MAX_PORT] ' cog ptr to txtail[] hub address start long rxhead[MAX_PORT] ' rx/tx head and tail arrays long rxtail[MAX_PORT] long txhead[MAX_PORT] ' long txtail[MAX_PORT] ' long txdelay[MAX_PORT] ' port baud rate long cog ' cog flag/id byte rxbuf[BUF_SIZE * MAX_PORT] ' buffers - set MAX_PORT to actual open ports byte txbuf[BUF_SIZE * MAX_PORT] byte pbuf[80] ' padded strings pub main()| t ' testing setup only t := addport(5, 6, %0000, BR_TERM) ' 1st open port t := addport(8, 9, %0000, BR_TERM) ' 2nd open port t := addport(11, 12, %0000, BR_TERM) ' 3rd open port start() txtest() pub txtest() | i ' testing object i := 100 repeat tx(0, i) ' output port 0 tx(1, i+15) ' output port 1 i++ if i == 1000 i := 0 pub null() '' This is not a top-level object pub addport(rxp, txp, mode, baud) : result | baudcfg, spmode, tdelay '' add ports - call before start. miniumum 1 port required '' load data arrays and setup smartpins '' does not check for port duplication '' run addport at least once before calling start method '' returns portid (0 to MAX_PORT-1), -1 on error '' -- rxp... receive pin (-1 if not used) '' -- txp... transmit pin (-1 if not used) '' -- mode.... %0xx1 = invert rx '' %0x1x = invert tx '' %01xx = open-drain/open-source tx if (rxp == txp) ' pins must be unique result := -1 '' check if pin used by any other ports if num_ports > (MAX_PORT-1) ' port in range result := -1 ' error, initializing too many ports abort else result := num_ports ' return port number (0-7), -1 if error txdelay[num_ports] := clkfreq / baud * 11 ' tix to transmit one byte baudcfg := muldiv64(clkfreq, $1_0000, baud) & $FFFFFC00 ' set bit timing baudcfg |= (8-1) ' set bits (8) rxpins[num_ports] := rxp ' save rx pin if rxpins[num_ports] >= 0 ' configure rx pin if used spmode := P_ASYNC_RX if (mode.[0]) spmode |= P_INVERT_IN pinstart(rxpin, spmode, baudcfg, 0) txpins[num_ports] := txp ' save tx pin if txpins[num_ports] >= 0 ' configure tx pin if used spmode := P_ASYNC_TX | P_OE case mode.[2..1] %01 : spmode |= P_INVERT_OUTPUT %10 : spmode |= P_HIGH_FLOAT ' requires external pull-up %11 : spmode |= P_INVERT_OUTPUT | P_LOW_FLOAT ' requires external pull-down pinstart(txpin, spmode, baudcfg, 0) rxhub[num_ports] := @rxbuf[num_ports * BUF_SIZE] ' ptr to hub buffer address txhub[num_ports] := @txbuf[num_ports * BUF_SIZE] p_rxhd[num_ports] := @rxhead[num_ports] ' ptr to hub addresses p_txhd[num_ports] := @txhead[num_ports] p_txtl[num_ports] := @txtail[num_ports] num_ports++ ' increment port counter pub start() : result '' Start new cog stop() ' save hub addresses of head and tail indexes for use in pasm cog := coginit(COGEXEC_NEW, @uart_mgr, @num_ports) + 1 ' start uart manager cog return cog pub stop() '' Stop serial driver '' -- frees a cog if driver was running if (cog) ' cog active? cogstop(cog-1) ' yes, shut it down cog := 0 ' and mark stopped pub rx(portval) : b '' Pulls byte from receive buffer if available '' -- will wait if buffer is empty repeat while rxtail[portval] == rxhead[portval] ' hold while buffer empty b := rxbuf[(portval * BUF_SIZE) + rxtail[portval]] ' get a byte if ++rxtail[portval] == BUF_SIZE ' update tail pointer rxtail[portval] := 0 pub rxcheck(portval) : b '' Pulls byte from receive buffer if available '' -- returns -1 if buffer is empty if rxtail[portval] <> rxhead[portval] ' something in buffer? b := rxbuf[(portval * BUF_SIZE) + rxtail[portval]] ' get a byte if ++rxtail[portval] == BUF_SIZE ' update tail pointer rxtail[portval] := 0 else b := -1 ' mark no byte available pub rxtime(portval, ms) : b | mstix, t '' Wait ms milliseconds for a byte to be received '' -- returns -1 if no byte received, $00..$FF if byte mstix := clkfreq / 1000 t := getct() repeat until ((b := rxcheck(portval)) >= 0) || (((getct()-t) / mstix) >= ms) pub rxtix(portval, tix) : b | t '' Waits tix clock ticks for a byte to be received '' -- returns -1 if no byte received t := getct() repeat until ((b := rxcheck(portval)) >= 0) || ((getct()-t) >= tix) pub available(portval) : count '' Returns # of bytes waiting in rx buffer if (rxtail[portval] <> rxhead[portval]) ' if byte(s) available count := rxhead[portval] - rxtail[portval] ' get count if (count < 0) count += BUF_SIZE ' fix for wrap around pub rxflush(portval) '' Flush receive buffer repeat while (rxcheck(portval) >= 0) pub tx(portval, b) | n '' Move byte into transmit buffer if room is available '' -- will wait if buffer is full repeat n := txhead[portval] - txtail[portval] ' bytes in buffer { debug("-------------") debug(udec(portval)) debug("Input: ", udec(b)) debug(udec(txhead[portval])) debug(udec(txtail[portval])) debug(udec(n))} if (n < 0) ' fix for index wrap-around n += BUF_SIZE if (n < BUF_SIZE-1) quit txbuf[(portval * BUF_SIZE) + txhead[portval]] := b ' move to buffer offset location ' debug(udec(txbuf[(portval * BUF_SIZE) + txhead[portval]])) ' debug(udec(b), dly(750)) if (++txhead[portval] == BUF_SIZE) ' update head pointer txhead[portval] := 0 pub txn(portval, b, n) '' Emit byte n times repeat n tx(portval, b) pub str(portval, p_str) '' Emit z-string at p_str repeat (strsize(p_str)) tx(portval, byte[p_str++]) pub substr(portval, p_str, len) | b '' Emit len characters of string at p_str '' -- aborts if end of string detected repeat len b := byte[p_str++] if (b > 0) tx(portval, b) else quit pub txflush(portval) '' Wait for transmit buffer to empty '' -- will delay one byte period after buffer is empty repeat until (txtail[portval] == txhead[portval]) ' let buffer empty waitct(getct() + txdelay[portval]) ' delay for last byte dat { smart pin uart/buffer manager } org uart_mgr ' read hub arrays into cog, get # of open ports ' to copy to cog ram rdlong portnum, ptra++ ' get # of open ports setq #MAX_PORT*7-1 ' block copy hub variables to cog rdlong rxpin, ptra sub portnum, #1 ' subtract 1 to make loop count right uart_main ' loop through each port one at a time to get rx, tx mov portctr, #0 ' initialize portctr = 0 .loop ' get offsets for array values ' debug("------------") ' debug(udec(portctr)) alts portctr, #rxpin ' add portctr offset to rxpin - rxpin[portctr] mov rxd, 0 ' copy rxpin to rxd ' debug(udec(rxd)) alts portctr, #txpin ' add portctr offset to txpin - txpin[portctr] mov txd, 0 ' copy txpin to txd ' debug(udec(txd)) ' rxpin available, jump to rx_serial or tx_serial testb rxd, #31 wc ' rx pin in use? -test for -1 when no pin saved if_nc call #rx_serial testb txd, #31 wc ' tx in use? -test for -1 if_nc call #tx_serial ' increment counter to next port. if portctr = ' open ports, reset portctr to 0 and loop incmod portctr, portnum wc ' check if portctr = open ports if_nc jmp #.loop ' repeat loop for each open port jmp #uart_main rx_serial testp rxd wc ' anything waiting on smartpin? if_nc ret debug("------------") debug(udec(portctr)) debug(udec(rxd)) rdpin t3, rxd ' read new byte from smartpin shr t3, #24 ' align lsb debug("Rx Input: ",udec(t3)) alts portctr, #p_rxbuf ' get @rxhub[portctr] from hub mov t1, 0 ' t1 := @rxhub[portctr] debug(udec(#p_rxbuf)) debug(udec(t1)) alts portctr, #p_rxhead ' get rxhead[portctr] value from hub mov offset, 0 ' offset = rxhead[portctr] rdlong t2, offset ' read rxhead[portctr] from hub debug(udec(#p_rxhead)) debug(udec(offset)) debug(udec(t2)) add t1, t2 wrbyte t3, t1 ' rxbuf[rxhead] := t3 incmod t2, #(BUF_SIZE-1) ' update rxhead index _ret_ wrlong t2, offset ' write rxhead index back to hub tx_serial rdpin t1, txd wc ' check busy flag if_c ret ' abort if busy ' debug("---------------") ' debug(udec(portctr)) ' debug("Tx pin: ", udec_(txd)) alts portctr, #p_txhead ' get txhead value from hub mov offset, 0 ' offset = txhead[portctr] rdlong thead, offset ' thead = txhead[portctr] in hub memory ' debug(udec(#p_txhead)) ' debug(udec(offset)) ' debug("txhead: ", udec_(thead)) alts portctr, #p_txtail ' get txtail value from hub mov offset1, 0 ' offset1 = txtail[portctr] rdlong t2, offset1 ' t2 = txtail[portctr] in hub memory ' debug(udec(#p_txtail)) ' debug(udec(offset1)) ' debug("txtail: ", udec_(t2)) cmp thead, t2 wz ' byte(s) to tx? if_e ret alts portctr, #p_txbuf ' get txbuf[portctr] in hub memory mov thead, 0 ' t1 := txhub[portctr] add thead, t2 ' add tail index rdbyte t3, thead ' t3 := txhub[txtail] ' debug("txhub: ", udec_(t3)) wypin t3, txd ' load into sp uart incmod t2, #(BUF_SIZE-1) ' update tail index _ret_ wrlong t2, offset1 ' write tail index back to hub ' -------------------------------------------------------------------------------------------------- portnum long 1 ' total # ports rxpin long 0[8] ' rx pin array txpin long 0[8] ' tx pin array p_rxbuf long 0[8] ' ptr to rxhub p_txbuf long 0[8] ' ptr to txhub p_rxhead long 0[8] ' ptr to rxhead p_txhead long 0[8] ' ptr to txhead p_txtail long 0[8] ' prt to txtail portctr res 1 ' loop counter rxd res 1 ' rx pin txd res 1 ' tx pin rhead res 1 thead res 1 ttail res 1 offset res 1 offset1 res 1 t1 res 1 ' work vars t2 res 1 t3 res 1 fit 472 con { license } {{ 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 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. }}