Shop OBEX P1 Docs P2 Docs Learn Events
Multiple serial ports running independently — Parallax Forums

Multiple serial ports running independently

elmoqelmoq Posts: 4
edited 2013-03-22 13:54 in Propeller 1
Hello, I have just started working with the propeller, would appreciate some assistance.

The project I'm working on is to input data strings from four independent RS232 ports, process them, and output a combined string (made from parts of the four input strings), to a fifth serial port.

My intention was to allocate a serial port to each of four separate cogs for the inputs, and have a fifth "master" cog to do the combining and outputting to the fifth port. Seems straightforward enough, but I can't get it to work.

I have used the objects FullDuplexSerial, and Extended_FDSerial. These work fine on separate cogs.

But my problem is this: When the "master" cog starts each of the serial objects (eg RxStr) on the separate cogs, it always stops and waits until the string has been read. I can't quite figure out how to make the separate cogs just read in a string from the serial ports, and raise a flag to the master cog that a buffer of data is ready. When the master cog starts a serial object on the separate cogs to start reading in a string (eg using RxStr from Extended_FDSerial), it always has to wait until the string has been read in, before the method returns. Everything I've tried to make the separate cogs run independently have failed.

Could someone suggest a way for the master cog to start a serial object on a separate cogs, return to its main program, then periodically check whether a string buffer is ready. The separate input cogs should read in a string (say 100bytes), and raise a flag so that the master cog can get the data (eg through passing a buffer pointer)

Thanks

Gary

