Shop OBEX P1 Docs P2 Docs Learn Events
Creating a multi port serial object — Parallax Forums

Creating a multi port serial object

I have been working on a large hexapod robot for several years that currently uses 7 P1 processors (6 leg controllers and one master). After a while it become evident that the P1master was not fast enough to perform floating point trig calculations so I started to look for other options. Luckily the P2 is available so my quest has been to replicate or find replacements for the P1 Objects used in my code. The robot relies on serial communications between the master and the individual leg controllers for leg movements. At this point its using 2 copies of the P1 4 port serial communications object for a total of 8 separate serial lines (1 line for output from master to legs, 6 separate receive only lines from the legs to the master, and 1 line to the master via the prop plug for troubleshooting).
Looking over the serial options I thought JonnyMac's jm_serial.spin2 looked easiest to modify to allow multiple ports with its use of Smart Pins. I did a quick mod to allow 2 ports for testing. The code compiles with no errors but in testing I'm not getting any response at the PST terminal. As best I can tell the Smart Pins aren't being configured correctly in the addport routine but I'm not figuring out what I did wrong. I archived the demo (I used JonnyMac's serial demo also, commented out most of the code to reduce complexity until it works) and attached it along with a portion of the serial code.
con { fixed io pins }

  RX1     = 63  { I }                                           ' programming / debug
  TX1     = 62  { O }

  FS_CS   = 61  { O }                                           ' flash storage
  FS_CLK  = 60  { O }
  FS_MOSI = 59  { O }
  FS_MISO = 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

obj

  nstr : "jm_nstr"                                              ' number-to-string

var

  long  rxp0
  long  txp0
  long  rxp1
  long  txp1

  byte  pbuf[80]                                                ' padded strings


pub null()

  '' This is not a top level object


pub addport(port1, rxpin, txpin, baud) | bitmode

'' Start simple serial coms on rxpin and txpin at baud, can use different rx/tx based on selected port

  case port1
    0: longmove(@rxp0, @rxpin, 2)                                     ' save port0 pins
    1: longmove(@rxp1, @rxpin, 2)                                     ' save port1 pins
  bitmode := muldiv64(clkfreq, $1_0000, baud) & $FFFFFC00       ' set bit timing
  bitmode |= 7                                                  ' set bits (8)

  org
            fltl      rxpin                                 ' configure rx smart pin
            wrpin     ##P_ASYNC_RX, rxpin
            wxpin     bitmode, rxpin
            drvl      rxpin
            fltl      txpin                                 ' configure tx smart pin
            wrpin     ##(P_ASYNC_TX | P_OE), txpin
            wxpin     bitmode, txpin
            drvl      txpin
  end


pub rxflush(port1)

'' Clear serial input
  repeat
  while (rxcheck(port1) >= 0)


pub rxcheck(port1) : rxbyte | check

'' Check for serial input
'' -- returns -1 if nothing available

  rxbyte := -1
  case port1
    0:
      check := pinr(rxp0)
      if (check)
        rxbyte := rdpin(rxp0) >> 24
    1:
      check := pinr(rxp1)
      if (check)
        rxbyte := rdpin(rxp1) >> 24


pub rxtime(port1, 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(port1)) >= 0) or (((getct() - t) / mstix) > ms)


pub rx(port1) : rxbyte

'' Wait for serial input
'' -- blocks!
  repeat
    rxbyte := rxcheck(port1)
  until (rxbyte >= 0)


pub tx(port1, b)

'' Emit byte
  case port1
    0:
      wypin(txp0, b)
      txflush(port1)
    1:
      wypin(txp1, b)
      txflush(port1)


pub txn(port1, b, n)

'' Emit byte n times

  repeat n
    tx(port1, b)


pub txflush(port1) | check

'' Wait until last byte has finished
  case port1
    0:
      repeat
        check := pinr(txp0)
      while (check == 0)
    1:
      repeat
        check := pinr(txp1)
      while (check == 0)


pub str(port1, p_str)

'' Emit z-string at p_str

  repeat (strsize(p_str))
    tx(port1, byte[p_str++])

«13

