Shop OBEX P1 Docs P2 Docs Learn Events
Question on Parsing using received data from Serial Terminal Object — Parallax Forums

Question on Parsing using received data from Serial Terminal Object

kvaneppskvanepps Posts: 10
edited 2012-01-24 20:47 in Propeller 1
Hello to all:

I have been working the the Propeller for a few days now and would like to try to create a parer for incoming serial data over the serial port. I posted something earlier today but I do not see it so I am posting my problem in a different way since I was originally posting on a parsing method I was trying to create. Since then, I have found two objects on the OBEX site that provides parsing of string methods. But I do have some questions since I am running on a short time frame to get something working.

My plan is to have a protocol of ASCII data that is delimited by a pipe characters (|) sent as a single string. There will be five fields of numbers sent from a GUI on a PC to the Propeller via the USB/serial connector on the propeller board. These five fields will be separated by a pipe so it will be received as a string of text as follows:

40|200|14|12|15|

I want to parse this string and separate the five fields into a a byte array of five.
i.e
VAR
byte field[5]

By using some of the String parser methods, I want to read each field and convert the ASCII text of each field into a single byte, storing it in the byte field array. These fields will always be numbers and none of them will have a value over 254..

I plan on using the Parallax Serial Terminal object to receive and send data between the PC GUI and the Parallax Quickstart board.

I was thinking on using the method StrIn(stringptr) in the Serial Terminal Object to get the incoming string and them parse by looking for the pipes. To use this method, do I create a variable in the VAR section as a long or do I create another array of bytes that will hold the string returned from this method?

Does the Serial Terminal Object support a buffer for incoming messages? I noticed that the Serial Terminal Object also has a method (private) called StrToBase(stringptr,base) that will convert a string to a number. I would like to use this. Would it be better to copy this into my main program or would it be acceptable to change this method to be public?

Thanks
Ken