Comments

  • kwinnkwinn Posts: 8,697
    edited 2013-03-15 18:41
    Post your code so forum members can see what you are doing. Also, you may want to use the 4 port serial object for the input strings and one of the ports or FullDuplexSerial for outputting the combined string.
  • kwinnkwinn Posts: 8,697
    edited 2013-03-15 18:54
    The simplest way I can see of doing what you want is to start a spin program in it's own cog, that program then starts the 4 port serial object, scans the 4 ports for serial data and stores it in a buffer for each port, and signals the master program when the full string has been received.
  • Duane DegnDuane Degn Posts: 10,588
    edited 2013-03-15 18:59
    There are lots of ways to do what you want.

    One technique I've used in several projects is to have the PASM driver watch for an end of message character. When the end of message character is received a counter is incremented. Post #9 of this thread has one version of this type of serial driver with flags. The serial driver had been modified to reuse the memory previously occupied by the PASM code so it didn't use much additional RAM for the large rx buffers.

    Tracy Allen has greatly improved the multi-port serial driver. If possible, I'd suggest using his driver as a starting point.

    I have another project that merges serial lines. The driver adds a prefix to the outgoing signal to indicate which serial line the data was received. Maybe you'll find something useful in the code used for that project.
  • elmoqelmoq Posts: 4
    edited 2013-03-16 01:11
    kwinn wrote: »
    Post your code so forum members can see what you are doing. Also, you may want to use the 4 port serial object for the input strings and one of the ports or FullDuplexSerial for outputting the combined string.

    Thanks for the replies so far. Am looking in detail at the replies.

    In the meantime, and in response to kwinn - sorry, should have included some code to show what I've tried, and what I want:

    Here's an example of what I've tried (showing two input ports, and one output port):
    ===========
    OBJ
    ser1: "External_PDSerial" ' serial input 1
    ser2: "External_PDSerial" ' serial input 2
    ser3: "External_PDSerial" ' master processor, and output port

    VAR
    Byte Bytearray1[100],Bytearray2[100]

    PUB Serial
    ser1.Start(17, 16, 3, 4800)
    ser2.Start(19, 18, 3, 4800)
    ser3.Start(21, 20, 3, 4800)

    repeat
    ser1.RXstrTime(1000,@Bytearray1)
    ser2.RXstrTime(1000,@Bytearray2)

    'process Bytearrays
    =============
    This works, but the repeat loop gets blocked in each RXstrTime call until that call finishes. Data arriving on the other port in the meantime is lost.

    What I would really like is something like this:
    ===========
    ..... OBJ, VAR and PUB same as above

    repeat
    if ser1.dataready1
    ser1.RXgetStr(@Bytearray1)
    'process Bytearray1
    if ser2.dataready2
    ser1.RXgetStr(@Bytearray1)
    'process Bytearray2
    =============
    Here, the "serX.datareadyX" is some flag which returns null if there's no data ready.
    The important point is that, whatever is happening in this loop, the cogs are still independently reading the port and filling the array.

    Thanks
    Gary
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2013-03-16 09:45
    Hi Gary,
    You have to look at the code behind the
    ser1.RXstrTime(1000,@Bytearray1)

    to see why it is blocking. Also, since there is no flow control back to the input devices, you may have to specify larger receive buffers for the serial ports, and/or provide double buffering. By double buffering, I mean that there would be two input buffers for each channel

    A routine to receive lines from multiple serial ports simultaneously would use the RxCheck method in a loop, something like this as a 1st approximation:
    [SIZE=1][FONT=courier new]PUB MainSerial
    repeat
       if (char1 := ser1.RxCheck) > -1   ' check for bytes on port 1
         if char1 == eol     ' end of line detected
           ProcessLine1Input
           idx1~
         else 
           bytearray1[idx1++] := char1
       if (char2 := ser2.RxCheck) > -1    ' check for bytes of port 2
         if char2 == eol     ' end of line detected
           ProcessLine2Input
           idx2~
         else 
           bytearray1[idx2++] := char1
          [/FONT][/SIZE]
    
    That would require that the time to process each line input is short in relation to the time it takes to fill the serial receive buffers. A variation of this could run in its own cog, in which case it would not call ProcessLineInput directly, but would set a flag so that another cog could do that in parallel and reset the flag when finished.

    Come to think back on your original post, it may be that you have to wait until all 4 input strings are available, and only then do you combine them for output--That would simplify things. Do these external devices stream out data asynchronously, or is the whole process command-driven from the Prop? If asynchronous, that complicates things, because you have to wait for the start as well as for the stop.
  • elmoqelmoq Posts: 4
    edited 2013-03-20 22:49
    Thanks for the replies, but I am still having trouble understanding how to get separate cogs to receive a data string, check for a CR, store in a local buffer, then flag this to another cog which can then extract the string and process it.
    I want this to be happening on 4 separate cogs monitoring 4 separate serial lines.

    I have looked at FullSerial4Input, and the examples, but they don't seem to do what I require.

    In the examples for FullSerial4Input(eg demo2_FDS4port), the collection and checking of the strings is *still* done in the main loop (on the first cog).

    For example, in demo2_FDS4port, this is in the main cog:
    ..
    fds :FullSerial4Input
    ...
    ===================
    repeat
        char := fds.rxCheck(B_PORT)                     ' non-blocking, read input port
         if char > -1                                    ' -1 is no character, otherwise ascii code of character
            fds.tx(B_PORT,char)                           ' send it right back out the B_PORT
            fds.tx(DEBUG,char)                            ' also send it right out the debuf port
            myBuf[idx++] := char                          ' also append it to the string buffer
            if fds.rxcheck(DEBUG) == SP                   ' break out if user presses space at the DEBUG port
                flag~~                                      ' user pressed spacebar
                quit
    until char==CR  
    
    ===================
    I want this to be done in the remote cog(s), and then flag to the main cog when the buffer is ready.

    What I would really like is something like this:
    ===========
    ser1: ExtSerial
    ser2: ExtSerial
    ....
       
     repeat       
      if ser1.dataready1
           ser1.RXgetStr(@Bytearray1)
             'process Bytearray1
      if ser2.dataready2
           ser1.RXgetStr(@Bytearray1)
           'process Bytearray2
    
    =============
    Here, the "serX.datareadyX" is some flag which returns null if there's no data ready.
    The important point is that, whatever is happening in this loop, the cogs are still independently reading the port and filling the array.

    In the examples (eg demo2_FDS4port), the collection and checking of the strings is *still* done in the main loop (on the first cog).

    There seem to be a lot of examples of multiple serial ports, but when I look into them I can't see how to do what I want as described above. Any suggestions and help gratefully accepted.

    Thanks
    Gary
  • AribaAriba Posts: 2,690
    edited 2013-03-21 00:48
    You cand use 1 cog that checks the 4 serial objects for received bytes and build 4 strings.
    the methode RXCHECK of the FullDuplexSerial checks if something is received but is non blocking (does not wait for a byte).

    So a code could look like that (just a draft and not tested):
    OBJ
      ser1 : "FullDuplexSerial"
      ser2 : "FullDuplexSerial"
      ser3 : "FullDuplexSerial"
      ser4 : "FullDuplexSerial"
    
    VAR
      long  sp1,sp2,sp3,sp4
      byte  strbuf1[64]
      byte  strbuf2[64]
      byte  strbuf3[64]
      byte  strbuf4[64]
      byte  flag1,flag2,flag3,flag4
      long stack[20]
    
    PUB main
      cognew(receive4strings,@stack)
      repeat
        ...
        if flag1
          flag1 := 0
          'do something with string1
        ...
    
    PUB receive4strings : c
     repeat
       c := ser1.rxcheck
       if c => 0
         strbuf1[sp1++] := c
         if c==13  'CR
           flag1~~
           strpuf1[sp1] := 0  'str_end
           sp1 := 0
       c := ser2.rxcheck
       if c => 0
         strbuf2[sp2++] := c
         if c==13
           flag2~~
           strpuf2[sp2] := 0
           sp2 := 0
       c := ser3.rxcheck
       if c => 0
         strbuf3[sp3++] := c
         if c==13
           flag3~~
           strpuf3[sp3] := 0
           sp3 := 0
       c := ser4.rxcheck
       if c => 0
         strbuf4[sp4++] := c
         if c==13
           flag4~~
           strpuf4[sp4] := 0
           sp4 := 0
    
    I intentionally made it not optimized, so that it is easier to understand. You can also use object arrays and post clear operators and such things...

    Andy
  • Tracy AllenTracy Allen Posts: 6,664
    edited 2013-03-21 08:22
    You can if you want encapsulate the four different processes that Andy described using RxCheck into 4 objects. That could be four instances of one object, or it could be four unique objects, to deal with the 4 separate serial streams. Declare fullDuplexSerial4port as an object in all 4, and also probably in your main object too. Only one instance of fullDuplexSerial4port will be in memory, and it will commandeer only one pasm cog to do its work. In the main cog, .Init all 4 ports by number, 1,2,3,4 (or by alias names) and .Start them. From then on they are available to all of the cogs that are started by the four objects. Each of your four auxiliary cogs would do the RxCheck, using its own port number. Pointers or methods would need to be set up as usual in order to allow the end-of-line flag and the data to be passed between the objects.
  • elmoqelmoq Posts: 4
    edited 2013-03-22 13:54
    Thanks for all the replies and comments on this.

    Special thanks to Andy, who took the trouble to write a section of code. This was perfect - Great!
    Just what I wanted to handle the four serial ports on a separate cog, and process the resulting strings on the "main" cog. Am using this for my application.

    Also helped me learn about the Propeller. Takes a bit of a "mind shift" to get used to the multiple cogs.Still getting used to it. But worth the effort.
    The more I look at the Propeller the more I find what a unique device it is.

    Gary

    PS - Not sure how to change the tag UNSOLVED to SOLVED...?
Sign In or Register to comment.