SPIN2 SEND Command, A Short Tutorial
JonTitus
Posts: 193
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 will pass all parameters, including any return values from called methods, to the method SEND points to:
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:
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.
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.
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
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.
It may be however, I hope no one locks you up. LOL
@ JonTitus
Excellent!! Thanks Jon.
Today I added RECV, which gets inherited like SEND, but it takes no parameters and returns a single value.
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.
Mike
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?
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
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:
or to poll in the background:
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:
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.
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.
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.
RECV() takes no parameters and returns one value.
I agree, Chip.
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]
Jon, maybe you could edit the initial post and your source document.
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
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
sure one can define tx1(value) and tx2(value) but this is sort of cumbersome.
Enjoy!
Mike
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.