SPIN2 SEND Command, A Short Tutorial

JonTitusJonTitus Posts: 158
edited 2020-09-15 - 15:39:17 in Propeller 2
SEND

SEND acts as a special type of method pointer, inherited from the calling method and, in turn, conveyed to all called methods. It provides an efficient output mechanism for data.

You may assign SEND as you would any method pointer, but it must point to a method that 1. takes one parameter and 2. has no return values:
SEND := @OutMethod   'SEND points to OutMethod

SEND will pass all parameters, including any return values from called methods, to the method SEND points to:
SEND("Hello! ", GetDigit()+"0", 13)

Any methods called from within the SEND parameters, such as GetDigit() in the example above, will inherit the SEND pointer, so that they also may use the SEND method. The following code provides an example of SEND use. It sends 8-bit patterns of 0s and 1s to LEDS at pins P56 through P63:
PUB go()
  SEND := @SetLED
  REPEAT
    SEND($01, $02, $04, $08, $10, $20, $40, $80)

PRI SetLED(x)
  PINWRITE(56 ADDPINS 7, !x)
  WAITMS(125)

Note: LEDs on the P2 EVAL Board are driven by active-low signals.

Within the go() method, the statement SEND := @SetLED, gives SEND the pointer to the SetLED method. This method satifies the requirement: only one variable and no return value. Note the SetLED method above includes a short delay so the LED patterns remain visible long enough so you can see them.

Next the REPEAT loop executes the SEND($01, $02...) statement that transfers the first parameter $01 to the SetLED method. The LED at pin P56 turns on. When this method finishes, it returns control to the SEND statement, which then sends $02 to the SetLED method, which turns on the LED at P57. Each LED turns on and off in sequence again and again in the REPEAT loop.

A second example shows how other methods can inherit the SEND pointer. An added method, Flash(), will turn all LEDs on and off. This method includes a SEND statement, too.
PUB go()
  SEND := @SetLED
  REPEAT
    SEND(Flash(), $01, $02, $04, $08, $10, $20, $40, $80)

PRI Flash() : x
  REPEAT 2
    SEND($00,$FF,$00)
  RETURN $AA

PRI SetLED(x)
  PINWRITE(56 ADDPINS 7, !x)
  WAITMS(125)

The program will call the Flash() method (in the first SEND() parameter) and will eventually pass the return value from Flash() to the SetLED() method (after Flash() has fully executed).

First, the Flash() method will run and send its own values, $00, $FF, $00, to the LEDs two times. Then, if you watch the LEDs, they next display $AA next. Why?

