@evanh said:
File size for -O1 is 84_844 bytes
File size for -O2 is 81_980 bytes
That was for my custom loaded 4-bit SD mode driver.
For the built-in SPI mode driver the sizes are:
File size for -O1 is 71_592 bytes
File size for -O2 is 69_208 bytes
So -O2 shrinks the compiled binary file size by 2864 - 2384 = 480 bytes extra when having the driver as a separate object. Given the custom driver is bigger, a decent amount of that could just be optimising the driver itself.
One was a non-existent symbol being referenced in my driver. I've fixed the bug but am puzzled as to why it didn't produce a compile error when compiling the C version of the SD tester program.
Perhaps the function containing the non-existent symbol was never referenced? If so it can sometimes be skipped at a very early stage so we don't even see those kinds of errors. This is less likely to happen when the function is in an object/struct (because we have to assume all public functions in an object might be called).
The other is include/fcntl.h:56: error: syntax error, unexpected identifier 'mode_t' It's missing an include of it's own I guess. My current fix is to shift the order of includes in my driver. Again, the C version of the tester didn't produce any error here even though it's the identical driver code.
EDIT2: Come to think of it. A compile warning that I'd earlier dismissed shows up this same way - include/filesys/fatfs/fatfs_vfs.c:96: warning: mixing pointer and integer types in return: expected pointer to _struct__vfs but got int
These are both bugs in the header files. I've checked in some fixes. Thanks for catching these!
One was a non-existent symbol being referenced in my driver. I've fixed the bug but am puzzled as to why it didn't produce a compile error when compiling the C version of the SD tester program.
Solved! The driver was pilfering from an identical named Enum at the top level program. They were also the same values so it didn't cause any buggy behaviour. The Spin2 CON symbols at top level were named differently so the bug only showed up then. I'd revised the Spin2 program as I wrote it so there is some minor tweaks like that.
I've changed the C tester program to now use the same Enum symbol names as the Spin2 tester program.
Huh, that kind of begs the question, can I legally use Enums at the top level to set driver compile time presets?
Is printf() blocking? I.e., does it not return until the string is completely sent?
Or, is it doing something with buffers and/or interrupts to return before string is sent?
I presume the one-deep hardware buffer will be used but, either way, it's still going to be blocking on a whole string.
The Flex compiler doesn't support interrupts.
There is a number of features of the Prop2 that require multi-instruction atomic actions. eg: SETQ+RDLONG for a fast copy. An interrupt splitting them would bust that. The Cordic's pipelining is timing sensitive, an interrupt could lose Cordic results.
There's other examples but suffice to say there is need for careful explicit shielding of each and every case before interrupts can be allowed to operate on the same cog.
EDIT: Err, the SETQ might shield one subsequent instruction automatically.
The hidden Q register has to be managed though. It can be trashed by a number of instructions. If an ISR uses Q, even inadvertently, it prolly needs to stash and restore Q's content. This wasn't considered during design so there isn't an explicit instruction for reading Q. MUXQ can be configured to do it though.
Looking at my SD block device driver code the CRC processing would need modified to be friendly to interrupts. Not because it won't work but because it naturally shields from interrupts for a very long interval using a large REP loop.
if_z rep @.rend, #512/4+16/8 // one SD block + CRC
if_z alti pb, #0b100_111_000 // next D-field substitution, then increment PB, result goes to PA
if_z movbyts pa, #0b00_01_10_11 // byte swap within longword
if_z splitb pa // 8-nibble order swapped to bit order of 4 bytes
if_z setq pa
if_z crcnib crc3, poly
if_z crcnib crc3, poly
if_z crcnib crc2, poly
if_z crcnib crc2, poly
if_z crcnib crc1, poly
if_z crcnib crc1, poly
if_z crcnib crc0, poly
if_z crcnib crc0, poly
.rend
130 loops of 12 instructions ... 130 x 24 + 2 = 3122 sysclock ticks.
Possibly the only shielding I'd need to add is for five instances of this across the whole driver:
xinit m_align, #0 // lead-in delay from here at sysclock/1
setq v_nco // streamer transfer rate (takes effect with buffered command below)
xzero m_dat, #0 // rx buffered-op, aligned to clock via lead-in
dirh p_clk // clock timing starts here
wypin clocks, p_clk // first pulse outputs during second clock period
Just have to add a REP #5,#1 ahead of each XINIT.
Might have to also reorder a few instructions where I'm currently counting on an instruction to not take effect instantly.
My SETQs are all safe I think and I don't use the Cordic in the Pasm code, therefore it shouldn't matter if Q got trashed by an ISR - not speaking for any of the C code though.
@evanh said:
There is a number of features of the Prop2 that require multi-instruction atomic actions. eg: The Cordic's pipelining is timing sensitive, an interrupt could lose Cordic results.
The flexspin code generator never does anything like that on its own. As long as the ISR doesn't touch CORDIC or FIFO state, everything should be fine. However, it will generate REP loops, block fills and other interrupt-blocking sequences. You could set up an ISR by writing it in assembly and manually loading it into the explicitly free LUT area. But unsupported at your own risk etc. You may also find that flexspin has slightly relaxed memory semantics (i.e. a value that is semantically in hub RAM may be cached in a register when it is used multiple times) but that's also visible to regular multi-core code.
@Rayman said:
Is printf() blocking? I.e., does it not return until the string is completely sent?
Or, is it doing something with buffers and/or interrupts to return before string is sent?
It depends on what file the printf output is going to. For the default case (serial output) it blocks until the last character has been put in the smartpin output buffer; it'll then take a little longer for that character to actually get to the host.
Eric,
I want to duplicate, in Spin2, a technique that I've copied of yours where, in C, you created some magical way to have the SD driver as a local scoped pointer to malloc()'d memory. Yet, the driver still has to be brought in at compile time. Which I'm scratching my head over.
@evanh said:
I want to duplicate, in Spin2, a technique that I've copied of yours where, in C, you created some magical way to have the SD driver as a local scoped pointer to malloc()'d memory. Yet, the driver still has to be brought in at compile time. Which I'm scratching my head over.
An object is just (1) a set of methods, and (2) some memory for the member variables (and also (3) some static DAT section, but that's shared by all instances of an object so it isn't very interesting). In C/C++ terms a Spin object is just a C++ class, or equivalently a C struct with some associated functions. So to get a dynamically allocated instance of an object you just have to make sure you have enough memory for the member variables (which sizeof() can tell you).
I can't remember if the official Spin2 compiler supports object pointers, but flexspin does, and the syntax is like:
OBJ sdmm = "blkdrv/sdmm_bashed.cc" ' = instead of : to declare an "abstract" object
VAR long sdptr
...
sdptr := _gc_alloc_managed(sizeof(sdmm)) ' get space for member variables
sdmm[sdptr].open(CLK_RL, CS_RL, MOSI_RL, MISO_RL)
One thing to watch out for is that the allocated memory is not initialized to 0, so if your methods expect this you have to set it manually.
Having said all that, why not just use C? You can mix C and Spin2 freely, so there's no great need to translate this kind of thing into Spin2. It's more conveniently expressed in C, and you can provide whatever API you want for Spin2 programs to call it.
@ersmith said:
An object is just (1) a set of methods, and (2) some memory for the member variables (and also (3) some static DAT section, but that's shared by all instances of an object so it isn't very interesting). In C/C++ terms a Spin object is just a C++ class, or equivalently a C struct with some associated functions. So to get a dynamically allocated instance of an object you just have to make sure you have enough memory for the member variables (which sizeof() can tell you).
I must admit I'm struggling with the OOP side of things. I never learnt C++. Thanks for some descriptions.
I can't remember if the official Spin2 compiler supports object pointers, but flexspin does, and the syntax is like:
```
OBJ sdmm = "blkdrv/sdmm_bashed.cc" ' = instead of : to declare an "abstract" object
VAR long sdptr
...
It's not the instancing that I'm after here. The way you did it in C looked like you were intentionally hiding the object existence inside a function's local namespace. Because, aside from the initial device open call, the object methods of the driver are only meant to be accessed via your underlying VFS layer.
Having said all that, why not just use C? You can mix C and Spin2 freely, so there's no great need to translate this kind of thing into Spin2. It's more conveniently expressed in C, and you can provide whatever API you want for Spin2 programs to call it.
I wanted to be able to provide an example integration of the driver plug-in for people coding in just Spin2.
@ersmith said:
I can't remember if the official Spin2 compiler supports object pointers, but flexspin does, and the syntax is like:
```
OBJ sdmm = "blkdrv/sdmm_bashed.cc" ' = instead of : to declare an "abstract" object
VAR long sdptr
...
It's not the instancing that I'm after here. The way you did it in C looked like you were intentionally hiding the object existence inside a function's local namespace. Because, aside from the initial device open call, the object methods of the driver are only meant to be accessed via your underlying VFS layer.
Oh, no, I was just being lazy. The direct C equivalent to the Spin2 code would be something like:
I just combined the typedef and the use of SDMMObj into the same line.
Your static object approach seems perfectly reasonable and probably more idiomatic as Spin2. Thinking about it it's probably better to avoid dynamic allocations anyway.
Ah, wrong direction. The C code of yours that I've worked from is in include/filesys/fatfs/fatfs_vfs.c
As far as I can see, you've created a local namespace instance with the purpose of hiding the object from all else. Then only the assigned VFS function pointers remain afterwards.
@evanh said:
Ah, wrong direction. The C code of yours that I've worked from is in include/filesys/fatfs/fatfs_vfs.c
As far as I can see, you've created a local namespace instance with the purpose of hiding the object from all else. Then only the assigned VFS function pointers remain afterwards.
No, that was what I was talking about. There was no particular desire (or need) to hide the object from anything else. The line:
and the typedef can then be pulled out of the function without really changing anything. I mean, yeah, the typedef is then in the file namespace, but that shouldn't interfere with what any other file has.
Spin2's OBJ foo = "filename" acts the same as a typedef, creating an alias for the object without actually creating any instances of the object.
@Rayman said:
Way back when, got fatfs to compile as add on to flexC . Can probably still do that and have all the usd you want…
If that's directed at me, I don't think I ever got it working properly ... nope, even now I can open two different drivers and copy between them just fine. But if I try to open two instances of the same driver then the first of the two instances is gone/unusable.
EDIT: I seem to have got it fully working with the sdmm_bashed.cc driver. And also ripped and tweaked version of sdmm.cc works too.
The following code mounts fine and works. The second driver source file sdsd2.cc is literally a duplicated sdsd.cc. However, if I change it to using "blkdrvr/sdsd.cc" for both drv1_t and drv2_t then sd2/ appears to replace sd1/
EDIT: Err, interesting. If I use a simpler driver then multiple instances do work!
@evanh said: The following code mounts fine and works. The second driver source file sdsd2.cc is literally a duplicated sdsd.cc. However, if I change it to using "blkdrvr/sdsd.cc" for both drv1_t and drv2_t then sd2/ appears to replace sd1/
EDIT: Err, interesting. If I use a simpler driver then multiple instances do work!
If you want to post the latest version of the more complicated driver we could try to figure out why it doesn't. Generally as long as you avoid static variables it should be OK to have multiple instances.
I gothad lots of statics in driver namespace, none inside functions. I thought the whole struct __using thing hid all that.
EDIT: Right, thanks, all fixed up now. Tested and working. Converting those DMA parameter structures into non-statics meant I also had to change their respective Pasm code to use a runtime C assigned local pointer instead of compile-time symbolic address.
@evanh said:
I gothad lots of statics in driver namespace, none inside functions. I thought the whole struct __using thing hid all that.
static works in objects like it does in C++ classes, namely it defines something in the DAT section that's shared among all instances of the object. Sometimes that's what you want, but it definitely isn't right for some things.
There is a way to assemble a 4-bit SD slot for testing if you don't mind getting out the soldering iron.
It does require having the components lying around of course. In particular you have to find an unused SD card reader from which to desolder the SD slot. This along with a 2x6 IDC female plug, some hookup wire and six resistors makes the complete assembly. Pictures here - https://forums.parallax.com/discussion/comment/1563018/#Comment_1563018
Comments
That was for my custom loaded 4-bit SD mode driver.
For the built-in SPI mode driver the sizes are:
File size for -O1 is 71_592 bytes
File size for -O2 is 69_208 bytes
So -O2 shrinks the compiled binary file size by 2864 - 2384 = 480 bytes extra when having the driver as a separate object. Given the custom driver is bigger, a decent amount of that could just be optimising the driver itself.
Perhaps the function containing the non-existent symbol was never referenced? If so it can sometimes be skipped at a very early stage so we don't even see those kinds of errors. This is less likely to happen when the function is in an object/struct (because we have to assume all public functions in an object might be called).
These are both bugs in the header files. I've checked in some fixes. Thanks for catching these!
Solved! The driver was pilfering from an identical named Enum at the top level program. They were also the same values so it didn't cause any buggy behaviour. The Spin2 CON symbols at top level were named differently so the bug only showed up then. I'd revised the Spin2 program as I wrote it so there is some minor tweaks like that.
I've changed the C tester program to now use the same Enum symbol names as the Spin2 tester program.
Huh, that kind of begs the question, can I legally use Enums at the top level to set driver compile time presets?
Is printf() blocking? I.e., does it not return until the string is completely sent?
Or, is it doing something with buffers and/or interrupts to return before string is sent?
I presume the one-deep hardware buffer will be used but, either way, it's still going to be blocking on a whole string.
The Flex compiler doesn't support interrupts.
Ok, I was thinking Flex doesn't allow interrupts because it uses them itself and doesn't wank conflict. But, maybe remembering wrong...
There is a number of features of the Prop2 that require multi-instruction atomic actions. eg: SETQ+RDLONG for a fast copy. An interrupt splitting them would bust that. The Cordic's pipelining is timing sensitive, an interrupt could lose Cordic results.
There's other examples but suffice to say there is need for careful explicit shielding of each and every case before interrupts can be allowed to operate on the same cog.
EDIT: Err, the SETQ might shield one subsequent instruction automatically.
The hidden Q register has to be managed though. It can be trashed by a number of instructions. If an ISR uses Q, even inadvertently, it prolly needs to stash and restore Q's content. This wasn't considered during design so there isn't an explicit instruction for reading Q. MUXQ can be configured to do it though.
Looking at my SD block device driver code the CRC processing would need modified to be friendly to interrupts. Not because it won't work but because it naturally shields from interrupts for a very long interval using a large REP loop.
130 loops of 12 instructions ... 130 x 24 + 2 = 3122 sysclock ticks.
Possibly the only shielding I'd need to add is for five instances of this across the whole driver:
Just have to add a
REP #5,#1
ahead of each XINIT.Might have to also reorder a few instructions where I'm currently counting on an instruction to not take effect instantly.
My SETQs are all safe I think and I don't use the Cordic in the Pasm code, therefore it shouldn't matter if Q got trashed by an ISR - not speaking for any of the C code though.
The flexspin code generator never does anything like that on its own. As long as the ISR doesn't touch CORDIC or FIFO state, everything should be fine. However, it will generate REP loops, block fills and other interrupt-blocking sequences. You could set up an ISR by writing it in assembly and manually loading it into the explicitly free LUT area. But unsupported at your own risk etc. You may also find that flexspin has slightly relaxed memory semantics (i.e. a value that is semantically in hub RAM may be cached in a register when it is used multiple times) but that's also visible to regular multi-core code.
Ah, yeah, my block driver uses the FIFO, with the Streamer. It would be catastrophic if an ISR used RDFAST for example.
Might be fun to do some interrupt jitter measuring ...
I also heavily use SETSE1 and SETSE2. An ISR using those would destroy the driver too.
It depends on what file the printf output is going to. For the default case (serial output) it blocks until the last character has been put in the smartpin output buffer; it'll then take a little longer for that character to actually get to the host.
Eric,
I want to duplicate, in Spin2, a technique that I've copied of yours where, in C, you created some magical way to have the SD driver as a local scoped pointer to malloc()'d memory. Yet, the driver still has to be brought in at compile time. Which I'm scratching my head over.
My example use:
An object is just (1) a set of methods, and (2) some memory for the member variables (and also (3) some static DAT section, but that's shared by all instances of an object so it isn't very interesting). In C/C++ terms a Spin object is just a C++ class, or equivalently a C struct with some associated functions. So to get a dynamically allocated instance of an object you just have to make sure you have enough memory for the member variables (which sizeof() can tell you).
I can't remember if the official Spin2 compiler supports object pointers, but flexspin does, and the syntax is like:
One thing to watch out for is that the allocated memory is not initialized to 0, so if your methods expect this you have to set it manually.
Having said all that, why not just use C? You can mix C and Spin2 freely, so there's no great need to translate this kind of thing into Spin2. It's more conveniently expressed in C, and you can provide whatever API you want for Spin2 programs to call it.
I must admit I'm struggling with the OOP side of things. I never learnt C++. Thanks for some descriptions.
It's not the instancing that I'm after here. The way you did it in C looked like you were intentionally hiding the object existence inside a function's local namespace. Because, aside from the initial device open call, the object methods of the driver are only meant to be accessed via your underlying VFS layer.
I wanted to be able to provide an example integration of the driver plug-in for people coding in just Spin2.
I guess I'll just stick with the static object approach I already have. eg: https://forums.parallax.com/discussion/comment/1565714/#Comment_1565714
Oh, no, I was just being lazy. The direct C equivalent to the Spin2 code would be something like:
I just combined the
typedef
and the use ofSDMMObj
into the same line.Your static object approach seems perfectly reasonable and probably more idiomatic as Spin2. Thinking about it it's probably better to avoid dynamic allocations anyway.
Ah, wrong direction. The C code of yours that I've worked from is in
include/filesys/fatfs/fatfs_vfs.c
As far as I can see, you've created a local namespace instance with the purpose of hiding the object from all else. Then only the assigned VFS function pointers remain afterwards.
Here's the original complete function:
No, that was what I was talking about. There was no particular desire (or need) to hide the object from anything else. The line:
is directly equivalent to:
and the typedef can then be pulled out of the function without really changing anything. I mean, yeah, the typedef is then in the file namespace, but that shouldn't interfere with what any other file has.
Spin2's
OBJ foo = "filename"
acts the same as a typedef, creating an alias for the object without actually creating any instances of the object.Hmm, why not just use a static C object then? No malloc()'s and it's less clutter in the source.
I think I wanted to provide for the (unlikely) possibility of having multiple SD cards in a system.
Way back when, got fatfs to compile as add on to flexC . Can probably still do that and have all the usd you want…
If that's directed at me, I don't think I ever got it working properly ... nope, even now I can open two different drivers and copy between them just fine. But if I try to open two instances of the same driver then the first of the two instances is gone/unusable.
EDIT: I seem to have got it fully working with the sdmm_bashed.cc driver. And also ripped and tweaked version of sdmm.cc works too.
The following code mounts fine and works. The second driver source file sdsd2.cc is literally a duplicated sdsd.cc.
However, if I change it to using "blkdrvr/sdsd.cc" for both
drv1_t
anddrv2_t
thensd2/
appears to replacesd1/
EDIT: Err, interesting. If I use a simpler driver then multiple instances do work!
If you want to post the latest version of the more complicated driver we could try to figure out why it doesn't. Generally as long as you avoid
static
variables it should be OK to have multiple instances.I gothad lots of statics in driver namespace, none inside functions. I thought the whole
struct __using
thing hid all that.EDIT: Right, thanks, all fixed up now. Tested and working. Converting those DMA parameter structures into non-statics meant I also had to change their respective Pasm code to use a runtime C assigned local pointer instead of compile-time symbolic address.
Finished driver, with tester programs - https://obex.parallax.com/obex/sdsd-cc/
static
works in objects like it does in C++ classes, namely it defines something in the DAT section that's shared among all instances of the object. Sometimes that's what you want, but it definitely isn't right for some things.Perfectly reasonable.
Thanks for the explain.
There is a way to assemble a 4-bit SD slot for testing if you don't mind getting out the soldering iron.
It does require having the components lying around of course. In particular you have to find an unused SD card reader from which to desolder the SD slot. This along with a 2x6 IDC female plug, some hookup wire and six resistors makes the complete assembly. Pictures here - https://forums.parallax.com/discussion/comment/1563018/#Comment_1563018