The new DEBUG in the SPIN2 Documentation lists several useful functions for Decimal to Binary to Hex conversions. Is there a link or routine or other??? to allow these functions can be directly used with SPIN2 via the VGA / SEND commands?
These are just part of the DEBUG system and can't be used outside of it. I could add such functions to Spin2. They would always take space, in that case, though.
For the DEBUG system, all related code and data get tucked into the too 16KB of RAM and then write-protected from non-debug access.
That would be great - Something like
PUB UDEC_LONG( value )
but only if the DEBUG code can be easily transposed into SPIN2 functions, and regardless of memory space.
and bundling it like the original SimpleNumbers.spin of P1 fame
The current interpreter completely fills the cog and LUT RAM, so it may be tricky to add features. With unsigned modulus and divide operators, its easy enough to write a method to convert any value to a string. My jm_nstr.spin2 object does everything accept unsigned decimal, so I'm going to add this to it.
dat
nbuf byte 0[16]
pub udec(value, digits) : p_str | len, d
bytefill(@nbuf, 0, 16) ' clear buffer
p_str := @nbuf ' point to buffer
digits := 0 #> digits <# 10 ' fix digits
len := 0
repeat
d := value +// 10 ' get a digit
byte[p_str++] := d + "0" ' convert to ASCII
value +/= 10 ' remove digit from value
if (digits) ' specified length?
if (++len == digits) ' done?
quit
else
if (value == 0) ' done?
quit
byte[p_str++] := 0 ' terminate the string
return revstr(@nbuf)
pub revstr(p_str) : result | first, len, last
'' Reverse the order of characters in a string.
result := first := p_str ' start
len := strsize(p_str) ' length
last := first + len - 1 ' end
repeat (len >> 1) ' reverse them
byte[first++], byte[last--] := byte[last], byte[first]
If you have a .str() method, you can do this:
serial.str(udec(somevalue, 0))
The second parameter is the number of digits to print; 0 is auto width, 1..10 limits the width (and may truncate).
Here's a slightly different approach that uses SEND directly. The ers_fmt and SmartSerial objects are attached, a demo is:
CON
_clkfreq = 180_000_000
OBJ
f: "ers_fmt"
ser: "SmartSerial"
PUB demo(): i
ser.start(115_200) ' pick your baud rate
send := @ser.tx ' set function to send one character
send("hello", f.nl())
repeat i from -2 to 3
send("i=", f.dec(i), " hex=", f.hexn(i, 2), " unsigned=", f.unsdec(i), f.nl())
That's a pretty clean way to do it Eric. Nice one.
Another extension I just thought of when looking at this might be if the send function was itself also format aware as it outputs characters, we could possibly use that to support format control string portions like this for C programmer types... it's somewhat of a hybrid approach compared to normal printf, working by splitting into multiple format strings. e.g.
send("i=%d", i, " hex=%2x", i, " unsigned=%u", i, "\n")
The sending function would need to look out for the % escape or \ chars and process the next item accordingly, but you could only do one escape per string at the end of it and no other characters could follow that until the next one. In the past I've tried to get send to take a single format string at the start but you do run into issues capturing the full string before all the data and also differentiating the chars from integers etc. Downside with the above is that the sender would need its own formatter object/capabilities (or its own send redirect control to other senders) which could add code overhead. Your method is clean and easily extended to other formats (if somewhat more verbose), and I like it. Maybe unsdec could be renamed udec, which reads better (to me anyway). EDIT: oops that name would clash with Chip's use above.
By the way I tested the maximum number of send arguments, it seems to be limited to 15 in the version of FastSpin I had.
I also wonder if send and a custom formatter can be used for to gain some type of rudimentary sprintf capability. We could have a simple string generation object based on Eric's formatter that takes a pointer to where it needs to write to next, an optional limit, and returns the address of its own sending function which then accumulates the bytes into the string, as called by send(). It would just need to maintain its own pointer/limit context per COGID and restart whenever character 0 is printed or a new string is setup with sprint, and optionally stop output once the limit is reached, until a new string is setup perhaps.
This could be handy to construct dynamic strings without lots of extra baggage to code up on the calling side, though being invoked per character it could be slower to run vs directly using other fixed strings and mem copies etc.
OBJ
fmt: "stringfmt"
PUB demo() : x
x:=20
send := fmt.sprint(@strbuf1, 100) ' returns address of string printing format routine for send
send("hello, x= ", fmt.dec(x), 0)
send := fmt.sprint(@strbuf2, 50)
send(fmt.hex(20), 0)
DAT
strbuf1 long 0[100]
strbuf2 long 0[50]
My second idea above didn't work without extra changes because the format code returns 0 after printing which also gets written into the string. @ersmith you may find you are sending nulls out the serial port with your code as a side effect.
I found I could eliminate it if I filtered the tx routine to ignore values outside ascii range from 0-255 and just return -1 from each format routine. Then when this return value is also passed through to send() it will filter the -1. It's working now, files attached.
Another extension I just thought of when looking at this might be if the send function was itself also format aware as it outputs characters, we could possibly use that to support format control string portions like this for C programmer types.
That's an interesting idea. It could be done with a kind of double indirection, something like:
send := fmt.printfsend(@ser.tx)
where the fmt.printfsend would itself have to call the original method pointer. Not sure if it's worth it though.
By the way I tested the maximum number of send arguments, it seems to be limited to 15 in the version of FastSpin I had.
I also wonder if send and a custom formatter can be used for to gain some type of rudimentary sprintf capability. We could have a simple string generation object based on Eric's formatter that takes a pointer to where it needs to write to next, an optional limit, and returns the address of its own sending function which then accumulates the bytes into the string, as called by send().
I'd do that by having a separate string buffer class that appends characters to the string, and then using the original formatting class with SEND set to the "addchar" method. Something like:
OBJ
fmt: "ers_fmt"
str: "stringbuf"
PUB demo() : x
' initialize string buffer with 100 characters
str.init(@strbuf1, 100)
send := @str.addchar
x:=20
send("hello, x= ", fmt.dec(x), 0)
DAT
strbuf1 long 0[100]
My second idea above didn't work without extra changes because the format code returns 0 after printing which also gets written into the string. @ersmith you may find you are sending nulls out the serial port with your code as a side effect.
That's a fastspin bug, it doesn't happen in PNut . SEND() in fastspin isn't ignoring the dummy return value of 0 for void functions. That was working at one time, but it looks like I broke it. It's fixed again in github now.
SEND() in fastspin isn't ignoring the dummy return value of 0 for void functions. That was working at one time, but it looks like I broke it. It's fixed again in github now.
Good, thanks Eric. Glad it is fixed so we don't have to hack the format stuff to use special return codes. Separating string printing stuff into another independent string related object should work cleanly with SEND fixed now so those extra NULLs don't make it into the string prematurely.
Actually I think having a good string manipulation library or a nice suite of methods for P2 would be good, particularly once dead code removal is working (I know Chip wants to add that add some point to PNut SPIN2 so hopefully we can count on this). There really isn't much string processing inherently built into SPIN2 apart from strsize(), and strcomp() so it sort of needs to be added by each user right now.
Using some small amounts of inline PASM2 could work out reasonably well for special character searching or other token parsing on the P2, especially in the interpreted version. The sort of things available in <string.h> for example. This could help things like file parsers and other text processing applications work at higher speed on the P2.
This formatting feature with SEND is really great one Eric and was a missing link to help me port my driver code (which now works on PNut and Fastspin) along with your new SmartSerial code that also now compiles in PNut. I had previously written some test stuff using your older SmartSerial which had a printf capability with variable args but that was not available in PNut and until now it has held me back getting things tested out better with PNut.
Using this approach my test programs and drivers can now work with both Fastspin and PNut tools and now I should be able to directly send formatted strings/numeric stuff into my video buffer region(s) or serial port using SEND and your format object. It's very cool now, thanks so much for this!
Comments
For the DEBUG system, all related code and data get tucked into the too 16KB of RAM and then write-protected from non-debug access.
PUB UDEC_LONG( value )
but only if the DEBUG code can be easily transposed into SPIN2 functions, and regardless of memory space.
and bundling it like the original SimpleNumbers.spin of P1 fame
If you have a .str() method, you can do this: The second parameter is the number of digits to print; 0 is auto width, 1..10 limits the width (and may truncate).
Another extension I just thought of when looking at this might be if the send function was itself also format aware as it outputs characters, we could possibly use that to support format control string portions like this for C programmer types... it's somewhat of a hybrid approach compared to normal printf, working by splitting into multiple format strings. e.g.
The sending function would need to look out for the % escape or \ chars and process the next item accordingly, but you could only do one escape per string at the end of it and no other characters could follow that until the next one. In the past I've tried to get send to take a single format string at the start but you do run into issues capturing the full string before all the data and also differentiating the chars from integers etc. Downside with the above is that the sender would need its own formatter object/capabilities (or its own send redirect control to other senders) which could add code overhead. Your method is clean and easily extended to other formats (if somewhat more verbose), and I like it. Maybe unsdec could be renamed udec, which reads better (to me anyway). EDIT: oops that name would clash with Chip's use above.
By the way I tested the maximum number of send arguments, it seems to be limited to 15 in the version of FastSpin I had.
This could be handy to construct dynamic strings without lots of extra baggage to code up on the calling side, though being invoked per character it could be slower to run vs directly using other fixed strings and mem copies etc.
I found I could eliminate it if I filtered the tx routine to ignore values outside ascii range from 0-255 and just return -1 from each format routine. Then when this return value is also passed through to send() it will filter the -1. It's working now, files attached.
I'm not seeing that in 4.3.0.
I'd do that by having a separate string buffer class that appends characters to the string, and then using the original formatting class with SEND set to the "addchar" method. Something like:
That's a fastspin bug, it doesn't happen in PNut . SEND() in fastspin isn't ignoring the dummy return value of 0 for void functions. That was working at one time, but it looks like I broke it. It's fixed again in github now.
Actually I think having a good string manipulation library or a nice suite of methods for P2 would be good, particularly once dead code removal is working (I know Chip wants to add that add some point to PNut SPIN2 so hopefully we can count on this). There really isn't much string processing inherently built into SPIN2 apart from strsize(), and strcomp() so it sort of needs to be added by each user right now.
Using some small amounts of inline PASM2 could work out reasonably well for special character searching or other token parsing on the P2, especially in the interpreted version. The sort of things available in <string.h> for example. This could help things like file parsers and other text processing applications work at higher speed on the P2.
Using this approach my test programs and drivers can now work with both Fastspin and PNut tools and now I should be able to directly send formatted strings/numeric stuff into my video buffer region(s) or serial port using SEND and your format object. It's very cool now, thanks so much for this!