The Flash() method returns the value $AA to the SEND statement: SEND(Flash(),$01,$02... In effect the $AA value gets inserted in place of the call to Flash() in the list of parameters, making the whole program execution behave as if the SEND(Flash(), $01, $02...) had really been:

SEND($00,$FF,$00)
SEND($00,$FF,$00)
SEND($AA, $01, $02...)

The Flash() method inherited the SetLED() address and can use it independent of other uses in this program.

Jon Titus, 10-SEPT-2020
Some information taken from "Parallax Propeller 2 Spin2 Language Documentation," 2020-07-19, V34u. And thanks to Jeff Martin for corrections as of 09-14-2020.

Comments

  • cgraceycgracey Posts: 13,050
    edited 2020-09-10 - 21:55:39

    I really like short write-ups like this. Two-minute reads have a nice granularity.

    If we had lots of these, we could easily dynamically arrange categories of them and make them searchable.

    Could we have an alternative documentation that is comprised entirely of two-minute reads?

    The other nice thing about these minis is that they are not overwhelming to embark upon as the writer. Finite focus and subject.

    I'm telling you, this is the way of the future.

  • @cgracey
    I'm telling you, this is the way of the future.

    It may be however, I hope no one locks you up. LOL

    @ JonTitus
    SPIN2 SEND Command, A Short Tutorial

    Excellent!! Thanks Jon.
  • cgraceycgracey Posts: 13,050
    edited 2020-09-12 - 09:32:56

    Today I added RECV, which gets inherited like SEND, but it takes no parameters and returns a single value.

    RECV.png
    284 x 605 - 9K
  • This will be very useful Chip! :smile: Having some simple I/O type of standard like this will help make things more consistent for sharing objects and also make it much easier to redirect IO dynamically.

    Maybe we also may need a non-blocking RX command option that could test if RECV is ready, or maybe a single true/false or other control argument passed into RECV for indicating that we just want to see if something can be received yet? Sometimes we don't want to block if nothing is ready.
  • Thanks Chip. Since it is returning a long we could use -1 for nothing and 0-255 for a value.

    Mike
  • Have you thought of adding some coroutine-based version of RECV, or some other sort of solution that doesn't require manually calling RECV at the right times?
  • cgraceycgracey Posts: 13,050
    edited 2020-09-12 - 20:01:20
    Yes, this issue of needing to know WHEN data is ready is a huge issue. You don't want to stall everything. This is why I didn't implement RECV earlier. It wasn't clear to me how it could work as needed.

    What Mike pointed out, using -1 for no-data-ready and 0..$FFFFFFFE for valid values is one way to do it.

    Another way could be to use the return value as a pointer to a variable, or even a structure, while a 0 could signal no-data-ready.

    I thought of making another routine to cover the is-the-data-ready issue, and also making RECV have a variable number of return values, but those solutions don't really offer much, while they certainly complicate things. It's like squeezing sand, where the sand (complexity) just squirts out somewhere else.

    Do you guys have any compelling reasons to change RECV() from what it now is?
  • msrobotsmsrobots Posts: 3,237
    edited 2020-09-12 - 22:53:34
    Well I think that returning a byte for a RX is good enough, any buffering can be done by the serial driver/keyboard driver/whatever driver.
    so -1 and 0-255 is good enough.

    The main point in my opinion is to have a unified way to send and receive, so that one is able to switch input/output easy from say serial to VGA/Keyboard.

    The WHEN issue can be solved by -1 (nothing there) as done before in various serial drivers.

    For multi byte input without stalling (say string input) one might need a buffer, what I liked to do is as simple protocol I stole from JD4portserial (I think) and that does return -1 for nothing, 0-255 for a byte and >255 is the address of a zero terminated string in HUB buffer to be returned after the complete string is entered.

    Sure one could extend RECV with multiple parameters, sort of Prompt "enter age" and receive a long but I think it would be overkill to provide support for that in the RECV command.

    Actually even the send thing does way too much there because it also may stall when connected to a slow output device.

    But the main point for both is to unify objects.

    Mike
  • roglohrogloh Posts: 2,678
    edited 2020-09-13 - 01:14:09
    Keeping RECV simple is best if we want this to be portable across many objects and it's worth taking the time to come up with something good.

    I quite like the idea of RECV taking a pointer to where the received data will be copied and returning true/false to indicate if the data was copied there. It remains somewhat generic and also doesn't put any restriction on the data size and it allows the actual RECV returned value to be used for multiple purposes like indicating that data was copied, how much more data might still be buffered in case the driver is buffering it (which could be useful for responding to flow control), as well as allowing for things like special codes for end-of-file conditions or indicating received data has extended scan codes, or extra modifier keys etc.

    I think it would be important to have RECV compatible with the USB keyboard driver and serial port data, which will be common uses of it. Having RECV work from data redirected from input files might also be important.

    Eg. to just wait for a char it is simply this:
    repeat until RECV(@ch)
    
    or to poll in the background:
    repeat
     ' background work
    until RECV(@ch)
    ' process char ch
    

    The approach in general is compatible with byte/word/long data which is handy for any custom use, though for software portability it will most likely converge to bytes for interactive data.

    If the RECV value was false, the RECV method could still also copy the reason why nothing was received into the byte pointed to by @ch, making that information accessible to the caller too. Eg. you could have things like this...
    0 - no data ready
    1 - end of file (EOF)
    2 - other device error, USB disconnect etc
    etc

    So for file use this would be very handy...you could do things like this:
    ' an "open" method in some file object could open a file handle requested
    ' the RECV function is mapped to an address by calling reader(handle)
    ' which selects the file for reading and also returns the recv function to use
    
    ' open file and setup receiver
    handle := file.open("filename")
    RECV := file.reader(handle) ' Note: "reader" returns a pointer to file RECV method
    
    ' begin using RECV on file data
    repeat while RECV(@ch)
     ' process file data read
    

  • rogloh wrote: »
    Keeping RECV simple is best if we want this to be portable across many objects and it's worth taking the time to come up with something good.

    I quite like the idea of RECV taking a pointer to where the received data will be copied and returning true/false to indicate if the data was copied there. It remains somewhat generic and also doesn't put any restriction on the data size and it allows the actual RECV returned value to be used for multiple purposes like indicating that data was copied, how much more data might still be buffered in case the driver is buffering it (which could be useful for responding to flow control), as well as allowing for things like special codes for end-of-file conditions or indicating received data has extended scan codes, or extra modifier keys etc.

    Making the RECV return value have different possible interpretations would defeat the purpose of having RECV be portable across different objects. A similar problem would come up if the pointer passed to RECV could be different sizes; then there would have to be different kinds of RECV methods (one for bytes, one for words, etc.). It's not an insurmountable issue, but it complicates things.

    I think keeping everything as simple as possible is important. In the compiler, let's just make RECV() return an integer, period. The interpretation of that integer is up to the application, but as a general convention having -1 mean "nothing available right now" seems reasonable. Whether RECV is blocking or non-blocking depends on what pointer you assign to it.

    For a USB driver or file I/O object, wrap the driver state and buffer up in the object: that's the thing that knows whether we need buffered I/O or not, how big the blocks are, etc.
  • The problem with returning a buffer in a standard RECV call is the buffer needs to be reserved. How do you do this in a generalised routine, where is it, what size.
    I’ve encountered these problems when i tried to do it in the ROM. That’s why there is an initialise routine which passes the buffer location. Size needs to be passed in a general routine. Eg SD will require 512 bytes.
  • ersmith wrote: »
    I think keeping everything as simple as possible is important. In the compiler, let's just make RECV() return an integer, period. The interpretation of that integer is up to the application, but as a general convention having -1 mean "nothing available right now" seems reasonable.
    Yes agreeing on return sizes for the compiler is important. The long data can still carry a byte fine anyway and would be the obvious container size to choose even if the underlying data is smaller.

    For portability across objects what do we expect RECV is mainly going to be used for? I imagine it is likely to take input data from keyboards, serial ports, data streams from network connections and possibly also redirected from internal file/flash data for assisting with automation/testing or other command batching purposes etc. If that assumption is correct then this is mainly going to be simple byte oriented data, maybe with some extra complications when receiving keyboard data (unless terminal type escape sequences are used perhaps for supporting things like cursor keys which would be one a solution to keep it working with byte streams only).

    Are there any other applications people want or need for RECV beyond interactive control where having separate paths for receive data and the receive status would be useful, or will the combined path be sufficient for everything expected?

    @Cluso99 , in my original example only a single element would be expected to be copied into the address you pass to RECV so you won't blow the buffer size. Another internal buffer contained within the driver itself could be receiving the incoming data in the background if required by the type of device.
  • I like it simple, the way it is.

    RECV() takes no parameters and returns one value.
  • I like it simple, the way it is.

    I agree, Chip.
  • Jeff MartinJeff Martin Posts: 638
    edited 2020-09-15 - 13:28:36
    Jon, thanks for your "SEND Command, A Short Tutorial;" it's exactly the effort we need. I enjoyed reading it; reinforcing my understanding of SEND as well.
    JonTitus wrote: »
    The program will now pass the first parameter, Flash(), to the SetLED method. The Flash() method will run and send its own values, $00, $FF, $00, to the LEDs two times. If you watch the LEDs, they next display $AA next. Why?

    The Flash() method returns the value $AA to the SEND statement: SEND(Flash(),$01,$02... In effect the $AA value gets inserted into the list of parameters so that it executes as if you had written:

    SEND(Flash(), $AA, $01, $02...)

    The Flash() method inherited the SetLED() address and can use it independent of other uses in this program.

    I understand what you're saying, but I think this isn't quite correct. (Please correct me if I'm wrong, Jon, Chip, or anyone.) The program doesn't pass Flash() to the SetLED method, but rather calls the Flash() method (since it's the first parameter) and passes the return value from Flash() to the SetLED method after Flash() has fully executed. Since Flash() contains its own call to SEND, those parameters are sent to SEND first before Flash() returns and the original SEND continues.

    The effect is execution as if the SEND(Flash(), $01, $02...) had really been:

    SEND($00,$FF,$00)
    SEND($00,$FF,$00)
    SEND($AA, $01, $02...)

    [edited to fix my mistake in "The effect..." line]


  • Jeff, you're right. I didn't catch that.

    Jon, maybe you could edit the initial post and your source document.
  • cgracey wrote: »
    I like it simple, the way it is.

    RECV() takes no parameters and returns one value.

    Maybe it would be nice to return two longs, one as actual value to be returned and a optional second one for status?

    And that brings up the optional parameter values fastspin supports. It would be REALLY nice if your spin2 could support that too.

    I think the current version of eric works like this
    PUB doSomething(alpha, beta, gamma=5)
    ...
    

    and if you just provide the first two parameters the last one gets populated with 5.

    This is very helpful.

    For example my two port serial driver has tx defined as
    PUB tx(value, port=0)
    
    so you can write
    
    tx(byte)
    
    equivalent to 
    
    tx(byte,0)
    
    but are able to do tx(byte,1) to access the second port.
    
    

    sure one can define tx1(value) and tx2(value) but this is sort of cumbersome.

    Enjoy!

    Mike
  • Jeff and Chip, please check updated info. Thanks for your changes, Jeff.
  • @JonTitus I read it and noted a mistake I had made (my "original" line was the "augmented" result; oops!), plus the original description still remained afterward. So, I went ahead and edited it directly in the first post to better merge yours and my explanation together. Please review and make any improvements you see necessary.
  • That looks correct now. There is one issue, though. Those LEDs are active-low on the P2 Eval board. So, The x would have to be inverted in SetLED.(x).
  • OK, Chip. I used external LEDs on a breadboard. I'll add the inversion.
  • JonTitus wrote: »
    OK, Chip. I used external LEDs on a breadboard. I'll add the inversion.

    Maybe best to note that the P2 Eval board uses inverted signalling on its 8 LEDs, in which case SetLED(x) would do a !x in the PINWRITE.
  • Added that info, too.
Sign In or Register to comment.