Shop OBEX P1 Docs P2 Docs Learn Events
SPI / SHIFTIN - High Speed — Parallax Forums

SPI / SHIFTIN - High Speed

parskoparsko Posts: 501
edited 2006-08-16 19:06 in Propeller 1
Hi all,

I have, more or less, given up on the search feature in the forums. For some reason, I've never been good at searching (Google is not my friend).

I created an SPI object to interface with a MCP3001 1 channel ADC using SPIN code. The fastest throughput I can get with 10 bits is 0.82ms or 1200 samples/sec. I have a desire to sample faster than that, say at 10,000 samples/second. So, this leads to high speed SPI.

The point above was to point out that I can't find a high speed (costomizable speed) SPI object using the Search engine in the forums. So, I need to write one myself.

Has this been done? Am I reinventing the wheel?

If not, please see the attached. I have included the MCP3001 timing diagram, along with my first start at an object. It is missing mostly everything, except for my outline of how to get it done. I'm not even sure THAT is correct.

Please have a look, as I feel this could be usefull to all in the future. I will not (admittedly) be able to do this by myself. I simply don't know enough about this stuff to make the corect connections/conclusions. But, I want to learn.

My goal:
Have is set-up so that an object can start this in a new cog, and have it update global variables. Also be able to have it capable of using multiple input ADCs. Also to set the bandwidth (baud rate).

Thanks,

-Parsko

