Shop OBEX P1 Docs P2 Docs Learn Events
Receive packets — Parallax Forums

Receive packets

TumblerTumbler Posts: 323
edited 2011-12-15 10:02 in Propeller 1
Hi,

I have a question about the fullduplexserial object.

I want to receive some packets and want to save one in memory.
A packet always starts with $FB and ends with $04. (packet lenght is between 8 and 16 bytes)

I can receive a packet, but have trouble to hold one in memory.
Can somebody help me further with this?

this is what i have so far...
CON  _clkmode = XTAL1|PLL16X
  _xinfreq = 5_000_000


OBJ
  com: "FullDuplexSerial"
  serial: "Parallax Serial Terminal"  




VAR
  byte char
  byte packet[16]
  byte packetStart
  
PUB Main  | ptr
  'com.start(rxpin, txpin, mode, baudrate)   
  com.start(0,1, 00, 38400)
  serial.start(115200) 


  repeat
    com.RxFlush
    char:=com.rx
    if char == -1
      ptr:=0


    if char == 15 'packet start
        
        packet[ptr]:=char
        ptr++
    if char == 4 'packet end


      packet[ptr]:=char
        
        serial.dec(ptr)
        serial.char(10)
        serial.char(13)
        ptr:=0


Comments

  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-12-12 19:44
    Tubular,

    You said the packet starts with $FB, but your code indicates that it starts with 15. In any event, try this for your main loop. (I'm assuming that $FB is correct.) It will store everything between $FB and $04 to your packet array.
      repeat
        repeat until com.rx == $FB
        ptr~
        repeat until (char := com.rx) == $04
           packet[ptr++] := char
    

    -Phil
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-12 23:22
    Sometimes it's also helpful to understand why the code does not work. So, here is my explaination:

    (If you indentation is really like it's displayed here in the thread)
      repeat
        com.RxFlush                                ' empty any transmissions that might have been missed so far - OK
        char:=com.rx                                ' read one character - OK
        if char == -1                                  ' com.rx will not return -1 as far as I know. -1 is returned by rxTime when the time passed by without receiving a character 
          ptr:=0                                         ' so, here you set prt to 0 in a piece of code that never runs
    
        if char == 15 'packet start              ' this will only happen once the transmission start code is send.
            packet[ptr]:=char                      ' so this code will only be executed once as well. In the end you only store the start-byte and the end byte and nothing else ;o)
            ptr++
    
        if char == 4 'packet end                 ' find the end - OK
          packet[ptr]:=char                        ' but as start and end are always the same there is no added value in storing these bytes
            serial.dec(ptr)
            serial.char(10)
            serial.char(13)
            ptr:=0.
    

    And here a suggestion for an enhancement of Phils code. It can happen that you have transmission problems ... or maybe do a mistake in setup of the senders baud-rate ... whatever.
    If you have such kind of problems it can happen that you don't see the END-character after 16 bytes. With Phils code you'd continue to read and store and you'd overwrite important memory locations sooner or later.
    It's only a small amount of additional memory needed for a small check, but it saves your program in case of these transmission problems.

    BTW with phils code a buffer size of 14 is enough, as the start- and end-characters are not stored
      repeat
        repeat until com.rx == $FB
        ptr~
        repeat until ((char := com.rx) == $04) or (ptr>13)
           packet[ptr++] := char
    
        if char==$04
           ' do whatever you want to do with the package .. call functions .. whatever
        ' else the package is ignored because the end sign has not been received in time
    

    It's also a good practice to use constants. One reason is for making the code better readable and second reason is to allow easy changes without searching all the places that have to be changed. For example if you want to increase the size of the buffer. So, here is my final proposal:
    CON
       START_OF_PACKET = $FB
       END_OF_PACKET = $04
       BUFFER_SIZE = 14
    
    VAR
      byte char
      byte packet[ BUFFER_SIZE ]
    
    PUB main
    ...
      repeat
        ' skip all characters until the start-char has been received
        repeat until com.rx == START_OF_PACKET
        ptr~
        ' read characters until the end-character has been received or the buffer would overflow
        repeat until ((char := com.rx) == END_OF_PACKET) or (ptr>constant(BUFFER_SIZE-1))
           packet[ptr++] := char
    
        ' recheck the end-condition - in case the last read character is a end-char everything is fine and
        ' the package can be processed
        if char==END_OF_PACKET
           ' do whatever you want to do with the package .. call functions .. whatever
        ' else the package is ignored because the end sign has not been received in time
    

    So, the START/END_OF_PAKET constants are mainly for better readability. But it might also help later on in copy and paste code development. If you are in a new project and realize that this piece of code would also work there but with different characters you only have to change the constants.
    Constants like BUFFER_SIZE are always usefull. It avoids bugs due to typos and in bigger code it's easier to resize the buffer because no code-change is needed. For example you might find out that it's better to read 2 packets before processing.
  • JonnyMacJonnyMac Posts: 9,198
    edited 2011-12-14 07:48
    I have a couple projects that can receive packets while doing other things -- the code is constructed like this:
    var
    
      byte  packet[32]
    
    
    pub main | t, state, c, idx
    
      term.start(RX1, TX1, %0000, 115_200)
      pause(1)
    
      state := 0
      idx := 0                                            
    
      t := cnt
      repeat
        c := rx.check
        case state
          0:
            if (c == $FB)
              packet[idx++] := c
              state := 1
    
          1:
            if (c => 0)
              packet[idx++] := c
              if (c == $04)
                ' process packet data
                ' idx is legnth of packet   
    
              
        ' other loop code here
    
    
        waitcnt(t += (25 * MS_001))
    

    In the event your code doesn't want to sit around waiting on a packet, that is, it could be doing other things, this structure may be helpful (combined with the other great tips in this thread).
  • TumblerTumbler Posts: 323
    edited 2011-12-14 20:11
    Thx all..

    Just solved a little problem here with the package length.
    Sometimes i received a $04 char as the third byte (package length)

    This is what i have at the moment.
    PUB Main  | ptr,i
      ' set pins 3 to 10 to outputs
      dira[3..10]~~
      'com.start(rxpin, txpin, mode, baudrate)   
      com.start(0,1, %0000, 38400)
      serial.start(115200) 
      ' skip all characters until the start-char has been received
      ' repeat until com.rx == START_OF_PACKET
      repeat
        com.RxFlush
        repeat until com.rx == START_OF_PACKET
          ptr~
          packet[ptr++] := com.rx 'get priority
          packet[ptr++] := com.rx 'get address
          char := com.rx 'get packetlength
          repeat i from 0 to char-1
            packet[ptr++] := com.rx 'get byte
          packet[ptr++] := com.rx 'get crc
          repeat until com.rx == END_OF_PACKET
          'serial.dec(ptr)
          'repeat i from 0 to ptr-1
          '  serial.hex(packet[i],2)
          '  serial.str(string(","))
          '  serial.char(13)  
        '
        '' we have received a full package
         ' let start parse it
         
         if packet[0]==32 ' we received a packet from module address 20
            if packet[2]==1
              'output pin 3 high
              serial.str(string("Output 3 High"))
              !outa[3] 
    
    

    Now the package sender needs some data back. But first i have to look how i can do it.

    For those who are interested
    I'm experimenting with some velbus modules: http://www.velbus.eu/products/view/?id=387354
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-14 22:18
    Nonononono ... I mean ... yes, your code works, but only in case there are no problems. Please do yourself a favour and write more robust code!

    First thing: what is the difference in those 2 pieces of code (with really no additional code inside of the repeat!) ??
    com.RxFlush
    
    repeat until com.rx==START_OF_PACKET
    

    Flush will simply throw away everything in the full duplex internal buffer. The repeat-loop on the other hand will only throw away something if there is unexpected garbage.
    Let's say after receiving a valid package you will do some more time consuming processing. The full duplex still buffers bytes coming from your device even if you are busy. So, most propable it will send START_OF_PACKET followed by the first bytes of the next package. When you're done with processing, why would you want to throw away this valid data?

    Second thing: why would you add all this extra code?
          ptr~
          packet[ptr++] := com.rx 'get priority
          packet[ptr++] := com.rx 'get address
          char := com.rx 'get packetlength
          repeat i from 0 to char-1
            packet[ptr++] := com.rx 'get byte
          packet[ptr++] := com.rx 'get crc
          repeat until com.rx == END_OF_PACKET
    

    Reading from START_OF_PACKET til END_OF_PACKET will perfectly do the job! You can inspect all the bytes after receiving! AND it is safer! What happens if you simply have a bit-read-error in the packetlength when receiving the most significant bit? This would make your inner repeat run 128+x times instead of x times.

    My proposal:
    CON
       START_OF_PACKET = $FB
       END_OF_PACKET = $04
       BUFFER_SIZE = 14
    
    [color=green]
       ' define the index of different data inside of the packet
       POS_PRIORITY        = 0
       POS_MODULE_ADDRESS  = 1
       POS_DATA_SIZE       = 2
       OFFSET              = 4          ' real packet size is packetlength as received 
                                        ' plus the extra bytes received for priority, address, packetlength and crc 
       OFFSET_CRC          = 3
    
       PIN_LED             = 3
    [/color]
    
    VAR
      byte char[color=green], crc[/color]
      byte packet[ BUFFER_SIZE ]
    
    PUB main
    ...
      repeat
        ' skip all characters until the start-char has been received
        repeat until com.rx == START_OF_PACKET
        ptr~
        ' read characters until the end-character has been received or the buffer would overflow
        repeat until ((char := com.rx) == END_OF_PACKET) or [color=red](ptr>constant(BUFFER_SIZE-1))[/color]
           packet[ptr++] := char
           [color=green]' add the CRC-code here[/color]
    
        ' recheck the end-condition - in case the last read character is a end-char a full packet has been received
        ' now check the important content: packetsize (as announced in the packet) + OFFSET should match with ptr
        ' and the CRC send has to match with the calculated CRC
        if (char==END_OF_PACKET) [color=green]and ((packet[ POS_DATA_SIZE ]+OFFSET)==ptr) and crc==packet[ packet[ POS_DATA_SIZE ]+OFFSET_CRC ][/color]
           ' do whatever you want to do with the package .. call functions .. whatever
           [color=green]if packet[ POS_MODULE_ADDRESS ]==32
               if packet[ POS_DATA ]==1
                   !outa[ PIN_LED ]
           [/color]
              
        ' else the package is ignored because the end sign has not been received in time
    

    The code in red saves your *** if you have transmission-problems. For example you switch on the light exactly when the most significant bit of the packetlength is transferred. I know these things propably won't happen for days, weeks, month or longer, but when these things happen it even can destroy your device.
    Let's imagine your code would then overwrite a variable or some piece of code which sets the DIRA-register and by accident some pins attached to output pins of pheripherals will be set to output on the propeller as well. *BAZINGA* ;o)

    I don't know the specs of your packages, so I did not add the CRC-code. This is the only thing I would add to the original receiving loop because then you can immediately decide if the package is OK after receiving the END_OF_PACKET. But you need to know which bytes are included in the CRC. Is it only the data or is it data and header (priority, address and packetlength)? What CRC-function is used? Simple XOR?


    PS: LOL .... saves your *** ... are you so prude on the other side of the ocean? Then you need to check this as well: ass
  • TumblerTumbler Posts: 323
    edited 2011-12-15 03:36
    MagIO2,

    Got this from the velleman forum about the crc:
    Velbus is using two's complement checksum
    Take the sum of all bytes (unsigned) up to the checksum, discarding any overflow bits. Then invert all bits and add 1.
    CHECKSUM = (~SUM)+1

    Your code: repeat until ((char := com.rx) == END_OF_PACKET) or
    (ptr>constant(BUFFER_SIZE-1))

    What if the second byte is the 'end_of_packet'? it simply stopsI don't know if you noticed, i'm from Belgium :)
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-15 05:23
    Did you recognize that we have 3 loops?
    Loop one (the outer one) is looping forever and repeats the whole process over and over again.
       Initialize buffer, ptr and crc variable
       Loop 2 is waiting for the START_OF_PACKET and skipping all other characters
       Loop 3 is filling the buffer until a) END_OF_PACKET has been received OR b) the buffer is full
          b) should usually only happen if there is a transmission-error when receiving the END_OF_PACKET. Otherwise the BUFFER_SIZE constant is to small.
       If the packet is OK then process it, otherwise the outer loop starts again
    

    So, what happens if the second byte is END_OF_PACKET?
    Of course the 3rd loop will immediately end, leaving prt set to 0. Now have a look at the following part of the if statement:
    [b][color=blue]packet[ POS_DATA_SIZE ]+OFFSET)==ptr[/color][/b]
    
    which evaluates to ?+OFFSET == 0, which is false in any case because OFFSET alone is > 0.

    ? reminds me that the buffer should be initialized before getting filled with data again. So the following lines should be added/changed:
    PUB main
    ...
      repeat
        [color=green]
        ' initialize for the next package
        bytefill( @packet, 0, BUFFER_SIZE )
        crc~  ' or maybe crc := START_OF_PACKET, depending on whether tha character also counts
        ptr~
        [/color]
    
        ' skip all characters until the start-char has been received
        repeat until com.rx == START_OF_PACKET
        [s][color=green]ptr~[/color][/s]
    
        ' read characters until the end-character has been received or the buffer would overflow
        repeat until ((char := com.rx) == END_OF_PACKET) or (ptr>constant(BUFFER_SIZE-1))
           packet[ptr++] := char
           ' add the CRC-code here
           [color=green]crc := (crc-char+1) & $FF[/color]
    
        
        ' remove the crc byte send from the calculated crc - this is faster than having an if-statement in the crc-calculation
        crc := (crc + packet[ packet[ POS_DATA_SIZE ]+OFFSET_CRC ] - 1) & $FF
    
        ' recheck the end-condition - in case the last read character is a end-char a full packet has been received
    ...
    

    Hopefully I did not add to many bugs, as I can't test at the moment.
    Tumbler wrote: »
    ... don't know if you noticed, i'm from Belgium :)
    What do you mean with that? Do loops work different in Belgium? ;o)

    PS:
    @JonnyMac:
    Thanks for sharing this! I'm not sure if it's needed in Tumbler's project, that's why I did not add it to my proposed code so far.
    I'd propably not put the waitcnt(t += (25 * MS_001)) into the code addressed to a beginner without further comments. Guess it means that the loop will be executed each 25ms. But 25 ms makes 360 bytes that can be received in the meantime with the current baudrate. So it very much depends on the device attached if this code workes over the long run. Having a device continuously sending at that speed means that the buffer of the full duplex serial will overflow very soon. Am I right?
  • MagIO2MagIO2 Posts: 2,243
    edited 2011-12-15 06:16
    You should have linked to this:
    http://www.velleman.eu/downloads/0/velbus/manuals/protocol/protocol_vmb8pbu.pdf

    Funny thing is that it's really easy for me to understand what's written on the page in your language.

    What puzzles me a bit is that this document says the START_OF_FRAME is always zero and the END_OF_FRAME is $7F, it lists some more bytes to be send before the data length code.
    All in all this is a bad documentation! It does not say a single word about the baudrate and how many frames are send per second ..... or whether the inter-frame byte is continuously send ...
    Good documentation always comes with examples.

    So, without having such a device you can't get ready to run code from the forum. You have to find out things by yourself.


    PS: The CRC code in my post has to be changed, because the CRC is 16 bit - I thought we were talking about 8 bit. Can you do that by yourself? The idea stays the same.
    PPS: WAAAAAAA .... bad documentation ... or is the CRC 15 bit because the document says CRC15...CRC1? Typo? Who knows?!

    Good luck ;o)
  • TumblerTumbler Posts: 323
    edited 2011-12-15 10:02
    Documentation is bad, i know. (Open protocol, if you search a lot, hehe)
    I have a lot of modules here, nice thing is that i have a log function in the software (velbus link)
    I can see what packages are send and received between the modules.

    I'm sure, the crc is only 8 bit.
    baud is 38400, and i thougt 60 or 100 mSecs between a package.


    I will take a look at your code later, thx for the help

    Grtz Luc
Sign In or Register to comment.