Comments

  • Better to look at my jm_fullduplexserial.spin2 (latest attached). It also uses smart pins but has a uart manager cog that handles data in and out of the smart pins. If you look at it, you'll see that I wrote that background code to make it easier to handle multiple ports/buffers. It will take a bit of work, but I think it's a better place to start. I am going to do it myself when I get some free time and figure out the best way to add ports to an object that's already running.
  • Thanks for the heads up Jon, I'll take a look at it tonight and see if I can figure it out!
  • I've been having fun reviewing the FullDuplexSerial coding, smartpins really simplifies coding compared to the P1. I thought that I could use the P1 version of a multi-port serial code to help figure out how to run several ports at once but it was less helpful than I anticipated. I started doing some mods to JonnyMac's code, here is the setup portion of the code, you have to run the addport function at least once before starting the cog. I still have to go through the rest of the code making additional mods but thought I'd check with more experienced heads whether they think this approach would work for running 8 serial ports before I went too much further down this rabbit hole.
    var 
      long  cog                                                     ' cog flag/id
    
      long  rxp[8]
      long  txp[8]
      long  rxhub[8]                                                   ' hub address of rxbuf
      long  txhub[8]                                                   ' hub address of txbuf
      long  rxhead[8]                                                  ' rx head index
      long  rxtail[8]                                                  ' rx tail index
      long  txhead[8]                                                  ' tx head index
      long  txtail[8]                                                  ' tx tail index
      long  rxmask[8]
      long  txmask[8]
      long  txdelay[8]                                                 ' ticks to transmit one byte
      byte  rxbuf[BUF_SIZE]                                         ' buffers
      byte  txbuf[BUF_SIZE]
      byte  pbuf[80]                                                ' padded strings
    
    pub init()
    '' initialize code
      stop()
      
     
    pub addport(port, rxpin, txpin, mode, baud) : result | baudcfg, spmode 
    '' add additional ports - call before start. miniumum 1 port required
    '' does not check for port duplication yet
    '' Start simple serial coms on rxpin and txpin at baud
    '' run addport at least once before calling start method
    '' -- port.... port number up to maximum (0-7 in test)
    '' -- rxpin... receive pin (-1 if not used)
    '' -- txpin... transmit pin (-1 if not used)
    '' -- mode.... %0xx1 = invert rx
    ''             %0x1x = invert tx
    ''             %01xx = open-drain/open-source tx   
      if (rxpin == txpin)                                           ' pin must be unique
        return false
      '' check if pin used by any other ports  
      
      if cog OR (port > 1)                        'port in range and no cog running
        abort
      if rxpin <> -1
        long[@rxmask][port] := decod rxpin                'copy rxpin into rxmask
      if txpin <> -1
        long[@txmask][port] := decod txpin
      
      longmove(long[@rxp][port], @rxpin, 1)               'save pins
      longmove(long[@txp][port], @txpin, 1)
      
      long[@txdelay][port] := clkfreq / baud * 11                   ' tix to transmit one byte
      
      baudcfg := muldiv64(clkfreq, $1_0000, baud) & $FFFFFC00       ' set bit timing
      baudcfg |= (8-1)                                              ' set bits (8)
    
      if (long[@rxp][port] >= 0)                                                 ' configure rx pin if used
        spmode := P_ASYNC_RX
        if (mode.[0])
          spmode |= P_INVERT_IN
        pinstart(long[@rxp][port], spmode, baudcfg, 0)
    
      if (long[@txp][port] >= 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(long[@txp][port], spmode, baudcfg, 0) 
    
    
    pub start() : result
    
      cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1              ' start uart manager cog
    
      return cog
    
    
  • Got the spin code portion of the code completed so that it compiles with no errors. Now looking at the pasm part of the code. At this point I'm still going though JonnyMac's code to understand what it is doing on each line. So I have a couple of questions for those more pasm savy.
    The SETQ works with the RDLONG to block transfer 4 parameters into rdx, tdx, p_rxbuf and p_txbuf. Does this same mechanism work for transferring in the array of pin values stored in rxp[8] from the addport routine? Another question is how to determine how many ports have actually been assigned via the addports routine.
    I haven't found any examples of moving arrays into pasm so I don't know if its possible or just no one has needed to do it yet. I'll run some tests and see what happens also.
  • So far as I know, you can use the SETQ trick with RDLONG and WRLONG -- you just need to know the hub and cog addresses for the move.

    I still haven't completely sorted out how I'm going to do multi-port, but these are my thoughts:
    -- true mode only
    -- additional ports will use external buffers
    -- keep a byte flag of ports that are opened
    -- have a command mail box that can tell the pasm cog to add a port

    I'm hoping that the current code will allow for global cog variables to be used so that there's not too much redundancy.
  • A lot of this is an exercise to help me learn pasm (plus I need something along thes line for my robot communications!).
    When I started on the P1 the object exchange was pretty good and the code objects I needed so there wasn't any need to learn pasm. Now the P2 is still pretty young and the extensions to allow in-line pasm make learning it a whole lot more attractive. My biggest stumbling block is the lack of in-depth documentation at this point. I tend to learn best from written examples vs studying someone else's code and figuring out their thought process. Knowledge is coming along but slowly.
  • Been spending a lot of time reading people's PASM code and the available documentation to figure out how P2PASM works. Got a couple of things I want to verify that I am understanding the code correctly. The code snippets are coming from jm_fullduplexserial.spin2.
    var
    
      long  cog                                                     ' cog flag/id
    
      long  rxp                                                     ' rx smart pin
      long  txp                                                     ' tx smart pin
      long  rxhub                                                   ' hub address of rxbuf
      long  txhub                                                   ' hub address of txbuf
    
      long  rxhead                                                  ' rx head index
      long  rxtail                                                  ' rx tail index
      long  txhead                                                  ' tx head index
      long  txtail                                                  ' tx tail index
    
      long  txdelay                                                 ' ticks to transmit one byte
    
    Making sure I understand ptra pointer for passing info to PASM. In the VAR section, longs rxp and on are in a specific order so they can be transferred into PASM code
    cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1              ' start uart manager cog
    
    That is done here with the pointer to the memory address of rxp in coginit.
    uart_mgr        setq      #4-1                                  ' get 4 parameters from hub
                    rdlong    rxd, ptra
    ...
    rxd             res       1                                     ' receive pin
    txd             res       1                                     ' transmit pin
    p_rxbuf         res       1                                     ' pointer to rxbuf
    p_txbuf         res       1                                     ' pointer to txbuf
    
    Here the setq is used to get 4 parameters in hub memory starting at the address @rxp. Is the SetQ #4-1 just written like this for readability as it is starting with memory at location 0 through 3?
    rdlong is used to do a block copy of the 4 variables from hub memory over to cog memory. Since the reserved variables listed in the PASM section are in the same order as the VAR values, the copy keeps everything going to the right places.
    uart_main       testb     rxd, #31                      wc      ' rx in use?
    
    I'm not sure I fully understand testb. The description for testb is 'Test bit S[4:0] of D, write to C/Z. C/Z = D[S[4:0]].', I know this is a Verilog description but I haven't been able to decipher what that actually means. Rxd would have the output from the smartpin, is the smartpin output located in the 24-31 positions of the output long value. If that is the case then testb is checking for a stop bit from an incoming value?
    rx_serial       testp     rxd                           wc      ' anything waiting?
        if_nc       ret
    
    Here is testp that tests the smartpin. The description is 'Test IN bit of pin D[5:0], write to C/Z. C/Z = IN[D[5:0]].', not sure what it means by the IN bit reference. If C is empty then the code returns to the last location on the stack which should be rx_serial.

    Thanks for any help.
  • I'm not sure I fully understand testb.
    The testb instruction lets you examine a bit in a register. In my driver, you may only want to transmit. If that's the case, you set the RX pin to a negative #. Bit31 holds the sign bit; if it's 1, the number is negative. I check this and skip looking at the RX pin if it's not in use.
    Here is testp that tests the smartpin. The description is 'Test IN bit of pin D[5:0], write to C/Z. C/Z = IN[D[5:0]].', not sure what it means by the IN bit reference.
    When a smart pin is ready it will give you a 1 with testp. When this is the case you use rdpin to get the value. With an RX UART you have to shift that value right by 24 bits to realign for the user.
  • Thanks for that information, that helps fill that gap in my knowledge on testb and testp
  • I’ve been looking for examples of how to pass array variables into pasm and then read the values and haven’t found any so far. My attempts to use variables like ‘long port[7]’ haven’t worked out and I haven’t found examples on the forum so does any one know of some pasm code that uses arrays?
    Thanks for any help!
  • Pasm will need to know the start of your array. From there, it's pretty easy to move the whole array into the cog. Look for a thread on rotating an array 90 degrees -- that shows the process of copying an array in and out of the cog.
  • Cluso99Cluso99 Posts: 18,066
    jonnymacs jm_fullduplexserial has an example where he passes info using PTRA which is set with the coginit method. Then access the longs using PTRA[n].
  • I found the thread where the array was rotated. A question then, in pasm at the end of the code where the pasm variables are declared the method used that I see used the most is 'variablename res 1' which reserves one long. The thread shows this format 'variablename long 0[8]'. Isn't that the same as declaring 'variablename res 8'?
  • Cluso99Cluso99 Posts: 18,066
    You have to be careful when using the RES n syntax because
    * it only reserves space
    * Does not set it to zero
    * Beware of any following code - it is mostly at the end of a piece of pasm code so should be followed only by a separate piece of code - a new DAT, ORG, PUB, PRI, VAR

    IIRC there is a P1 write up in the manual describing its use.
  • Continuing my education on PASM I found an old video that Jeff Martin did on P1 pasm that was very informative and explained a lot about P1 assembly. I can see how the P2 pasm has been improved to make things easier vs the P1. I’ve also been going through the P1 manual on assembly as I understand a lot of P1 code is very similar to P2 assembly. I’m working on figuring out the right way to pass data into P2 assembly, here I’m trying to spell out how I understand the process to work, hopefully the pasm experts out there can point out any knowledge deficiencies.
    PTRA points to the hub memory location that is set when using COGINIT. In the case below it points to the memory location of global variable RXP.
    cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1
    
    In the assembly code the SETQ and RDLONG then block copy the next 4 long values starting at global variable RXP into the memory location reserved for RXD and the next 3 sequential memory locations reserved in assembly.
    uart_mgr        setq      #4-1                                  ' get 4 parameters from hub
                    rdlong    rxd, ptra
    
    RXD is single unassigned long reserved long value set at the end of the assembly code. So the effect of the process copies the values in the global variables into the 4 variables used by assembly.
    rxd             res       1                                     ' receive pin
    txd             res       1                                     ' transmit pin
    p_rxbuf         res       1                                     ' pointer to rxbuf
    p_txbuf         res       1                                     ' pointer to txbuf
    

    As Cluso99 states the RES command reserves sequential long spaces but doesn’t assign a value (this is done by the rdlong command) and it needs to be at the end of the pasm commands otherwise bad things can happen.

    Could the following sequence have been used instead of RES?
    rxd             Long       0                                     ' receive pin
    txd             Long       0                                     ' transmit pin
    p_rxbuf         Long       0                                     ' pointer to rxbuf
    p_txbuf         Long       0                                     ' pointer to txbuf
    
    Going further I saw this syntax used in the array rotation assembly code
    rxd     Long  0[8]
    
    Does this create an array of 8 elements all assigned a 0 value that are accessed like rxd[0] through rxd[7]?

    This leads me to the problem I am trying to figure out. If I have a global variable that is an array, how do get the array values into assembly? For instance I declare these global variables.
    var
    Long rxp[8]
    Long txp[8]
    
    When the cog is setup the same code is used as it just points to the start of the global variable address
    cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1
    
    In assembly the code to get the 16 values into assembly variables would be like this
    uart_mgr        setq      #16-1                                  ' get 16 parameters from hub
                    rdlong    rxd, ptra
    
    rxd             res       8                                     ' receive pin
    txd             res       8                                     ' transmit pin
    
    This is the simplified version of the code I have been using for testing but so far it doesn’t appear to work. If the syntax is correct then my problem is most likely my method I’m using to test the code. I asked a lot of questions here but I want to make sure I understand this data transfer process as it hasn’t always worked the way I thought it should.
  • Cluso99Cluso99 Posts: 18,066
    Yes, everything you said is correct and is a nice explanation :)

    It is safer to use long 0 rather than res 1 unless you’re short on hub space.
  • I created 8 arrays consisting of 8 longs each. In the pasm I block copied the values to my pasm variables
    var   
      long  portctr
      long  rxp[7]                                                     ' rx smart pin
      long  txp[7]                                                     ' tx smart pin
      long  rxhub[7]                                                   ' hub address of rxbuf
      long  txhub[7]                                                   ' hub address of txbuf
    
      long  rxhead[7]                                                  ' rx head index
      long  rxtail[7]                                                  ' rx tail index
      long  txhead[7]                                                  ' tx head index
      long  txtail[7]                                                  ' tx tail index
    
    uart_mgr        setq      #33-1                                  ' get 33 parameters from hub
                    rdlong    Port, ptra
    
    rxd             Long       0[8]                                     ' receive pin
    txd             Long       0[8]                                     ' transmit pin
    p_rxbuf         Long       0[8]                                     ' pointer to rxbuf
    p_txbuf         Long       0[8]                                     ' pointer to txbuf
    
    I need to read the first indexed value in rxhead and used this code
    rdlong t2, ptra[33 + index]
    
    I get a compiler error that the ptra index constant has to be between -32 and 31. Is there another method for access the hub data?
  • Hi DiverBob,
    Please can you put up a link to that old video of Jeff Martin.
    It sounds like good value!
    DiverBob wrote: »
    Continuing my education on PASM I found an old video that Jeff Martin did on P1 pasm that was very informative and explained a lot about P1 assembly.



  • macrobeak wrote: »
    Hi DiverBob,
    Please can you put up a link to that old video of Jeff Martin.
    It sounds like good value!
    DiverBob wrote: »
    Continuing my education on PASM I found an old video that Jeff Martin did on P1 pasm that was very informative and explained a lot about P1 assembly.

    They implied there was going to be an advanced video that I haven't found yet. I didn't see this on the Parallax youtube channel, if there is another advanced video I haven't found it yet.
  • DiverBob wrote: »
    I created 8 arrays consisting of 8 longs each. In the pasm I block copied the values to my pasm variables
    var   
      long  portctr
      long  rxp[7]                                                     ' rx smart pin
      long  txp[7]                                                     ' tx smart pin
      long  rxhub[7]                                                   ' hub address of rxbuf
      long  txhub[7]                                                   ' hub address of txbuf
    
      long  rxhead[7]                                                  ' rx head index
      long  rxtail[7]                                                  ' rx tail index
      long  txhead[7]                                                  ' tx head index
      long  txtail[7]                                                  ' tx tail index
    
    uart_mgr        setq      #33-1                                  ' get 33 parameters from hub
                    rdlong    Port, ptra
    
    rxd             Long       0[8]                                     ' receive pin
    txd             Long       0[8]                                     ' transmit pin
    p_rxbuf         Long       0[8]                                     ' pointer to rxbuf
    p_txbuf         Long       0[8]                                     ' pointer to txbuf
    
    I need to read the first indexed value in rxhead and used this code
    rdlong t2, ptra[33 + index]
    
    I get a compiler error that the ptra index constant has to be between -32 and 31. Is there another method for access the hub data?
    Not finding any examples for solving the above problem and my review of P2 documents hasn’t turned up anything either. One idea as a possible solution would be to increase the setq value to #57-1 and add another 4 long arrays as variables to the pasm code so all the global arrays are moved at the same time.
    rxd             Long       0[8]                                     ' receive pin
    txd             Long       0[8]                                     ' transmit pin
    p_rxbuf         Long       0[8]                                     ' pointer to rxbuf
    p_txbuf         Long       0[8]                                     ' pointer to txbuf
    
    rhead            Long      0[8]                                     ‘ Pointer to rxhead 
    rtail            Long      0[8]                                     ‘ Pointer to rxtail 
    thead            Long      0[8]                                     ‘ Pointer to txhead 
    ttail            Long      0[8]                                     ‘ Pointer to txtail
    
    That seems wasteful of memory space, seems there should be a better method that I’m not aware of.
  • Reading through the P2 documentation on Hub RAM I came across this information that discusses using PTRx during read and writes.
    RANDOM ACCESS INTERFACE
    
    Here are the random-access hub RAM read instructions:
    
    EEEE 1010110 CZI DDDDDDDDD SSSSSSSSS        RDBYTE  D,S/#/PTRx  {WC/WZ/WCZ}
    EEEE 1010111 CZI DDDDDDDDD SSSSSSSSS        RDWORD  D,S/#/PTRx  {WC/WZ/WCZ}
    EEEE 1011000 CZI DDDDDDDDD SSSSSSSSS        RDLONG  D,S/#/PTRx  {WC/WZ/WCZ}
    
    For these instructions, the D operand is the register which will receive the data read from the hub.
    
    The S/#/PTRx operand supplies the hub address to read from.
    
    If WC is expressed, the MSB of the byte, word, or long read from the hub will be written to C.
    
    If WZ is expressed, Z will be set if the data read from the hub equaled zero, otherwise Z will be cleared.
    
    
    Here are the random-access hub RAM write instructions:
    
    EEEE 1100010 0LI DDDDDDDDD SSSSSSSSS        WRBYTE  D/#,S/#/PTRx
    EEEE 1100010 1LI DDDDDDDDD SSSSSSSSS        WRWORD  D/#,S/#/PTRx
    EEEE 1100011 0LI DDDDDDDDD SSSSSSSSS        WRLONG  D/#,S/#/PTRx
    EEEE 1010011 11I DDDDDDDDD SSSSSSSSS        WMLONG  D,S/#/PTRx
    
    For these instructions, the D/# operand supplies the data to be written to the hub.
    
    The S/#/PTRx operand supplies the hub address to write to.
    
    WMLONG writes longs, like WRLONG; however, it does not write any D byte fields whose data are $00. This is intended for things like sprite overlays, where $00 byte data represent transparent pixels.
    
    In the case of the 'S/#/PTRx' operand used by RDBYTE, RDWORD, RDLONG, WRBYTE, WRWORD, WRLONG, and WMLONG, there are five ways to express a hub address:
    
        $000..$1FF		- register whose 20 LSBs will be used as the hub address
        #$00..$FF			- 8-bit immediate hub address
        ##$00000..$FFFFF	- 20-bit immediate hub address (invokes AUGS)
        PTRx {[index5]}		- PTR expression with a 5-bit scaled index
        PTRx {[##index20]}	- PTR expression with a 20-bit unscaled index (invokes AUGS)
    
    If AUGS is used to augment the #S value to 32 bits, the #S value will be interpreted differently:
    
        #%0AAAAAAAA					- No AUGS, 8-bit immediate address
        #%1SUPNNNNN					- No AUGS, PTR expression with a 5-bit scaled index
        ##%000000000000AAAAAAAAAAA_AAAAAAAAA	- AUGS, 20-bit immediate address
        ##%000000001SUPNNNNNNNNNNN_NNNNNNNNN	- AUGS, PTR expression with a 20-bit unscaled index
    
    
    PTRx expressions without AUGS:
    
        INDEX6 = -32..+31 for non-updating offsets
        INDEX = 0..16 for ++'s and --'s
        SCALE = 1 for RDBYTE/WRBYTE, 2 for RDWORD/WRWORD, 4 for RDLONG/WRLONG/WMLONG
    
        S = 0 for PTRA, 1 for PTRB
        U = 0 to keep PTRx same, 1 to update PTRx (PTRx += INDEX*SCALE)
        P = 0 to use PTRx + INDEX*SCALE, 1 to use PTRx (post-modify)
        IIIIII = INDEX6, uses %100000..%111111 for -32..-1 and %000000..%011111 for 0..31
        NNNNN = INDEX, uses %00001..%01111 for 1..15 and %00000 for 16
        nnnnn = -INDEX, uses %10000..%11111 for -16..-1
    
        1SUPNNNNN     PTR expression
        ------------------------------------------------------------------------------
        100000000     PTRA              'use PTRA
        110000000     PTRB              'use PTRB
        100IIIIII     PTRA[INDEX6]      'use PTRA + INDEX6*SCALE
        110IIIIII     PTRB[INDEX6]      'use PTRB + INDEX6*SCALE
    
        101100001     PTRA++            'use PTRA,                PTRA += SCALE
        111100001     PTRB++            'use PTRB,                PTRB += SCALE
        101111111     PTRA--            'use PTRA,                PTRA -= SCALE
        111111111     PTRB--            'use PTRB,                PTRB -= SCALE
        101000001     ++PTRA            'use PTRA + SCALE,        PTRA += SCALE
        111000001     ++PTRB            'use PTRB + SCALE,        PTRB += SCALE
        101011111     --PTRA            'use PTRA - SCALE,        PTRA -= SCALE
        111011111     --PTRB            'use PTRB - SCALE,        PTRB -= SCALE
    
        1011NNNNN     PTRA++[INDEX]     'use PTRA,                PTRA += INDEX*SCALE
        1111NNNNN     PTRB++[INDEX]     'use PTRB,                PTRB += INDEX*SCALE
        1011nnnnn     PTRA--[INDEX]     'use PTRA,                PTRA -= INDEX*SCALE
        1111nnnnn     PTRB--[INDEX]     'use PTRB,                PTRB -= INDEX*SCALE
        1010NNNNN     ++PTRA[INDEX]     'use PTRA + INDEX*SCALE,  PTRA += INDEX*SCALE
        1110NNNNN     ++PTRB[INDEX]     'use PTRB + INDEX*SCALE,  PTRB += INDEX*SCALE
        1010nnnnn     --PTRA[INDEX]     'use PTRA - INDEX*SCALE,  PTRA -= INDEX*SCALE
        1110nnnnn     --PTRB[INDEX]     'use PTRB - INDEX*SCALE,  PTRB -= INDEX*SCALE
    
    
    Examples:
    
    Read byte at PTRA into D
    
        1111 1010110 001 DDDDDDDDD 100000000     RDBYTE  D,PTRA
    
    Write lower word in D to PTRB - 7*2
    
        1111 1100010 101 DDDDDDDDD 110111001     WRWORD  D,PTRB[-7]
    
    Write long value 10 at PTRB, PTRB += 1*4
    
        1111 1100011 011 000001010 111100001     WRLONG  #10,PTRB++
    
    Read word at PTRA into D, PTRA -= 1*2
    
        1111 1010111 001 DDDDDDDDD 101111111     RDWORD  D,PTRA--
    
    Write lower byte in D at PTRA - 1*1, PTRA -= 1*1
    
        1111 1100010 001 DDDDDDDDD 101011111     WRBYTE  D,--PTRA
    
    Read long at PTRB + 10*4 into D, PTRB += 10*4
    
        1111 1011000 001 DDDDDDDDD 111001010     RDLONG  D,++PTRB[10]
    
    Write lower byte in D to PTRA, PTRA += 15*1
    
        1111 1100010 001 DDDDDDDDD 101101111     WRBYTE  D,PTRA++[15]
    
    Read word at PTRB into D, PTRB += 16*2
    
        1111 1010111 001 DDDDDDDDD 111100000     RDWORD  D,PTRB++[16]
    
    So the error I got from trying to use ptra[56] is due to operating under INDEX5: PTRx {[index5]} - PTR expression with a 5-bit scaled index. This limits the ptra[-32 to 31] in this mode. There is another mode that allows a 20 bit index: PTRx {[##index20]} - PTR expression with a 20-bit unscaled index (invokes AUGS).
    PTRx expressions with AUGS:
    
    If "##" is used before the index value in a PTRx expression, the assembler will automatically insert an AUGS instruction and assemble the 20-bit index instruction pair:
    
        RDBYTE  D,++PTRB[##$12345]
    
    ...becomes...
    
        1111 1111000 000 000111000 010010001     AUGS    #$00E12345
        1111 1010110 001 DDDDDDDDD 101000101     RDBYTE  D,#$00E12345 & $1FF
    
    It appears that if I use the INDEX20 mode then I can use the following command for larger index numbers. Is my understanding correct for this use of INDEX20?
    rdlong t2, ptra[##(33 + index)]
    
  • JonnyMacJonnyMac Posts: 8,912
    edited 2021-01-08 03:53
    I'm under the weather and a little confused, but I think you have a block of 56 longs that you've moved from the hub to the cog and you'd like to manipulate them in the cog and put them back in the hub. Is that it? Perhaps a couple simple subroutines will get you going until a better way is demonstrated.
    rd_hub          mov       hubpntr, ptra
                    mov       t1, hubindex
                    shl       t1, #2
                    add       hubpntr, t1
        _ret_       rdlong    hubdata, hubpntr
    
    
    wr_hub          mov       hubpntr, ptra
                    mov       t1, hubindex
                    shl       t1, #2
                    add       hubpntr, t1
        _ret_       wrlong    hubdata, hubpntr 
    
  • Thanks Jon for the code suggestion. Where I need this is for re-writing part of your jm_fullduplexserial assembly code that uses prta[5] and so on. Since I’m using several arrays to store the individual settings for 8 serial ports, ptra[5] would have been the equivalent to ptra[33] and above which INDEX5 didn’t permit.
    I will try out your code and see how it goes, it looks pretty good. I’m still curious about the ##INDEX20 operation mode, haven’t run across any examples of it in my searches except for in the P2 silicone documentation I posted. This assembly coding exercise has been good in making me study the documents much more closely and search for examples of each code element, learning a lot more about P2 assembly this way even if it is a slow process!
  • DiverBob wrote: »
    Is my understanding correct for this use of INDEX20?
    rdlong t2, ptra[##(33 + index)]
    

    This should work providing that index is a constant. Which may not be what you need. If index is a variable, you'll need to do something else, for example adding some appropriate multiple of index to ptra before using ptra (and then subtracting it back to restore ptra after you're done).
  • Thanks ersmith, I didn’t think that was probably legal syntax but it helped me visualize what I was looking for at that point.
  • rd_hub          mov       hubpntr, ptra
                    mov       t1, hubindex
                    shl       t1, #2
                    add       hubpntr, t1
        _ret_       rdlong    hubdata, hubpntr
    
    
    wr_hub          mov       hubpntr, ptra
                    mov       t1, hubindex
                    shl       t1, #2
                    add       hubpntr, t1
        _ret_       wrlong    hubdata, hubpntr
    
    Is shl T1, #2 used to line up to the next long by multiplying the index by 4?
  • Is shl T1, #2 used to line up to the next long by multiplying the index by 4?

    Yes, it's assuming that you want to read an array of longs, so the indexes refer to longs instead of bytes. That's why index has to be multiplied.

    You'll also need to offset ptra by the appropriate amount for the variables you're using. It looks like rxd has offset 0 and 8 longs, so the txd array will start at offset 32 (8*4), p_rxbuf at offset 64, and so on. so you'd do something like:
    con
        RXD_OFFSET = 0
        TXD_OFFSET = 32
        RXBUF_OFFSET = 64
        ' ... and so on
    
            ' access txd array
            shl    index, #2       ' multiply index by 4
            add    ptra, index     ' offset ptra by index*4
            rdlong t2, ptra[##TXD_OFFSET]        ' read data from hub
            sub    ptra, index     ' restore ptra
            shr    index, #2       ' restore index (only if necessary)
    

    This could be made a bit faster if you "inverted" things and used an array of structures rather than a set of arrays. That is, instead of declaring
    long rxd[8]
    long txd[8]
    
    and so on, for 9 different variables (or however many you need for each serial port) you did:
    con
        struct_size = 9  ' number of variables per serial port
        rxd_offset = 0   ' first variable is rxd
        txd_offset = 1   ' second variable is txd
        rptr_offset = 2  ' third is receive pointer
        ... ' and so on
    var
        long serdata[struct_size * num_ports]
    
    dat
            ' fragment to show how to access tx pin
            mov    t1, index
            mul    t1, #struct_size*4  ' adjust to start of data for port "index"
            add    ptra, t1            ' fixup ptra
            rdlong data, ptra[txd_offset] ' now data holds the txd value
            sub    ptra, t1            ' restore ptra
    

    If you're reading many variables all together the "add ptra, t1" / "sub ptra, t1" pairs can be combined, i.e. you just have to do one "fixup ptra" before reading the variables and one "restore ptra" at the end once all the variables are read.
  • Thanks ersmith for such a detailed response! You answered a question I had a couple of entries ago with your first example, that is the first code where I've seen the ptra[##INDEX20] mode used.
    Your last code example is great in that the array structure uses a lot of memory setting up for 8 ports even if you are only using 1 or 2 ports. This reduces the amount of space needed to only what is required for the actual number of ports.
  • It turns out that the "ptra[##BIG_OFFSET]" syntax isn't supported by flexspin yet :(. Seems nobody has used it before, or at least not enough to ask for it. It does seem to work in PNut. Remember that like any ## operand, this actually outputs *two* instructions, one AUG prefix and the RDLONG instruction. So it's slower than ptra[SMALL_OFFSET].
  • The ptra[##BIG_OFFSET] is slower than ptra[SMALL_OFFSET], but only if you don't need to manipulate and restore ptra to use a small offset.

    For single instances, the large offset will be more efficient in both code space and execution time, but for accessing blocks using a single ptra manipulation you quickly reverse that.

    Another advantage of using the structure approach could be to perform a single ptra manipulation around a burst read of only the parameter block for the port to be operated on.

    @DiverBob, As you have only buffer address, head, and tail pointers per buffer you can only have fixed size buffers, which might introduce risks of frame loss on fast ports, or wasting hubram for slower ports.

    The structure could grow to two more longs per port to give a buffer max address for each buffer, allowing per port buffer sizes and even different sized buffers for rx and tx functions.
    If the head and tail longs contain the absolute hubram addresses then there's no pointer maths required to access the buffer, and the wrapping tests simply compare the head and tail pointers against the buffer max address, wrapping to the buffer start address. All of this can be performed against data in cogram with the buffer pointer section of the parameter block being written back to hubram before switching to a different port.

    Advantages:
    No pointer maths
    Parameters sit at fixed locations in cogram, so code remains static
    Variable buffer sizes enabled by clean code
    Only 10 long parameter block loaded per port, so constant parameter loading time per port and smaller cogram footprint.
    Simpler code (no indexing) meaning smaller, faster code in cogram.

    Disadvantage:
    Parameters must be reloaded every time you need to switch to support a different port; although this is likely the case anyway.
Sign In or Register to comment.