Is there a good tutorial on using FullDuplexSerial to receive data?



  • JonnyMacJonnyMac Posts: 6,429
    edited 2018-04-15 - 16:25:19
    Just when I think I might know Tracy, I find out I need to know more. It looks like you can loop RxTime to capture the whole message. Jon's code above does that. But I don't think RxTime was designed for that. Otherwise, what would Rx be there for?
    The .rxtime() method allows you to limit the time the program is blocked -- the .rx() method will block until a byte is received; this is a critical distinction.

    In my example (actual code from customer project is displayed below), I use .rxcheck() to see if there is anything in the buffer. If not, I return to the caller so that the rest of the program can continue. In this case, the main loop checks to see if we've lost coms and if that is the case, the program switches to running the last animation (this is a road sign controller) that it was commanded to run. If coms are restored, this module will re-synchronize with the master controller.

    So... .rxcheck() is used to see if something has arrived. Once the packet header ($7E) is detected, the code loops using .rxtime() to receive the rest of the packet. Note that I always have a gap between packets, so I can use the timeout return value (-1) of .rxtime() as an end-of-message flag. If coms is lost mid-message, I have two checkpoints: 1) the length of the command message is known, and 2) I use CRC check on all bytes of the packet (except, of course, the final CRC byte).

    FTR, the rs485 object is mine and uses methods compatible with FullDuplexSerial.
    pub check_wire_message : idx | c, cs
      c := rs485.rxcheck                                            ' anything in buffer?
      if (c <> $7E)                                                 ' if not 1st byte of header
        return 0                                                    '  we're outta here
        bytefill(@cmd, 0, 16)                                       ' clear command buffer
        cmd[idx++] := c                                             ' capture header
        if (DEBUG == YES)
          term.str(string(13, 13, "<-- "))
          term.hex(c, 2)
          term.tx(" ")
      repeat                                                        ' capture rest of packet
        c := rs485.rxtime(2)
        if (c => 0)
          cmd[idx++] := c
          if (DEBUG == YES)               
            term.hex(c, 2)                
            term.tx(" ")                  
      if (idx <> (cmd[2]+4))                                        ' good packet length?
        return 0
      cs := crc8x(@cmd, (cmd[2]+3))                                 ' compare checksums
      if (cs <> cmd[cmd[2]+3])  
        return 0 
      if (DEBUG == YES)
        term.str(string(13, "    good packet"))
      hascoms := true
      return true
  • Here is a simple commented demo of how to use the string functions in FullDuplexSerial.
  • Hello ErNa

    I had a look. I really don't know PASM, but there is one line that get's me thinking.
                            add     t1,#4                 'get buffer_ptr
                            rdlong  rxbuff,t1
                            mov     txbuff,rxbuff
                            add     txbuff,#16          '< is this something to do with the buffer size?

    I saw some things about versions of FullDuplexSerial that have larger buffers. Isn't bigger going to be better?

  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-16 - 07:36:36
    Good news! I think my little coding muscles are getting warmed up. I am now able to send and receive about sixteen bits:
            repeat 14                     
                 x += 1
    The receive side only looks for a header, but that is enough for now..
      repeat                          'If no data, keep checking
        c := check_message            'check for incoming data, and return the result to c 
        if c == 1                      'If the header is recorded ($7E), all the other data is considered received 
    Pub check_message | idx, c, cs
      idx := 0
      c := FDS.rxcheck
      if (c <> $7E)             'exit if no header
        return 0
       repeat 14               'If header was detected, capture the rest of the message
        c := FDS.rxtime(5)    'Waits five ms for each byte 
          cmd[idx++] := c
      return 1  
    This is a quite stripped down version of Jon's code. It works fine, but I cannot transmit all the bits that I would like to.

    I cannot loop Tx enough times to transmit all of the code. 14 is the upper limit with this approach.

    This seems to be a buffer size limit issue. And that is going to be a problem in the future. I want to be able to send a few thousand variables for backing up the whole stack of variables between devices. And MIDI sends a lot of codes... several for device identification, and then you have to send two or three bytes just to get one thing done.

    So, is there a way around the buffer size limit, or how do I make the buffer larger? Changing just these lines in FullDuplexSerial does not help:
      byte  rx_buffer[24]           'transmit and receive buffers
      byte  tx_buffer[24]            'upped them to 24 (from 16)
    What else needs to be done? Can I wipe the buffer and just send some more right away?

  • The transmitting side is normally not the problem, when the buffer is full, the tx() methode just waits until there is some free space in the buffer.

    But on the receiving side the buffer will overflow, if the processing of the received data takes longer than the transmitting of the data. That's why most enhanced FDS objects only modify the rxbuffer size.
    The FD_Serial_Conf object from the synthesizer object I have linked at begin of this thread, lets you configure the receive buffer up to 256 bytes.

    You can also try to insert some delays in the sending Prop to give the receiving Prop more time.

  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-17 - 08:32:53
    Good! I'll see if I can figure out what the problem is then. The system won't function at all if I set the repeat to more than 14 times, with sending the header. It starts up the welcome screen, and then when it goes to the functional code, everything ceases functioning.
  • Hello ErNa

    I had a look. I really don't know PASM, but there is one line that get's me thinking.
                            add     t1,#4                 'get buffer_ptr
                            rdlong  rxbuff,t1
                            mov     txbuff,rxbuff
                            add     txbuff,#16          '< is this something to do with the buffer size?

    I saw some things about versions of FullDuplexSerial that have larger buffers. Isn't bigger going to be better?
    Hello Robert, IIRC the buffersize is just 16 bytes, so tx buffer is located 16 after rx buffer. In the diagramm I pointed to are different versions and ideas to modify the driver, this map is quite outdated, but shows the principle of fds
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-18 - 06:54:49
    Ok, I just looked again and saw where you mentioned JDCogSerial and also LIN_FullDuplexSerial. JDCog is still on the OBEX; I don't see LIN. Have you used either much?

    I am also going to make a test object for FDS and pals, that has almost no user interface. Just a few lights to indicate that button pushes are being detected. That way, I will know that it isn't my spaghetti code that is causing the system to lock up on startup.
  • Are you suggesting there's a problem with FDS? You may re-think that, or look at your specific implementation. The original had very small buffers which could be easily overrun. Have you tried a variant -- mine, for example -- that allows for bigger buffers?

    I recently created another specialty version of FDS that uses a very large RX buffer and a smaller TX buffer. It's for a laser-tag controller. When the game is in progress there can be a lot of messages flying around (the serial device in use is an XBee radio). I found that we were occasionally losing messages in big fire-fights so I dug in a bit and make a version that works in this specific application.

    I added some more notes to my [standard] version of FDS and have compiled it into a program that's attached.
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-19 - 10:04:16
    I usually have no idea as to why code crashes the Propeller, Jon. Unless I assigned only 40 longs of memory to a cog, and never changed it as the code grew (oops!). I have only done software as a hobby, so far. And not very often. I didn't write ANYTHING last year.

    And this case is no different. Why can't I loop Fx 16 times? I have no idea.

    What I do know, is that if there is hardly any other code running in the chip, it's probably an issue with FDS. Also, I really do have some sloppy code that I have kept running just because it was still working. But just because it works, doesn't mean I did it the correct way.

    Also, if there are only a few dozen lines of code in the whole test object, it won't take a minute for some one who knows this stuff to look it over, and tell what I am doing wrong.

    The test object I am making would also be a good basis for an object that would replace my old code.
  • It's hard for anyone else to say -- or help you -- without seeing the actual code. Can you share? I shared production code with you! BTW, that client just tested his product last night and is thrilled -- I'm a happy guy today.
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-20 - 00:09:25
    It's a mess. And it's a large mess.

    I think this is the heart of the problem:
      long stack[700]
      Byte a[30]
      Byte aa[30]
      Byte d[12]
      Byte dd[12]
      Byte ddd[12]
      Word c
      Word cc
      Word little[16]
      Word big[4]
      Word number[100]
      Word bob
      Word TextLine[20]
      Byte button
      Byte button2
      Byte Group
      Byte go
      Byte go2
      Byte clear
      Byte column[20]
      Byte rows[16]
      Byte columnb[10]
      Byte rowb[4]
      Byte menu
      Byte off
      Byte zz
      Byte zzz[13]
      Byte pointer
      Byte muxaddress[16]
      Byte kitty
      Byte e[25]
      Word f
      Word g
      Byte h
      Byte j
      Byte S[5]
      Byte m[20]
      Byte o[45] 
      Byte p
      Byte r[16]
      Byte t[24]
      Byte z[40]
      Byte qq[24]
      Byte v
    All those variables being passed through the hub can't be helping anything.

    And this isn't either:
      if go == 3
          go := 5
          x := 0
          y := 0
          repeat 10
              repeat 10
                 lcd.PUTC(ddd[x])     'zz takes the place of f, which is what normally leads to the start of a new name.  
                 x += 1
            zz += 1      
            x := 0     
            y += 1
          zz -= 10 
    This is calling PUTC 100 times to generate a page of text. I am fairly certain this was not helping things. I didn't know how to put together a string from individual bytes at the time though, so I left it that way.

    However, this method didn't crash the chip when I was loading all of the presets into RAM on startup (via VarBackup). It wasn't until after I started loading all the variables from EEPROM as needed (also via VarBackup... this was done to cut down on how much RAM the object used) that the chip was starting to choke.

    There is a clear pattern here of large amounts of calls for complicated methods crashing the Propeller.

    The latest tested version of ButtonScanner will only loop the Tx method from FullDuplexSerial 15 times max. I made a test object that has no capability to edit the presets, and only has support for a row of LED's (no support for a screen), and it will loop Tx fifty times no problem. I haven't checked all of the data it's sending yet (that will be done shortly, via Parallax Serial Terminal), but it will execute the code repeatedly without locking up.

    We don't expect an $8 chip to run complicated code. It's supposed to be powerful... for the price. I am going to have to find ways to do basically the same things while taking up less resources.
  • JonnyMacJonnyMac Posts: 6,429
    edited 2018-04-20 - 15:43:15
    We don't expect an $8 chip to run complicated code. It's supposed to be powerful... for the price. I am going to have to find ways to do basically the same things while taking up less resources.
    Last year I finished a commercial laser-tag controller program that is about 3000 lines in the main app with another 3000 lines of library code, and I believe is far more sophisticated than what you're trying to approach here. In real-time it is processing IR messages from other taggers, shooting IR "bullets" at other taggers, dealing with radio messages from taggers and the management system, it plays WAV audio files, controls PWM on eight outputs, manages an HMI (parallel LCD and three buttons that share the LCD bus), and monitors its own battery. I don't mean to be unkind, but I don't think the problem is with the Propeller; I think has more to do with your coding style

    It's hard to offer too much without a schematic and specification, but there are things I saw that I'd like to offer alternatives to.

    I cannot find the logic behind code like this:
      number[12] :=  string("01")
      number[13] :=  string("02")
      number[14] :=  string("03")
      number[15] :=  string("04")
      number[16] :=  string("05")
      number[17] :=  string("06")
      number[18] :=  string("07")
      number[19] :=  string("08")
      number[20] :=  string("09")
      number[21] :=  string("10")
      number[22] :=  string("11")
      number[23] :=  string("12")
    You're storing pointers to inline strings. Unless there's an absolute time crunch, a simple method would remove all these variables
    pub dec2str(n)
      serial.tx(n  / 10 + "0")
      serial.tx(n // 10 + "0")
    If you do want to store them, there is another way without all the variables
      Dec2          byte    "01", 0, "02", 0, "03", 0, "04", 0, "05", 0
                    byte    "06", 0, "07", 0, "08", 0, "09", 0, "10", 0
    pub dec2str(n)
      serial.str(@Dec2 + (--n * 3))
    You could replace this code:
      muxaddress[0] := %0000
      muxaddress[1] := %1000
      muxaddress[2] := %0100
      muxaddress[3] := %1100
      muxaddress[4] := %0010
      muxaddress[5] := %1010
      muxaddress[6] := %0110
      muxaddress[7] := %1110
      muxaddress[8] := %0001
      muxaddress[9] := %1001
      muxaddress[10] := %0101
      muxaddress[11] := %1101
      muxaddress[12] := %0011
      muxaddress[13] := %1011
      muxaddress[14] := %0111
      muxaddress[15] := %1111
    ...with a simple loop:
      repeat idx from 0 to 15
        muxaddress[idx] := idx >< 4
    Better yet, dump the variable array and just use the idx >< 4 to setup your mux pins.

    There are a lot of strings in the tagger application as well. I find that defining them in DAT sections is cleaner than manually defining them inline and using variables to store their address. Consider this:
      s_Adjust      byte    "Adjust", 0
      s_Backlight   byte    "Backlight", 0 this will make your program easier to read when you do something like this:
    If you have a long list of [contiguous] strings you can access the nth item in that list with this code:
    pub str_pntr(idx, p_list)                                        
    '' Returns pointer to idx'th string in list (at p_list)       
    '' -- strings may be variable length                              
      repeat idx                                                     
        p_list += strsize(p_list) + 1                               ' skip current string
      return p_list
    FWIW, I find the use of trailing tildes problematic. Of these two lines of code:
      result := 1
    ...the latter is far more obvious to those who may not know the few obscure operators of Spin, and it's actually faster. The trailing tilde version consumes 336 system ticks while the verbose/obvious version consumes 288.

    You're still using the standardized version of FDS which has VERY SMALL buffers; that may be part of the problem. If you have a spare cog, you might consider using a PASM I2C driver to speed up your EEPROM access. In the laser tag project I have a dual-speed driver. High speed I2C is used when I'm reading a program module from the SD card into the lower 32K portion of the Propeller EEPROM. During normal game activities, the low-speed version is used to access the database which is in the upper 32K. This strategy allows a new program module load quickly, but the in-game access to the database does not need to be fast, so I save a cog by switching to the low-speed (Spin) version.

    I've attached my high-speed EEPROM object in case you want to give it a try. Note that it does require pull-ups on both I2C pins.
  • Jon, you hit the nail right on the head.

    My code just isn't at all efficient. And the prop is choking on being asked to repeat simple tasks so many times. It can do much heavier lifting... fewer times. I wanted to do things the way you are showing above, but I didn't know how yet! But now I HAVE to do it the right way, or I simply am going to have to cut features that I really want. And I really love this device.

    I really didn't expect to get this much help, and I very much appreciate it! It will save me a great deal of time.
  • I was able to prove that FullDuplexSerial will do what I need it to. Tx and Rxtime will loop sixty times no problem. I don't think I am going to continue to use this approach, because this is the sort of code that got me into trouble in the first place, but I wanted to see that it would actually keep looping dozens of times if asked to do so.

    I am now going to set up a bare circuit board to receive and display data with FDS, and then I will move onto phase 2, which will be creating a practical MIDI controller based around FullDuplexSerial or one of it's variants.

    I had to improvise to prove functionality on a device that doesn't have an LCD display (nor was it connected to a PC via Parallax Serial Terminal).
      repeat                          'If no data, keep checking
        c := check_message                   'check for incoming data, and return the result to c 
        if c == 0
          'c := check_message
        elseif c == 1         'If the header is recorded ($7E), all the other data is considered received (for now)
    Pub check_message | idx, c, cs
      idx := -40     'The whole stack of variables is being recorded on this end, but only twelve will be displayed via LED's
                           'So I had to sample them a dozen at a time, but I think this is a valid test. 
      c := FDS.rxcheck
      if (c <> $7E)                                                 ' 1st byte of header
        return 0
        'cmd[idx++] := c                                             ' capture header
      repeat 60                                                      ' capture rest of packet
        c := FDS.rxtime(5)    'Waits five ms for each byte 
          cmd[idx++] := c
      return 1   
    It occurs to me that this is a rather crude way to send data. This is actually just receiving bits which were stored in bytes at this point.

    What I want to do is store eight bits in a byte, send that, and then decode the bytes back into eight bits, which will then be used to control the Relaydriver method to switch relays.

    I also want to send all of the bytes as a string, so I don't have to loop a call for Tx and Rx or Rxtime.
  • This does work work well for sending four bit addresses to a CD4067 multiplexer :)
    outa[17..20] := x >< 4   'where x is from 0 to 15
  • AribaAriba Posts: 2,230
    edited 2018-04-21 - 11:40:30
    This does work work well for sending four bit addresses to a CD4067 multiplexer :)
    outa[17..20] := x >< 4   'where x is from 0 to 15

    It's the same as:
    outa[20..17] := x
    but here without double bitreversion.

  • Funny. I was wondering why the bits were reversed and I didn't even look at the MSB..LSB pin assignments in the outa[] call. I'll blame it on being on the road and staying up too late! Great catch, Andy.
  • Robert GrahamRobert Graham Posts: 55
    edited 2018-04-23 - 08:32:46
    Yeah, Andy's version also works on the test unit. Always got to have confirmation.

    The cleanup continues. It's actually going quite smoothly, with generous help from you gentlemen!

    This is the new code for generating the footnote (it's a bit difficult to keep track of exactly what preset you are on without it)
         lcd.PUTC((button+bob+1)  / 10 + "0")   'lcd.PUTC(number[f+group+12]) was the old code.  "number" stored the character to be displayed.
         lcd.PUTC((button+bob+1)  // 10 + "0")  'f and button do similar things, I am not sure why exactly I use them both
                                                                       'bob keeps track of what group of presets I am on.  I will try to use better variable names!
    I am going to change how data is sent to the LCD screen quite a bit. I don't send many strings... this is probably the cause of most of the inefficiency. I also want to combine... is concatenate the right word?... each bit for the relay switching in bytes.

    10000011 needs to be an actual byte, so I can save and transmit them in that format. Currently, it is saved and transmitted as separate bytes. This can't be a good thing.

    I have this idea for encoding the bits into bytes:
    Pub Encode(x) | y
      y := 0
      abyte[x] := %00000000
      repeat 8
            if abit[y++] == 1   'where abit[y] is input by the user as a binary value
                   abyte[x] |= %00000001   
         abyte[x] <<= 1
    To display and edit the presets, I need to decode them:
    Pub Decode(x) | y
      y := 0
      repeat 8
        abit[y++] := abyte[x] & %00000001
        abyte[x] >>= 1
    In English, I am using Bitwise Shift, Bitwise And, and Bittwise Or, do the exciting work (pages 161, 164 and 165 of the manual).

    I had this basic idea a few years ago. My little coding muscles just were up to the task yet, but I think I about have it. Has anyone done this/seen this used somewhere?

    If someone has something better, or a better version of this, don't be shy.

  • Robert Graham,

    Those binary numbers will be a lot easier to read if you put an underscore (_) in them.
    The underscore has no effect on the number's value.
    newValue := oldValue | %0000_0000
    anotherValue := someValue & %0000_0001
  • I will do that : )
  • I just gave FullDuplexSerial it's own cog for the send side... and voilà, it can loop Tx 24 times. Previously, it would only loop 14 times, but this was just another example of me trying to make a cog do too many things, and then it locks up. Twas a method too many. But this looks good:
    Pub Eloader | x,y
      x:= 0
      y:= 20
      if h == 0
        repeat 24                      'This plug the data into the e variable from the a variable, so the mux can send to the demux.
           e[x] := a[x]
           x += 1
      if h == 1
        repeat 24                      'This plugs the data into the e variable from the a variable, so the mux can send to the demux.
           e[x++] := o[y++]                        
     CogIDMux := cognew(Multiplexer, @stack[600])
    Pub Multiplexer  |  x               
      if v > 0
         v -= 1
      x := 0
      repeat 24                     
        x += 1
    I had made an object that had little but FDS running in it, and it loops Tx 60 times no problem. This proved that I had been running into a TMC (too much code) situation with my early attempt to run FullDuplexSerial in my object.

    So, another little success. This was critical to the upgrade, though. I think I was supposed to be running more cogs all along, but hey we all gotta start somewhere.
Sign In or Register to comment.