Shop OBEX P1 Docs P2 Docs Learn Events
FullDuplexSerial string operation — Parallax Forums

FullDuplexSerial string operation

HarleyHarley Posts: 997
edited 2006-11-24 06:36 in Propeller 1
Looking at this object, which is almost NOT commented, I think I see how to transmit a string from one Prop to a second.

But how does one receive a string?___ I see no reference to that.

And, if a string is longer than the buffer length, will it wait for room to open up in the buffer?___

Sorry about questions so late in the day prior to a Holiday. Good turkeys to all forum members.

Thank you yeah.gif

▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
Harley Shanko
h.a.s. designn

Comments

  • Rich MRich M Posts: 33
    edited 2006-11-23 00:05
    There's no function to receive a string - in most applications, you wouldn't typically want to call a blocking function that might never terminate because of a disconnected device, an error, etc. It's trivial to just use rx and/or rxcheck to build the string yourself.

    Regarding your second question - if the buffer fills and you're not servicing the serial device fast enough, you'll start getting serial overruns.

    - Rich
  • HarleyHarley Posts: 997
    edited 2006-11-23 00:37
    Rich said...

    There's no function to receive a string - in most applications, you wouldn't typically want to call a blocking function that might never terminate because of a disconnected device, an error, etc. It's trivial to just use rx and/or rxcheck to build the string yourself.
    Would that 'build' involve a 'repeat' loop?___ And how would the receiving end 'know' when the string end occurs?___

    I'm using one Prop with TV_text object for debugging; want to get some info from the 2nd Prop's program plus CHIPVER; the latter works OK. Seems something like this might be required, from what I think you're suggesting:
      repeat until c := 0
        if (c := PropSer.rxcheck) <> -1
          Display.out(c)
    
    


    with Prop2 sending a '0' character at the end of the sending of a string?___

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
    h.a.s. designn
  • T ChapT Chap Posts: 4,223
    edited 2006-11-23 07:12
    Here are some examples of receiving a string that might help. Keep in mind that Rx WAITS on a byte to be rec'd before continuing, if no byte rec'd, it just sits there forever. RxCheck does NOT wait around for a byte. You need to consider whether anything else is to take place on the same cog. If it does, you may need to park the Rx on a new cog. The Rx will fill up the buffer then start over where it left off. Don't worry about buffers as long as you read what comes in as needed before it starts overwriting itself. I could stand corrected on this, but in practice, the examples are working. Blame Mike Green for any blown up chips as a result of use.

    CON
      TAB  =  $09
      CR    = $0D
    
    VAR
       byte array[noparse][[/noparse]16]
       long x, y , z
    
    OBJ
       ser   :   " Full Duplex Object"
    
    PUB start
      ser.start(0,1,0,9600)   input is O, out is 2  mode is 0   9.6k baud
      repeat
       if receiveStr(@array,24) <> TAB
         quit
       Temp1 := convertstr(@array)   
       if receiveStr(@array,24) <> CR                                 
         quit
       Temp2 := convertStr(@array)
       Case Temp          'lets make decision based on what came in
    
    
    PRI receiveStr(address,count) | c             
      repeat count                   'start counting
       c := ser.rx                   'c = first rec'd byte
       if c == TAB or c == CR        'c == CR or TAB,                              
          byte[noparse][[/noparse]address] := 0 
          return c                   'now return delimiter for optional checking     
       byte[noparse][[/noparse]address++] := c          'update c                     
      return 0                       'default delimiter is zero
    
    
    PRI convertStr(address) | C     'convert the ascii string to integers
      repeat while c := byte[noparse][[/noparse]address++]
       result := result * 10 + (c - "0")
    
    PRI RetEcheck(ID,Value)       'error checking sending back to the computer
       ser.str(ID)                'return for error checking before moving motors
       ser.tx(tab)          
       ser.dec(temp2)     
       ser.tx(tab)
       ser.str(string(" "))     'computer wants a Space as termination in this case
       
    PRI  CaseTemp
       case temp1
             4270 : x := temp2                         '4270 is the converted integer value of the first segment that is "XID"
                    repeat until not lockset(SemId)      'make sure lock out anyone else trying to use Full Dup Obj till done
                    RetEcheck(string("XID"), X)           'send back the exact same data to the sender to confirm what was rec'd
                    lockclr(SemId)
       case temp1
             4370 : y := temp2           'y is a variable that will receive the value in TEMP 2 if the ID string is "YID" or 4370
                    repeat until not lockset(SemId) 
                    RetEcheck(string("YID"), Y)
                    lockclr(SemId) 
       case temp1
             4470 : z := temp2           'if this is Z Id then write the Z variable
                    repeat until not lockset(SemId) 
                    RetEcheck(string("ZID"), Z)
                    lockclr(SemId)
    
    



    The code above takes a TAB delimted string, and separates it, and converts it into two integers. Here is what I sent it from a computer:
    Tab=Chr(9)
    Space=Chr(32)
    'X = some value from 0 to 50,000 depending on a slider
    OutputString = "XID" + TAB + str(X) + CR
    
    



    In this example, the computer looks at a slider position, and sends the ascii ID of the Slider value plus a TAB(chr(9)), then the string value of X(i.e. 5000), then a CR. The propeller reads it, and separates the string at the TAB, and terminates at CR. Then, the Propeller reads through a list of about 10 cases, and assigns values to variables depending on the ID is received. You could modify the code to suit your needs.

    If you only needed to get a byte only, then you could just"
    PRI getrx
       recbyte := ser.rx
    
    



    This will get the most recent byte rec'd.

    Good luck with it.

    Post Edited (originator99) : 11/23/2006 7:23:15 AM GMT
  • HarleyHarley Posts: 997
    edited 2006-11-23 08:22
    Thanks, originator99.

    Appears to be much more complicated than I'd of imagined. No wonder nothing seemed to work right.

    Will mull over why it is so involved and try your suggestions.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
    h.a.s. designn
  • T ChapT Chap Posts: 4,223
    edited 2006-11-23 09:15
    If you want, post what you are doing and maybe someone will have some suggestions. It only took me about 968 hours to figure it out, and it is debatable as to whether I have or not. Actually once you see it, it is not that complicated. The above code could likely be tweaked to solve what you are trying to accomplish. The idea is though, you have to first conceive what needs to be done with the information received. In my example, there are numerous ID's that are sent followed by a value attached to that ID. In the code above , there are two useable parts to the string in addition to the separator and terminator. Your string may simply be a name, for example, "Cat", and you want to display that info on an LCD. (Forgive the following if this is understood already) In this case, you simply read the first byte in(ser.rx), passing the first received byte to the first element in an array called My Array. MyArray has 4 actual elements because we have set it to expect a string no longer than "cat" + 0.

    When you read in the string, the first rec'd byte goes to MyArray[noparse][[/noparse]0], and then each iteration of the readByte adds the next byte to the next element until it hits the termintor. So that the result is:

    MyArray[noparse][[/noparse]0] == "C"
    MyArray[noparse][[/noparse]0] == "A"
    MyArray[noparse][[/noparse]0] == "T"
    MyArray[noparse][[/noparse]0] ==  0
    
    



    The z_string, or zero terminated string, is a method to tell your loop that is reading the bytes that the string is done. You could in fact set up the loop to determine other values as the termination. Sometimes a Return (CR) is the termination, as in the example posted above. However, once it is read into the Propeller, whatever termination used is ultimately converted to a zero.

    The end result is, once all the elements have been filled with your string, the array MyArray can then be used for the purpose of the application. For example, if you were wanting only to see the string on the LCD, you'd refer to the LCD object and display all or part of the array.

    To make this simpler, here is what I would do:

    1. determine what you want to do with the string
    2. deterimine your termination scheme(CR for example)
    3. set a loop to read the bytes in (see example above), just use ser.rx to wait until a byte is rec'd, or a code block that uses ser.rx to read each byte, and increment the append to the array(see PRI receiveStr above)
    4. separate your string if needed by a TAB or other separator
    5. run some code to do something with the variable or array, or ignore the string as desired
    6. go back to the loop and wait for more bytes

    Hopefully this helps more than it confuses, but at this hour you don't have many options left on the forum [noparse]:)[/noparse]

    Post Edited (originator99) : 11/23/2006 9:24:49 AM GMT

  • T ChapT Chap Posts: 4,223
    edited 2006-11-23 18:32
    repeat until c := 0
        if (c := PropSer.rxcheck) <> -1
          Display.out(c)
    



    This stands to be corrected by someone that knows what they are talking about, but I think there are several problems with this.

    1. What if your data had a 0 in it? You'd want to use some termination that would certainly not be a possibility for some real data.

    2. Your loop does nothing with each byte it receives in terms of keeping it around for future use. If you tell it to loop until c:= 0, you are just making c = the most recent byte received and all other bytes are now gone forever as you didn't park them anywhere in a variable/array for future use. Instead, run a loop that increments and appends the array as in the example I posted. It reads a byte, sticks it in the first element of the array, reads another, sticks it in the next elemenet and so forth. Note that there is a "count" parameter passed that tell the loop how many times to repeat. This could be any number, just be sure to have your array set to receive that number plus the termination. You start counting at array[noparse][[/noparse]0].

    3. Unless there is a good reason, don't use .RxCheck, since if the cog is busy with other things it may miss a byte. Use .Rx as it never misses a byte as it is always waiting for a byte. If needed put the .rx on another cog. if the Propeller is to be doing other things while it is waiting for the byte. If no action is needed prior to getting the string, just put your loop at the top of the block, when the loop hits the termination it can go run some other code and then come back and wait til the next string/byte.

    4. Don't worry about using <> -1, it will just read whatever comes in, it doesn't matter if if is nil.

    5. You may take a look at this syntax
     if (c := PropSer.rxcheck) <> -1 
    
    

    You may want

    if c == PropSer.rxcheck <> -1
    

    Post Edited (originator99) : 11/23/2006 6:42:33 PM GMT
  • HarleyHarley Posts: 997
    edited 2006-11-23 18:51
    Originator99, thanks again for the suggestions.

    I may not have stated that it is a 'string' that is being sent from one Prop to another. So a '0' terminated string will just send the ASCII characters; the '0' stopping the transmission, right?___

    Now the problem at the receiving end is 'how' to recognize when the string is ended. I suppose the transmitting Prop could append a '0' (0000_0000 binary) and the routine would see the string properly end.

    I was hoping there was already implemented was some (normal???) way to deal with receiving ASCII codes. And testing for CRs or TABs, or the '0's for endings. yeah.gif

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
    h.a.s. designn
  • T ChapT Chap Posts: 4,223
    edited 2006-11-23 21:08
    I don't think there is one single protocol for receiving strings. There are building blocks for receiving as in the Full Dup Object, but there are many different things to do with the string once received, that there is no one soltution, hence, you have to build it yourself to do what you want to do. I may be stating this incorrectly, but a 0 can in fact be an ascii number, a-z, 0-9, some symbols etc. Your string may be a number that does contain a 0, so you want to send a CR as a termination instead. The receiver code should read the string in via a loop that appends the bytes to the array, and when it sees the CR($0D), it appends 0 to the last element and stops for further instructions.

    Below is an example that sends out a string of various info, note that ID is a variable that is an integer(4270), TAB is a hex number $09, Temp2 is a variable that is an integer, all followed by a Space. The receiver in this case chops off the string after the Space as terminator.


    PRI RetEcheck(ID,Value)    'this is a Transmitted string made of of several parts
       ser.str(ID)             'return for error checking before move
       ser.tx(tab)   
       ser.dec(temp2)   'an integer in ascxii form
       ser.tx(tab)
       ser.str(string(" "))    ' a space
    
    



    So in simpler terms, the string looks like all these values sent in succession:

    4270 'contains 0's
    $09 '0's again!
    25000 'only an example, this could be any number, but note that it does contain 0's
    $09
    " " ' an ascii space

    Since a string cannot be a real integer since it is only ascii values, you must use some ascii value as terminator/separator that does not get used in your real info.

    If you go back and study the first code I posted, or rather pulg it in and test it, you will see that it gets to a TAB, then it runs the convert method to convert the ascii values to integers and assigns the value to a long variable for future use. In the example it assigns the first block of ascii info to Temp1. Then it continues reading, it assigns the second ascii block following the TAB to Temp2. This could in fact be any number of separate ascii blocks, separated by however many tabs you need. But when it gets to the CR, it is done for that string.

    If you post what you are trying to send, it will be easier to point you in a direction of how to receive it.
  • HarleyHarley Posts: 997
    edited 2006-11-23 21:26
    originator99,

    Thanks for the further information.

    Gotta break now to go to Thanskgiving dinner. Will try suggestions tomorrow. Family and holidays gets in the way for this 'fun' stuff.

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Harley Shanko
    h.a.s. designn
  • M. K. BorriM. K. Borri Posts: 279
    edited 2006-11-24 06:36
    Here's what I use to parse integers and floats respectively....



    
    
           
    pub ParseNextInt(StringAddress, ReturnValueAddress) | curs1, curs2, pointy, temp, sign
    
    
         temp~
         curs1~
         curs2~
         pointy~
         sign := 1
    
         repeat
            'pointy := pointy + 1
            if (byte[noparse][[/noparse]StringAddress + ++pointy] == $00)
                   return -1
         until (IsAsciiDigit(byte[noparse][[/noparse]StringAddress+pointy]) == true)
         curs1 := pointy
         repeat
            pointy++ ' := pointy + 1
         until (IsAsciiDigit(byte[noparse][[/noparse]StringAddress+ pointy]) == false)
         curs2 := pointy 
    
         pointy := curs1
         repeat (curs2 - curs1)
            temp := temp * 10 + (byte[noparse][[/noparse]StringAddress+pointy] - $30)
            byte[noparse][[/noparse]StringAddress+pointy++] := "#"
            'pointy := pointy + 1
    
    
    ' if there's a plus or minus sign right in front of the number, integrate it into the number itself
         if (byte [noparse][[/noparse]StringAddress + curs1 - 1] == "-")
             byte [noparse][[/noparse]StringAddress + curs1 - 1] := "#"
             sign := -1
    
         if (byte [noparse][[/noparse]StringAddress + curs1 - 1] == "+")
             byte [noparse][[/noparse]StringAddress + curs1 - 1] := "#"
         
         long[noparse][[/noparse]ReturnValueAddress] := (temp*sign)
         
         return pointy
    
    
    pub ParseNextFloat(StringAddress, ReturnValueAddress) | beforedecimal, afterdecimal, dp1, dp2
    
         dp2 := dp1 := ParseNextInt(StringAddress, @beforedecimal)  ' tells me after how many digits i got the dec point
         beforedecimal := m.ffloat(beforedecimal)
         if (byte[noparse][[/noparse]StringAddress + dp1] == ".")
              byte[noparse][[/noparse]StringAddress + dp1] := "#"
              dp2 := ParseNextInt(StringAddress, @afterdecimal)  ' tells me after how many digits i got the end of the number
              afterdecimal := m.ffloat(afterdecimal)
         ' now dp2 - dp1 contain the number of digits after the dec point if any
              if (afterdecimal and (dp2 > ++dp1))
                  afterdecimal := m.fdiv(afterdecimal, tenf[noparse][[/noparse]dp2 - dp1])
                  beforedecimal := m.fadd(beforedecimal, afterdecimal)
         long[noparse][[/noparse]ReturnValueAddress] := beforedecimal
         
    
         return dp2          
    
    pub IsAsciiDigit(ByteVal)
       return (ByteVal > $2F and ByteVal < $3A)
    
    DAT
    tenf    long  1.0, 10.0, 100.0, 1_000.0, 10_000.0, 100_000.0, 1_000_000.0, 10_000_000.0, 100_000_000.0, 1_000_000_000.0 
     
    
    
    




    Note that I return the position where an int or float was found and pass the variable to be taken from a string by reference -- this is because I'm using this mostly to parse NMEA strings, this allows me to do things like checking that a value is present and/or getting the next value by calling ParseNextInt or ParseNextFloat repeatedly.

    If someone more knowledgeable than me would care to help me optimize this, please do [noparse]:)[/noparse]
Sign In or Register to comment.