Can SPI in Simple Libraries be speeded up?
twm47099
Posts: 867
I want to try using the pixy SPI interface for my project with the ActivityBot in this thread:
http://forums.parallax.com/showthread.php/156970-Bluetooth-App-to-control-ActivityBot
So far I have used the pixy DAC to ActivityBot ADC which is good for tracking one object, and the UART mode to track up to 3 objects. According to the CMUCAM5 site, Pixy transmits at 1 u sec rate with a 20msec new Frame rate, and they recommend using SPI mode to capture as many Blocks of data (objects) per frame as possible. A fast transfer rate is needed to use their 'colorCode' mode that uses combinations of colors.
When I try SPI in C (from Simpletools library), the most I can capture is 2 Blocks before a new Frame starts (compared with 3 to 4 for UART).
I tried using the spin program "SPI Asm DEMO.spin" with the shiftin function just filling an array in one repeat loop and then subsequently analyzing the data array in a second repeat loop. I was able to get more than 6 blocks of data per frame. I had set the Timedelay value in SPI.Start() to 15 which gives a 1 u sec clock.
My questions:
1. Is the slow performance of SPI in the C Simple Libraries due to a clock setting (I was not able to find the source of the SPI)
2. If so, can it be changed to speed it up?
3. If so, how fast can it go, and how is it done?
(While the spin method works, I want to use C because of the other activitybot libraries, and because I don't know spin well enough to rewrite my code. -- maybe as a goal for the future, but not now.)
Thanks for your help,
Tom
http://forums.parallax.com/showthread.php/156970-Bluetooth-App-to-control-ActivityBot
So far I have used the pixy DAC to ActivityBot ADC which is good for tracking one object, and the UART mode to track up to 3 objects. According to the CMUCAM5 site, Pixy transmits at 1 u sec rate with a 20msec new Frame rate, and they recommend using SPI mode to capture as many Blocks of data (objects) per frame as possible. A fast transfer rate is needed to use their 'colorCode' mode that uses combinations of colors.
When I try SPI in C (from Simpletools library), the most I can capture is 2 Blocks before a new Frame starts (compared with 3 to 4 for UART).
I tried using the spin program "SPI Asm DEMO.spin" with the shiftin function just filling an array in one repeat loop and then subsequently analyzing the data array in a second repeat loop. I was able to get more than 6 blocks of data per frame. I had set the Timedelay value in SPI.Start() to 15 which gives a 1 u sec clock.
My questions:
1. Is the slow performance of SPI in the C Simple Libraries due to a clock setting (I was not able to find the source of the SPI)
2. If so, can it be changed to speed it up?
3. If so, how fast can it go, and how is it done?
(While the spin method works, I want to use C because of the other activitybot libraries, and because I don't know spin well enough to rewrite my code. -- maybe as a goal for the future, but not now.)
Thanks for your help,
Tom
Comments
Thanks, I remembered that there was a discussion, but I wasn't able to find it.
I was hoping that there was a way to change the clock speed to get 1MHz, but I guess not.
I've read that it is possible to translate a spin program that has PASM into C, but l'll have to do some more learning first.
Same with embedding PASM or even trying to translate the PASM Code into a C function.
But since learning something new is the reason I got involved with the Prop, it's a good thing.
Tom
There is no SPI library that I know of in the Simple Libraries.
This represents an opportunity for the adventurous programmer to contribute. Maybe one of the existing community libraries can be adopted?
Regardless of how it happens, I think it's good to understand what is functions are necessary to implement, and how one might go about developing a library in a reasonable way.
There is a tutorial on creating libraries, so it's hard to tell whether or not developing such an implementation is the subject of the Learn forum. If we try to keep a most helpful face and not stray from the topic, we're probably Ok.
Clearly the topic is the Parallax Simple Libraries which is written in C with Spin/PASM using SimpleIDE and Propeller C (propeller-gcc), so we should obviously stick to that. I'll help however I can with what little time I have.
Thanks for the info.
I tried to take the SPI tutorial and adapt it to read data from the Pixy (CMUCAM5). Getting the data from Pixy is actually easier because Pixy is always sending - no command is needed to initialize or start the transmission. However, that also causes the problem. There is no way to tell Pixy to start (or stop) sending a Frame of Blocks.
So the issue is getting the SPI read routine to read fast enough to collect a meaningful number of Blocks per Frame.
Today I looked up the shiftIn.c file and the supporting files in the SimpleTools library. I see that the code is all C (no PASM) and there doesn't appear to be any clock setting (just runs as fast as it can?) So I'm going to have to learn how to incorporate the PASM routine from the spin program I mentioned above. I know there have been threads about doing that. I'll have to find them and then I know I will have questions. I'd like to make a library to share, but since Pixy doesn't need all the features of a general SPI interface program, it may be to specialized and simple. I'm not sure if that strays too far from the Learn Forum goal.
Tom
If you have not already gone through the first half of Library Studies, make sure to try that first. It's here: http://learn.parallax.com/propeller-c-library-studies.
The second half of library studies is in the queue, and I planned to address writing code that links into PASM code at the end of it using a spiffed version of the attached as an example. I'll post a more extensive write-up on it, but for now, here are a few notes:
The Test BlinkPASM.side project behaves just about the same as /libblinkpasm/Test BlinkPASM Object.spin. They both declare two instances of the PASM light blinking process, configure them differently, and modify and monitor their behaviors.
The BlinkPASM.spin library has PASM and Spin interface methods. The most important thing to note is that there are three variables shared with the PASM code: maskLed, stateLed, and halfPeriod. The cognew(@entry, @maskLed) means that the address of maskLed will appear in the ASM par register. Since they are long variables, stateLed will be in address par + 4, and halfPeriod will be in address par + 8. The ASM code reads from and writes to them.
Next, open libblinkpasm.side, click the bottom-left Show/Hide Project Manager button, and double-click blinkpasm.h, blinkpasm.c, and BlinkPASM.spin. The blinkpasm.h file has the function prototypes, but also a structure with the same variables discussed in the spin file in the previous paragraph:
Declaring variables sequentially in C does not guaranty that they will be at sequential addresses, but declaring them within a structure does. Adding typedef to the beginning and blink_t to the end allows you to declare instances of this structure and then access them either directly or by address. To make the user experience as similar to Spin as possible, blinkpasm.c creates a pointer with blink_t *device, then sets aside memory for the structure with device = (void *) malloc(sizeof(blink_t)). The blinkpasm_start function returns that address so that the application can pass it to other functions to choose which instance of the process to monitor/modify.
That's what allows Test BliknPASM.c to say blink_t *blink[2] ... blink[0] = blinkpasm_start(26, CLKFREQ/2) ... blinkpasm_getState(blink[0]) ... blinkpasm_getState(blink[1]) ... blinkpasm_setRate(blink[0], CLKFREQ/8).
Back in blinkpasm.c, note that blink_t *device receives the pointer in blinkpasm_stop, getState, and setRate. Note also the functions address the blink_t type (the structure in the .h file) with device->stateLed, device->halfPreiod, etc. Those are shared with the PASM code in the same way as maskLed, stateLed, and halfPeriod in BlinkPASM.spin.
One other key thing to make note of is the way the PASM code gets launched. This excerpt from blinkpasm.c uses BlinkPASM (the .spin filename without the extension) in two places. Be careful to insert your filename sans extension just like that, in those two spots.
The second argument is a pointer to the starting address of your structure, which gets passed to the PASM code's par register, just as @maskLed did in the Spin code.
Sorry, this is really terse for a large amount of info to digest. There's lots of info queued for Learn site tutorials leading up to this. Feel free to ask lots of questions.
Andy
It's not a hack to make something work in Spin, it's just a convention that was created so that the user code accessing library code would get a 0 for failure or nonzero for success. That way, user code can say:
if(blinkpasm_start(...)) {code for success} else {code for failure}.
Andy
Thanks for the info. Doing a quick read of your post, I'd say it's well over my head. But it is something I've been wanting as I learn Prop C. It will take some careful reading and exploring. I've been wondering where structures would be useful (otherwise to a nubie it seems like a lot of typing -- maybe another future tutorial)
Is this method of including PASM the same as was used for the C version of full duplex serial? (It is useful to have a moderately complex working example.)
Tom
Yeah, Library Studies Part 2 will depend on a lot more background material, like passing pointers to/from functions, structures, typedefs and casting.
The FDS library actually has an extra level on top of what we have in blinkpasm because FDS is designed to play nice with the simpletext library. So, its structure also has some function pointers for serial transmit and receive that dprint, dscan, and friends can use. But aside from that, I think I actually started with FDS to make my first draft of an ASM example. It also had a button, and in this second example, I removed the button because the light blinking can still have two-way communication between cog and app.
As a starting point, examine Test BlinkPASM Object.spin and BlinkPASM.spin. Try following from the Test BlinkPASM Object.spin calls to BlinkPASM.spin Spin functions. Watch how those functions set and/or check global variables. Then, look at how cogstart sends the address of the first global variable to the ASM code's par variable. Keep the Propeller Manual on hand, and look up things things as you go. If you get stuck or would like clarification on any point, just ask in this thread. BTW, the posts about C + PASM and Spin + PASM will get migrated to a second thread soon.
Andy
if(blinkpasm_start(...) >= 0) {code for success} else {code for failure}.
and you'd have the advantage that you'd really be returning a COG ID.
Just an update --
I've run the Spin code (once I fixed my indentation mistake), and follow what both the PASM and spin code are doing. It's given me encouragement that I'll be able to follow the spin and PASM for the SPI objects.
The C code for the blinkPASM files seems pretty complex, and I will have to go through Part 1 of the library Tutorials slowly and carefully. I am looking forward to the Part 2 tutorial with all your explanations.
Tom
I recommend the 15 minute rule. If you can't answer a question after researching it for 15 minutes, ask it here. Then someone can show you how to find the answer. This can also help others find the answer. You will get an answer one way or another with less headaches (hopefully).
Andy mentions structures in C. Structures (also called records in other languages) are necessary for ordering data in memory. They also provide for convenient access using '.' dot or '->' pointer notation (don't let pointer give you a headache, it is actually quite easy). Andy should consider providing a pointer tutorial at some point.
One reason structures are necessary in C instead of using a set of variables is that the compiler is free to optimize code for us. If you have 4 variables in a row and rely on the position of any of them, then 1) the addresses can be completely backwards, and 2) if you don't use one of the variables, it will change the addresses. The alternative is to use arrays with indices to organize information ... which usually turns into horrible code. Also, using a struct makes working with a set of variables easier and less error prone.
Good deal. After Library Studies part 1, it'll seem more approachable, but there will still be some question marks. I'm working on filling those in, and will try to keep up with you.
Andy
Yep, it's at the top of my queue.
Thanks for the 15 minute rule, I'll certainly be asking questions.
Re structures, I've been using the alternate array method (not for ordering variables in memory, but for grouping variables by subject). And it does get unruly and requires a lot of commenting so that I can figure out what I did a week later.
Tom
Am I missing something?
If not, then next step is to write the c programs/headers for SPI_Asm.spin and see what happens.
Tom
Congratulations, you have made good progress. Yes, since the .spin file was added to the project's file list, it is part of the project. The .h and .c files exchange data with the PASM code the same way the spin methods in the object do. Keep in mind that if a variable needs to be shared with the ASM Code, it should be added to the struct in the .h file. Try looking up the code for each note in post #6. Some of it will start to make sense, but other parts will probably lead to more questions. Remember the 15 minute rule.
Andy
Andy,
My next step is to understand the SPI_Pasm.spin and demo programs. They don't appear to use the methods in the example of post 6 for synchronizing the variables between the Spin and PASM parts of the program.
I expect that I will have to read more of the Prop manual, and write out the steps of the PASM part (what each step is doing) for the intro part and the update_shiftin section.
I've thought about just adding a section to the spin code that declares the arg variables in relation to par, but I think I should try to understand what is going on first.
Question-- I don't see how 'value' in the spin SHIFTIN function gets associated with arg4 in the PASM code. Could you explain or point me to some info?
Thanks
Tom
If the app calls shiftout, shiftout in turn passes the address of Dpin to setcommand. The main ram byte address of Cpin is Dpin + 4. The address of Mode is Dpin + 8, etc through value, which is Dpin + 16
setcommand makes the command variable so that the address is carried in the lower 16 bits, and the command info is in the upper 16 bits. Remember, the address of command is what with to the ASM code in the par register, so the ASM watches to find when it gets populated with information.
Thanks for the info.
Last night I tried to understand the details of the beginning of the PASM code (up to the jumps label). I was not successful and came away with a lot of questions (such as why 'add :arg, d0' -- adding $200 to a label?, why such a complicated routine leading up to the 'jump t2', and more). But I think those need to be asked in the Prop 1 forum and probably after I learn some more about pasm.
Based on your last answer, I think the variables that need to be in the structure are:
Command
Clockdelay
Clockstate
Dpin
Cpin
Mode
Bits
Value
Flag
I'm not sure about what to do with the constants(MSBPRE, etc.). I think they can just be declared and intialized, but don't need to be in the structure. Is that correct?
I also think my next step is to port the spipasm.spin code to C, add a simplified version of the demo program (I don't have the temperature sensor that is used in the spin demo, so I will use Pixy for the demo / test harness code), and add the function prototypes into one file (the spin file with the pasm will be added to the project. Once I get that running, I will break the program into separate sections (header, test harness, spipasm.c) and build a library.
I expect to have questions as I go along.
If anything I've written doesn't make sense, please correct me.
thanks again
Tom
That sounds like a good plan. In the meantime, the PASM can be treated like a black box so long as we are clear on the variables and addressing in the Spin code.
Looks good. I'd recommend adding cog. Probably like this:
Excellent question. It's a little tricky because the enumeration for the same constants is different in the simpletools library. Let's start with this and see how it goes...
Agreed. I'd recommend using the blinkpasm project as a template. Do a Save Project As -> libspipasm, saving it into a folder named libspipasm. You can use a file program (Windows Explorer, Mac Finder) to rename blinkpasm.c to spipasm.c, and blinkpasm.h to spipasm.h. Make sure to copy SPI_Asm.spin into the folder too. Next, use SimpleIDE's bottom-left Show/Hide Project button to view the project's files. Click and delete blinkpasm.h, blinkpasm.c, and BlinkPASM.spin. Then, use Project->Open Tab to Project to add spipasm.h, spipasm.c, and SPI_Asm.spin.spin.
In spipasm.h, you'll to update #ifndef __BLINKPASM_H to #ifndef __SPIPASM_H and #define __BLINKPASM_H to #define __SPIPASM_H. After #ifdef __cplusplus, extern "C", {, #endif, add the following:
- The #define MSBPRE, LSBPRE, etc constants from the snippet above.
- The struct from above.
- Your function prototypes. Here is one way that might turn out to be useful:
Note that I did not add setcommand. I'd recommend adding its function prototype at the top of spinpasm.c. It'll probably have to take the form: void setcommand(spi *bus, int cmd, int *argptr);
A hint on the porting for spipasm.c.
1. In the blink program you used blink_t as the data type for the structure. I've seen other code that uses the _t suffix for a structure data type. I thought that it was necessary. Is that just an editorial convention? I had been useing spia_t in my code. But I like less typing.
2. I'm not sure if any of the function names have to be the same as those in the spin file. The function names in blinkpasm.c were the same. I did save SPI_ASM.spin into another file SPIAsm.spin since I wasn't sure if the underscore in the name would cause a problem.
3. I'm not sure if I called some of the parameters correctly in the C program.
-- In the functions spipasm_SHIFTOUT and spipasm_SHIFTIN I'm not sure how to write the address of Dpin in the calls to spipasm_setcommand.
-- In spipasm_SHIFTIN I'm not sure if I wrote the return of Value correctly.
-- in spipasm_SHIFTIN and in spipasm_setcommand I'm not sure if I wrote the 'while' correctly. The purpose of that command is to delay until the PASM clears 'Flag' (for the SHIFTIN) or 'command' for setcommand.
I didn't define the constants in this try, but just use the value in the function calls.
The code (very draft) is shown below. I've tried compiling it and it built ok (once I fixed the capitalization in the call to spipasm_SHIFTIN.) I haven't actually tried running it yet.
I did get some warnings. I copied them below the code.
I'm not sure about my use of parameters noted above.
Tom
Build messages:
1. The _t suffix is a shorthand indicating that it's a typedef and is considered a good coding practice. Likewise with the _st for structure names. When I want to make a more beginner-friendly API, I leave out those suffix because the user code becomes:
spi *bus = spi_start(etc...);
...which is a step down from:
spi_t *bus = spi_start(etc...);
2. The function names can be whatever you make them. The only thing the functions have to do is make sure a set of variables in the structure have the correct info before copying the address of the structure's Dpin element to the structure's command element. The examples I posted were (out of habit) experimenting with a user-friendly API style.
3.
The way you set up the return value in spipasm_SHIFTIN looks correct.
Notice that the compile errors are all complaining about argument 3 of the spinpasm_setcommand function, saying that they are receiving an int value instead of an int address. I think you can fix that by passing the address &device->Dpin instead of just the value at the device->Dpin address. In other words, just precede device->Dpin with an ampersand like this: &device->Dpin.
In the code below, the third argument is correctly set up to receive an address with int *argptr. But, cmd << 16 + *argptr is adding the value at the argptr address, not the address itself. I think it would be better to change it to device->command = (int) argptr | (cmd << 16). I would also change the add (+) to bit-wise or ( | ) in the expression because the intent of the operation is logical, not arithmetic. The spin code relies on the fact that both work if the target variable starts as zeros, but there are situations where you might try to use add (+) on a variable with bits that are already set, which will result in potential carry operations and unintentionally setting higher order bits in the variable. The bitwise or ( | ) operation will not carry to higher order bits.
Just some observations ....
At this point the library is still primitive. It is also more of an application than a library. At some point main needs to be split out of the library so that other applications having their own main can use the library. So the library, assuming a name like libspia, shoul have files like this:
- libspia.c : contains the "unit test" code main() for the library and only uses the spia_ functions.
- spia.c : contains the spia_ functions and global data if any.
- spia.h : contains the structure and other type definitions, forward function declarations, and documentation for the functions.
- SPIAsm.spin : contains the spin/pasm code.
- libspia.side : the project library which the IDE maintains.
About performance ....Just reading and writing one value at a time will never allow for the best possible performance because of function call overhead. One really needs to consider using buffers for reading and writing more than one byte at a time. I recommend something like this.
Please try to get into a habit of specifying what a function should do as above before writing the function. This accomplishes several important things.
Thanks for your help.
I thought that keeping every thing in the same file until I got it running would be best, then I'll break it into the library components.
I like the beginner's way better. Once it is working I may change it.
I wasn't sure. I still get mixed up on how this is working. I recommend stressing that in the future tutorial, especially if examples use the same function names as the spin object.
So the only thing that needs to be the same spelling is the spin file name in the 2 statements in the open function?
Can the spin file name have an underscore in it like the original API_Asm.spin did?
I made the changes to the calling functions (put & before device->) and to spipasm_setcommand (changed device->command = cmd << 16 + *argptr;
to device->command = (int) argptr | (cmd << 16);
but I was still getting warnings complaining about: I looked back at your examples and saw that you did not declare the dPin or cPin to be volatiles. So I deleted volatile from my definitions of the pins in the structure. It compiled w/o any errors or warnings. It will be later today before I have the opportunity to test with the Pixy.
The "error free??" code is posted below:
Jazzed' comment about filling multiple values was on my radar, but I have not checked if the PASM needs to be modified before that will be possible.
Our next task will be to test for runtime errors. There may well be mistakes in there that compile correctly, but result in memory or values being not quite perfect for the PASM code. Maybe it will run on the first try, but if not, I'm sure we'll be able to get it debugged.
I think it is safe to leave your code in this single file undocumented development version while getting the bugs out and making sure it functions well. Once we are past that, the next step would be to move the parts to the files and document them like in Jazzed' list, which is basically what you already did once in the Library Studies tutorial.
Thanks for the advice.
At this point the code is primitive. I wanted to use the steps in the "Library Studies" tutorial that started with a working application and developed a library from that. So my first step was to see if I could port the spin application to C. This spin program was a step more complex than the blinkpasm example. I was concerned about posting an unfinished work last night, but I felt I was starting to run around in circles due to my limited knowledge of pointers and how to use them (particularly after spending an hour tracking down a compile error that was just me not capitalizing part of a function name).
I was thinking about declaring the pins in the start function, but I wasn't sure if some SPI devices didn't use different I/O pins for different channels or something. The Pixy doesn't even use an input pin. I would expect the clock pin to always be the same for a given device. But since the spin model I was using included them in each shiftin or shiftout call I felt that I'd do the same. Also, at this stage I'm still trying to transition from crawling to walking, but once I get this running I will want to improve its efficiency and generality (maybe not in the same function).
I'm not even sure how I would go about using a buffer in this code without modifying the PASM. My clumsy use of the pixydata[] array in main() was an attempt to make sort of a buffer.
1. Use one loop to just collect a fixed number of bytes into an array - sufficient to collect two 20ms Frames of data,
2. Then have a subsequent loop that does something with the array -- in the example it just prints the values, in a pixy application it would have to find the 4 bytes that signal the start of a frame, then build the data words while checking for the start of a new block or frame).
I had made that modification in the original SPI_Asm_demo.spin and found that I was able to increase the number of Pixy blocks of data per Frame by a factor of 5 or more. (each block is the data for an object of a defined color or combination of colors)
Thanks for the advice on documentation. Usually, I do try to document the program before I write the code as an outline (particularly when I was writing forth, otherwise something I wrote at 3 AM would be a mystery when I woke up.) But I like the examples you gave. In this case I was using the spin program as the documentation and wanted to get the code and my questions posted.
Once I get the basic library working I will have some questions for you on how to optimize the code (in the Prop GCC or Prop1 forums).
Thanks again
Tom