Comments

  • MagIO2MagIO2 Posts: 2,243
    edited 2012-01-24 01:32
    Usually in such a scenario you would use 2 separate separator-characters, one for separating the numbers and one for separating the block of 5 numbers. I guess the numbers have different meanings and can not be mixed. Without different separators there is no chance to find out where your 5-number-block starts.

    Serial Terminal Object has a message buffer for sending and another one for receiving. (both set to 64 bytes in size)

    The Serial Terminal Object is a good starting point for your project even if you have to modify it here and there to make it a perfect fit. So, create a copy with a different filename and start tweaking!

    So, here is the idea of a program that is doing what you need:
    1. the big outer loop - repeat forever?
      2. wait until you received the block-end-separator
      3. repeat 5 times
         4. use a tweaked version of DecIn to read the number
         5. store it in your byte-array
      6. process the byte array
    

    The tweaked DecIn would simply receive bytes until it sees the number-separator-character ( | in your case ) instead of waiting for NL.
  • lyassalyassa Posts: 52
    edited 2012-01-24 09:38
    I would do the parsing on the PC side and send five numbers - or strings - consecutively to the Propeller. This will simplify the spin code. Then you will simply have:
    repeat
      num1 := DecIn
      num2 := DecIn
      ... 
      num5 := DecIn
    

    Please note the syntax could be off. I am writing this off memory :)
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-01-24 10:02
    Parsing on PC side???

    If you send a number as a string you have to parse it on propeller side to make it a number you can calculate with again. No way to parse it on PC-side.
    If you want to send bytes ... well ... possible but then you have a protocoll that only computers can read. But this is only a good advice if you need the extra performance.

    But in the end your avice is even wrong, because DecIn receives a number string and not a byte and is doing the parsing for you.
    So, either you have the combination
    send bytes - read with CharIn
    send string - read with DecIn
    Nevertheless in the byte transfer you really need to think about how to find the start/end of a package (5 bytes). So, you need a special kind of protocol which allows to recover if you have an transmission problem.

    With decimal string you can use each character other than numbers to give signals to the receiver like END_OF_NUM ( | in our example ) and END_OF_PACKAGE ( CR maybe ... )
  • sssidneysssidney Posts: 64
    edited 2012-01-24 10:48
    The FullDuplexParallel object has routines in it to convert from text strings to numeric values. You could just steal the StrToDec and StrToHex routines.

    http://obex.parallax.com/objects/644/

    There is also a good thread listing all the spin serial objects here:

    http://forums.parallax.com/showthread.php?128184-Serial-Objects-for-SPIN-Programming
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-01-24 12:24
    Why should he switch to another object? The object we talk about also has functions to parse string numbers.
  • kvaneppskvanepps Posts: 10
    edited 2012-01-24 15:56
    Hi guys,

    Been working on this and below is the code we came up with. There still is some debug statments in it for testing purposes. It will now take the following as a command with fields:

    +10|200|20|20|10|<NF>

    The line feed will indicate to the program that it is the end of the command and the ParseCommand method will work to parse out the five fields. Two errors will be fed back on the serial port if the "+" is not sent to indicate the start of the command fields as well as an error if too many fields are sent.
    I don't know if this is the most efficient but it works. Any suggested improvements would be appreciated. It took a while to figure out how to work with pointers and variables.
    SerialParser.spin
    ''Test message to Parallax Serial Terminal.
    CON
       
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
      DELIMITER        ="|"    ' delimiter is a pipe to separate fields
    VAR
      byte cmdfield[40]
      byte temp[40]
      byte input[60]
    
    OBJ
      pst : "Parallax Serial Terminal"
       
    PUB TestMessages
      ''Send test messages to Parallax Serial Terminal.
     
      pst.Start(115_200)
      repeat
        '' lets me know if I am receiving the string
        pst.StrInMax(input, 30)
        pst.str(string("Original string was: "))
        pst.str(input)
        pst.newline
        pst.str(string("we received string: '"))
        pst.str(input)
        pst.str(string("'"))
        pst.newline                                        
        ParseCommand(input)   '  this calls my parser of the received string
        Pst.newline
        pst.str(string("Sanity check!"))
        Pst.newline
    PRI PrintSingleChar(mystring)
      repeat strsize(mystring)
        
        pst.str((mystring++))
        pst.newline
        
    PRI ParseCommand(mystring)| field, count 
      field := 0
      count := 0
      if byte[mystring] <> "+"    '  must have a + at beginning of the command string
          pst.Str(string("ERR1"))
          return                     ' if not a + at the beginning of command, exit the ParseCommand method (will send error)
      mystring++                   'increment pointer of received string
      BYTEFILL(@temp, 0, 40)  '  clear my temp buffer
      
      '' print info for debugging purposes
      pst.newline     
      pst.str(string("In ParseCommand!"))
      pst.newline
      pst.str(string("mystring contains: '"))
      pst.str(mystring)
      pst.str(string("'"))
      pst.newline
      
      ''  parsing loop that will look for numbers and assign them to array a
      repeat strsize(mystring)
        if (field > 4)   'if more than four fields sent(send an error)
            ' print debug info
            pst.str(string("ERR2"))
            pst.newline 
            quit
        ' check to see if character read is a pipe (|)
        if byte[mystring] == DELIMITER
           '  if character is a pipe, convert
           '  string in temp array and store at cmdfield
            cmdfield[field]:=pst.StrToBase(@temp,10)
            
            'debug info sent out serial port
            pst.str(string("cmdfield["))
            pst.dec(field)    'display what is stored in cmdfield array
            pst.str(string("] contains as dec: '"))
            pst.dec(cmdfield[field])   ' display what number value is stored
            pst.str(string("'"))
            pst.newline
                              
             field+=1     '  increment i to count number number of 
          count := 0    'set count back to 0 and clear temp buffer
          bytefill(@temp, 0, 40)  ' clear temp array with nulls      
        else
           ' since the character is not a pipe, store it at temp[count]
           temp[count] := byte[mystring]    'store character
           count++    ' increment count to point to next temp array location
       mystring++
        
    
    
    
    
    
  • MagIO2MagIO2 Posts: 2,243
    edited 2012-01-24 16:30
    You should read the manual again! Your program works because you do it constantly wrong. But your program overwrites memory it should not touch!

    The definition of StrInMax says:
    PUB StrInMax(stringptr, maxcount)

    ptr means that you should really call this function with a pointer to a buffer as the first parameter. But have a look at your code, you don't use a pointer but a variable containing zero because it's initialized like that by the propeller tool. But in all other calls which also expect a pointer you also simply use "input". Maybe you are coming from C programming? There the name of an array is a pointer, but that is not true for SPIN.

    So, if you replace the input with @input in the TestMessage function you do it the right way.

    For ParseCommand I do not really understand why you do not use input directly? Nevertheless -without running your program- this seems to be coded in a way that it also needs a pointer as parameter. The call ParseCommand(@input) should do the job.
  • kvaneppskvanepps Posts: 10
    edited 2012-01-24 20:47
    MagIO2 wrote: »
    You should read the manual again! Your program works because you do it constantly wrong. But your program overwrites memory it should not touch!

    The definition of StrInMax says:
    PUB StrInMax(stringptr, maxcount)

    ptr means that you should really call this function with a pointer to a buffer as the first parameter. But have a look at your code, you don't use a pointer but a variable containing zero because it's initialized like that by the propeller tool. But in all other calls which also expect a pointer you also simply use "input". Maybe you are coming from C programming? There the name of an array is a pointer, but that is not true for SPIN.

    So, if you replace the input with @input in the TestMessage function you do it the right way.

    For ParseCommand I do not really understand why you do not use input directly? Nevertheless -without running your program- this seems to be coded in a way that it also needs a pointer as parameter. The call ParseCommand(@input) should do the job.

    Thanks for the info. I have been programming in C++ and Java so I am still trying to understand the syntax and language for Spin. I just received a Quickstart board this weekend and have been playing with some of the tutorials in the PE Expermienter manual. I will try your suggestions.

    Thanks
    Ken
Sign In or Register to comment.