Comments

  • Mike GreenMike Green Posts: 23,101
    edited 2006-07-30 15:38
    There are several SD/MMC card access routines that have been uploaded to the forum, one with an assembly routine for high speed transfers of a block of data. SD/MMC cards use SPI, so you should be able to modify the routines for your purposes. Keep in mind that the startup time for an assembly COG is substantial for what you want to do. Better to start up a routine and pass it the address of a 3 word common variable that tells it what to do. When the assembly routine is idle, it just loops looking at the first word of the variable until it changes say from -1 to something else. The second word would contain the current address for received data, and the third word would contain the count of data items left to receive. When the count goes to zero, the COG has finished and the SPIN program can process the data. You could even use two buffers, have the COG filling one while the SPIN program is processing the other. The first word (when not -1) might have an ADC channel number or something to tell the COG exactly what you want it to do.

    Scroll back through the threads looking for a mention of SD or MMC cards and I think you'll find the routines.
  • parskoparsko Posts: 501
    edited 2006-07-30 17:56
    Mike,

    Once I get the underlying assm SPI done, I will add a flag to the SPI code itself to "waitpeq", so it won't start until the pin is eiter high or low. When it sees this, it will run through a 4 input ADC routine, using semaphores (or equivalent) to keep the main program from messing with the variables. Dig?

    Regarding your suggestion: I have gone through all the posts with SPI and/or MMC, and no luck finding one that reads in SPI via an exclusive assembly program. The obvious links I found were:

    http://forums.parallax.com/showthread.php?p=597562
    http://forums.parallax.com/showthread.php?p=593778
    http://forums.parallax.com/showthread.php?p=588024
    http://forums.parallax.com/showthread.php?p=591406

    There's a few more I've found, but they weren't too helpful.

    Are these what you were refering to?

    -Luke
  • Mike GreenMike Green Posts: 23,101
    edited 2006-07-30 21:15
    In the first thread, there's a program by "David B" <http://forums.parallax.com/forums/attach.aspx?a=8496>. In this archive, there's a file "card.spin" which contains the assembly SPI routines. His program demonstrates how you can communicate from SPIN to assembly since his routine waits for "commands", then performs them. The actual raw SPI stuff is at the end, very short.
  • parskoparsko Posts: 501
    edited 2006-07-31 19:57
    Mike,

    I spent the better part of the day deciphering the timing diagram for the MCP3001. I wrote a sequence that would input the bits, high speed, using assy. I simply have to code it now. I am actually pretty proud of myself. Not only did I figure out this high speed SPI stuff, but I also conquored the Timing Diagrams. The downside: the email I sent to myself from work didn't make it to me... Dat is Jammer!!! The upside: being a paranoid MO-FO, I printed a copy, and brought that home. Who says email is guaranteed?!?

    Anyway, I did have one question I couldn't quite put my finger on;

    When I code the assm. line to write the bit to my global (or local) variable, what would that command be?

    Is assm. capable of writing one bit of a word at a time? Should/could I use an array to do this?

    Like I said, I now have the timing down, I just need to code. I think I might be able to get 50kps easy. We'll see...

    -Parsko
  • Mike GreenMike Green Posts: 23,101
    edited 2006-07-31 20:38
    1) When you update a global variable from assembly, do all the bit twiddling in assembly, then write the finished product to HUB memory using a WRBYTE or WRWORD or WRLONG instruction. When you initiate your assembly COG, you can pass a HUB address. Usually this is the start of a common work area (between SPIN and assembly).

    2) You usually use standard logical operations for bit manipulation. In assembly, really everything is a long. If I'm processing a serial bitstream from LSB to MSB, I usually load a variable with a %1 and shift it left with each cycle like this:
                  mov       data,#0
                  mov       mask,#1
                  mov       count,#8
    loop:       <test something, leave zero flag false if we want a one>
        if_nz   or          data,mask
                  shl         mask,#1
                  djnz       count,#loop
                  wrbyte   data,par
    
    
  • parskoparsko Posts: 501
    edited 2006-07-31 21:13
    This is odd, I feel like I am translating Dutch... but this is WAY easier, and actually logical.

    May I comment:


                      mov          data,#0           'data := 0
                      mov          mask,#1         'mask:= 1 or ...00000001 in binary
                      mov          count,#8         'count:= 8
    loop:                                                 'duh, the loop address
          in_nz    or              data,mask       'If Z-flag is #0, then or mask with data and write to data
                                                             '     When the Z-flag is 0, this won't happen??
                      shl             mask,#1         'Shift mask left by 1 bit
                                                             '   mask now = ....00000010 ??
                      djnz           count,#loop     'decrement count, if it is not 0, jump to loop
                      wrbyte       data,par          'write data to par in main memory
    
    



    I'm sorry, but I can't figure out how/where the bit is written??

    I might be taking your code a bit too literally. Please let me know if this is the case.

    I'm getting used to thinking in binary, but it's gonna take more practice and experience.

    -Parsko

    BTW - Thank you thank you thank you for the help thus far!
  • Mike GreenMike Green Posts: 23,101
    edited 2006-07-31 22:44
    The bit arithmetic instructions work just like the bit arithmetic operators in SPIN (and the Stamps with 16 bit words). Each bit of the 32 bit long word is set to the logical or/and/exclusive or of the original long word (the destination) with the source long word.

    Your comments are correct with the exception of the one on the "or" instruction. There I'm essentially copying the contents of the zero flag into the bit in "data" that corresponds to the bit I'm "receiving". The order is LSB to MSB (in this case 8 bits). If I want to reverse the order of the bits, I can initialize "mask" to %10000000 and use a "shr" instead of a "shl". The rest is the same.

    The general notion is that I have a bit mask that contains the value of the current bit I'm receiving. If I actually receive that bit, I "or" the mask with the data value I'm accumulating. The next bit to receive has a value two times the value of the current bit and so on.

    The "or" instruction is what "writes" the bit. It effectively copies the one bit from the mask into the corresponding bit in data.

    When the loop count reaches zero, the byte has been assembled in "data" and is ready to be written to HUB RAM.

    You are mostly taking my code literally. It' a pretty simple loop, but the concept may be new to you.

    Yeah, you've got to think in terms of binary, bit masks, logical bit arithmetic (and/or/and not/shifts)
  • LawsonLawson Posts: 870
    edited 2006-08-05 04:16
    um i bet you could speed up the loop below by putting the data bit to be recorded in the C flag and replacing the "if_nz· or· data,mask" and "shl· mask,#1" insturctions with a single "rcr· data,#1" instruction?·then add a "shr·· data,#24" after the loop ends to pack everything up nice and tidy.· (this assumes a byte is all you want with LSB comming first)· Huh, Cog assembly seems to be much less E-vile than SX assembly.· This little chip just keeps getting better and better. burger.gif

    My two cents,

    Marty


    Mike Green said...
    1) When you update a global variable from assembly, do all the bit twiddling in assembly, then write the finished product to HUB memory using a WRBYTE or WRWORD or WRLONG instruction. When you initiate your assembly COG, you can pass a HUB address. Usually this is the start of a common work area (between SPIN and assembly).

    2) You usually use standard logical operations for bit manipulation. In assembly, really everything is a long. If I'm processing a serial bitstream from LSB to MSB, I usually load a variable with a %1 and shift it left with each cycle like this:
                  mov       data,#0
                  mov       mask,#1
                  mov       count,#8
    loop:       <test something, leave zero flag false if we want a one>
        if_nz   or          data,mask
                  shl         mask,#1
                  djnz       count,#loop
                  wrbyte   data,par
    
    

  • Mike GreenMike Green Posts: 23,101
    edited 2006-08-05 05:27
    Yes, you can certainly do what you suggest for lsb .. msb data and it would save an instruction in the loop. There are all sorts of ways to do the same thing with various shifts, etc. If you're doing msb .. lsb, you can use the shift mask for a counter like:
                mov   data,#0
                mov   mask,#%10000000
                mov   pinmask,#1
                shl     pinmask,#26                     ' for I/O pin 26
                andn  dira,pinmask                      ' set to input mode
    loop      test    pinmask,ina              wc     ' check input
      if_c     or      data,mask
                shr    mask,#1                    wz
      if_nz   jmp   #loop
    
    
  • Chris SavageChris Savage Parallax Engineering Posts: 14,406
    edited 2006-08-10 19:59
    parsko said...(trimmed)
    Hi all,
    I have, more or less, given up on the search feature in the forums. For some reason, I've never been good at searching (Google is not my friend).
    I know this is a somewhat older thread, but I wanted to point out that if you're having trouble with the search engine we provided an enhanced one some time ago.· Please see the following thread.

    http://forums.parallax.com/showthread.php?p=562370

    ▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔
    Chris Savage
    Parallax Tech Support
    csavage@parallax.com
  • parskoparsko Posts: 501
    edited 2006-08-15 19:38
    Wow, it works. shocked.gif

    It simply works. I was so focused on the variable thing, that I didn't even bother a stand alone object with text out built in... and it works. I have to say I'm pretty proud of myself on this one. Extremely proud!! Being a mechanical engineer, only into this whole ucontroller thing for the past year-and-a-half, I never thought I'd be able to do something this high-level/low-level, I don't know, complex. Now that I look at it, it's easy.

    But, for now, I am going to go celebrate and spend the rest of the evening with my pregnant spouse. No matter what we watch/do/complain about, there will be a smile on my face!

    Please see attached, if you have an MCP3001 layin around, plug it in. I will tweak this in the future to make it a bit more streamlined. I am also open to suggestions to make it more compact.

    -Luke
  • parskoparsko Posts: 501
    edited 2006-08-16 19:06
    Okay, my daily Prop time allowance was spent tweaking the numbers to get some throughput. I have it going at 14usec per sample, which leads to a bit over 70,000samples/second. I'm extremely happy with that. I only wanted the ability to get 10,000sps, so 7 times faster will do just fine.

    Again, wow! The prop is such an amazing tool to work with. Now that I have tackled an assy program, I realize the power we have at our disposal. I really like the assy thing, A LOT!!! Assy isn't as hard as I thought it would be. But, I've never used any other assy before, so I guess I can't compare.

    Toodles,

    -Luke

    PS- The super high speed version is attached...
Sign In or Register to comment.