Shop OBEX P1 Docs P2 Docs Learn Events
SD Card Speed Issues — Parallax Forums

SD Card Speed Issues

Hello All,
I have an SD Card FAT class I built, and all works fine, but I have noticed that not all cards that have the TRAN_SPEED in the cards CSD set to 32(signaling a max clock of 25MHz) can handle data read at 10MHz and write at 20MHz from the propeller. For now, I have my class setup to auto detect the max speed. Does anyone know why certain cards that say they will support up to 25MHz with the TRAN_SPEED setting do not actually support it? I would like to get rid of the auto speed detection if possible, but I want to know why it is a problem in the first place. BTW, when I use to high of a speed, the R1 response is correct from the card, but the data I receive is all jumbled with no discernible pattern. Also, some cards do work with 10Mhz read and 20MHz Write fine(1GB sandisk microSD), i mostly have the problem with read and write not working at max speed on older 64MB or 256MB cards. Thanks!

Comments

  • How are you able to test such high speed SPI comms with the Propeller? Care to share your code?
  • Note that these are line speeds, it is of course slower with overhead

    For write, the fastest is 20MHz using the counters in NCO single-ended mode


    Here is the counter init
                            'Set Counter registers
                            movs ctrb, spic 
                            movi ctrb,#%0_00100_000
                            movs ctra, spio                  'Set counter A data pin and mode
                            movi ctra,#%0_00100_000    
    
    


    Here is the write procedure, note that 'loop' is the main wait loop for the spi cog
    :write
                            rdbyte r1, dta
                            shl r1, #24
                            mov phsa, r1
                            movi frqb,#%010000000   'Start counting at 20MHz!  
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            mov frqb, #0            'Stop counting
                            add dta, #1 
                            djnz ctr,#:write
                            mov phsa, #0     'Leave output low since both cogs control output(required!!) 
                            wrlong null, flg        'Clear data flag if done
                            jmp #:loop 
    

    Fastest for read is 10MHz since we can't link the counters to get input like we can link them for output
    
    :read
                            mov phsb, #0              
    :rdloop
                            mov r1,#0                       'Clear data buffer
                             
                            movi frqb,#%001000000           'Start counting at 10MHz!
                            test spiimask,ina       wc      'Bit 7
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 6
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 5
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 4
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 3
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 2
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 1
                            rcl r1,#1  
                            test spiimask,ina       wc      'Bit 0
                            movi frqb,#0                    'Stop Counting  
                            rcl r1,#1            
                            wrbyte r1, dta                  'Write to buffer
                            add dta, #1                     'Increment pointer 
                            djnz ctr,#:rdloop                 'Loopback for numbytes
                            wrlong null, flg                'Clear data flag if done
                            jmp #:loop
    
  • handermanntrhandermanntr Posts: 18
    edited 2016-09-22 19:05
    For those who care, here is the whole class
    {{
      SPI Management Functions
      Abstract:
      This class is built to be a universal SPI Driver
    
    }}
    
      {
       ASMDATA
       
       data[0]= hub data location ptr for read/write
       data[1]= num of bytes to write/readfunction 
       data[2]= function flag
      } 
    
    CON
      'Clocking settings
      _clkmode = xtal1 + pll16x
      _xinfreq = 5_000_000
    
    
    
    VAR
      
      
    OBJ
    
    PUB InitSPI(spioutpin, spiinpin, spiclock) : okay
    {
     dloc = -1 if coginit,memory address if other 
    }
    
      data[2] := 0 'Start with no function
       
      'Set Cog Vars
      spiomask := |< spioutpin
      spicmask := |< spiclock
      spiimask := |< spiinpin 
      spio := spioutpin
      spic := spiclock
       
      okay := cogon := (cog := cognew(@entry,@data)) > 0
    
    
    PUB WriteSPI(dataloc,numbytes,wrspeed)
    {
     Purpose: Write SPI
    }
    
      
      repeat until data[2] == 0     'Wait until cog ready 
       
      data[0] := dataloc
       
      data[1] := numbytes
       
      data[2] := wrspeed + 3
       
      repeat until data[2] == 0
    
    PUB SendCommandR1(command, argument)
    
    
        repeat until data[2] == 0       'Wait until cog ready 
         
        data[0] := command
         
        data[1] := argument
         
        data[2] := 6
         
        repeat until data[2] == 0  
    
        return data[0]       
    
    
    PUB ReadWriteSPISlow(writedata)
    {
     Purpose: Read SPI 
    } 
    
        repeat until data[2] == 0       'Wait until cog ready 
         
        data[0] := writedata
    
        data[2] := 7
         
        repeat until data[2] == 0    
    
        return data[0]
    
        
    
    PUB ReadSPI(dataloc,numbytes,rdspeed)
    {
     Purpose: Read SPI  
    } 
      
      repeat until data[2] == 0     'Wait until cog ready 
       
      data[0] := dataloc
       
      data[1] := numbytes
       
      data[2] := rdspeed + 1
       
      repeat until data[2] == 0  
    
    PUB Stop
      if cogon~
        cogstop(cog)
    
    
    DAT
            org 0
    
    entry
                            mov dt, par                     'Get data location
                            mov nbytesloc, dt              'Get number of bytes location
                            add nbytesloc, #4
                            mov flg, nbytesloc                                      'Get flag location
                            add flg, #4
                            andn outa, spiomask     'Set pins low
                            andn outa, spicmask 
                            or dira, spiomask       'Set pin directions
                            or dira, spicmask
                            andn dira, spiimask  
    
                            'Set Counter registers
                            movs ctrb, spic 
                            movi ctrb,#%0_00100_000
                            movs ctra, spio                  'Set counter A data pin and mode
                            movi ctra,#%0_00100_000    
    
                            
    {
     TASK LOOP
     0=No task, keep looping
     1=fastread
     2=slowread
     3=fastwrite
     4=slowwrite
     5=BLANK
     6=sendcommandr1
     7=read write spi slow
    }                            
                            
    :loop
                            rdlong r1, flg                  'Get data flag
                            tjz r1, #:loop                  'If zero,keep looping, waiting for task
                            rdlong ctr, nbytesloc           'Get bytes to write
                            rdlong dta, dt                  'Get data location  
                            cmp r1,#1               wz      'Jump to option
                  if_z      jmp #:read                         
                            cmp r1,#2               wz
                  if_z      jmp #:readslow                         
                            cmp r1,#3               wz
                  if_z      jmp #:write 
                            cmp r1,#4               wz
                  if_z      jmp #:writeslow
                            cmp r1, #6              wz
                  if_z      jmp #:sendcommandr1
                            cmp r1, #7              wz
                  if_z      jmp #:rdwrspislow
                            wrlong null,flg                 'If not valid choice, clear, and loop back
                            jmp #:loop
    
                            
    :write
                            rdbyte r1, dta
                            shl r1, #24
                            mov phsa, r1
                            movi frqb,#%010000000   'Start counting at 20MHz!  
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            mov frqb, #0            'Stop counting
                            add dta, #1 
                            djnz ctr,#:write
                            mov phsa, #0     'Leave output low since both cogs control output(required!!) 
                            wrlong null, flg        'Clear data flag if done
                            jmp #:loop 
    
    :writeslow
                            andn outa, spicmask  
    
    :wrslowloop
                            mov ctrsub, #8
                            rdbyte r2, dta
    
    :wrslowsubloop
                            mov r1, r2
                            and r1, #$80           wz
                  if_z      andn outa, spiomask
                  if_nz     or outa, spiomask
                            or outa, spicmask           'Raise Clock
                            shl r2, #1
                            andn outa, spicmask          'Drop clock
                            djnz ctrsub, #:wrslowsubloop
                            add dta, #1
                            djnz ctr,#:wrslowloop
                            andn outa, spiomask     'Leave output low since both cogs control output(required!!) 
                            wrlong null, flg        'Clear data flag if done
                            jmp #:loop 
    
    
    :readslow
                            mov ctrsub, #8
                            or outa, spiomask
    :rdsubloopslow
                            or outa, spicmask               'Raise Clock
                            shl r1, #1                      'Shift dta left to get next bit   
                            test spiimask,ina      wz
                  if_nz     or r1, #1
                            andn outa, spicmask             'lower Clock
                            djnz ctrsub, #:rdsubloopslow    'Keep reading if still bits, else, go back to main loop
                            wrbyte r1, dta
                            add dta, #1
                            djnz ctr, #:readslow 
                            wrlong null, flg                'Clear data flag if done
                            andn outa, spiomask
                            jmp #:loop
    
    
    :read
                            mov phsb, #0              
    :rdloop
                            mov r1,#0                       'Clear data buffer
                             
                            movi frqb,#%001000000           'Start counting at 10MHz!
                            test spiimask,ina       wc      'Bit 7
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 6
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 5
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 4
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 3
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 2
                            rcl r1,#1
                            test spiimask,ina       wc      'Bit 1
                            rcl r1,#1  
                            test spiimask,ina       wc      'Bit 0
                            movi frqb,#0                    'Stop Counting  
                            rcl r1,#1            
                            wrbyte r1, dta                  'Write to buffer
                            add dta, #1                     'Increment pointer 
                            djnz ctr,#:rdloop                 'Loopback for numbytes
                            wrlong null, flg                'Clear data flag if done
                            jmp #:loop
    
    :sendcommandr1
                            mov spiwrite, dta               'Copy command
                            call #writebytefast         'Write
                            mov spiwrite, ctr               'Copy argument
                            shr spiwrite, #24
                            call #writebytefast          'Write argument >> 24
                            mov spiwrite, ctr               'Copy argument
                            shr spiwrite, #16
                            call #writebytefast          'Write argument >> 16
                            mov spiwrite, ctr               'Copy argument
                            shr spiwrite, #8
                            call #writebytefast          'Write argument >> 8
                            mov spiwrite, ctr               'Copy argument
                            call #writebytefast          'Write argument >> 0  \
                            call #getcrc7                   'Generate CRC
                            mov spiwrite, crc               'Write CRC
                            call #writebytefast
                            
                            mov r2, r1attempts
    :sendcommandr1loop
                            mov spiwrite, #$FF
                            call #readwritespidelay
                            cmp spiread, #$40      wz, wc
            if_nz_and_nc    djnz r2, #:sendcommandr1loop
            if_nz_and_nc    mov r1, #$FF                        
            if_nz_and_nc    wrlong r1, dt   
            if_z_or_c       mov spiwrite, #$FF
            if_z_or_c       wrlong spiread, dt
            if_z_or_c       call #writebytefast                           'Save result                                        'Write failure result
                            wrlong null, flg
                            jmp #:loop
    
    :rdwrspislow
                            mov spiwrite, dta                                       'Copy data to write
                            call #readwritespidelay
                            wrlong spiread, dt                                      'Write read data
                            wrlong null, flg
                            jmp #:loop 
    
    
    
    
    readwritespidelay
                            mov ctrsub, #8
                            mov spiread, #0
    
    :readwritespidelayloop 
                            test spiwrite, #$80           wz
                  if_z      andn outa, spiomask
                  if_nz     or outa, spiomask
                            or outa, spicmask           'Raise Clock
                            shl spiwrite, #1                'Rotate instead of shift, so multibyte vars can be written with sequential calls
                            shl spiread, #1
                            test spiimask, ina              wz
                  if_nz     or spiread, #1
                            andn outa, spicmask          'Drop clock
                            djnz ctrsub, #:readwritespidelayloop
    
                            andn outa, spiomask
    
    readwritespidelay_ret   ret
    
                    
    
    getcrc7
                            mov crc, #0
                            mov r1, dta                     'CRC command
                            call #crcsub
                            ror ctr, #24                    
                            mov r1,ctr                      'CRC argument >> 24
                            and r1, #$FF
                            call #crcsub
                            rol ctr, #8                     'CRC argument >> 16    
                            mov r1, ctr
                            and r1, #$FF
                            call #crcsub
                            rol ctr, #8                     'CRC argument >> 8    
                            mov r1, ctr
                            and r1, #$FF
                            call #crcsub
                            rol ctr, #8                     'CRC argument >> 0    
                            mov r1, ctr
                            and r1, #$FF
                            call #crcsub
                            
                            and crc, #$7F
                            shl crc, #1
                            or crc, #1 
    
    getcrc7_ret             ret
    
    crcsub
                            'Data byte stored in R1
                            mov ctrsub, #8
    :crcsubloop
                            shl crc, #1
                            mov r2, r1
                            xor r2, crc
                            and r2, #$80             wz
                  if_nz     xor crc, #$9
                            shl r1, #1
                            djnz ctrsub, #:crcsubloop
    crcsub_ret              ret      
     
    writebytefast
                            shl spiwrite, #24
                            mov phsa, spiwrite
                            movi frqb,#%010000000   'Start counting at 20MHz!  
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            shl phsa, #1
                            mov frqb, #0            'Stop counting
                            mov phsa, #0     'Leave output low since both cogs control output(required!!) 
    writebytefast_ret       ret
    
                              
                            
    null LONG 0
    spiomask LONG 0                 'SPI out mask
    spicmask LONG 0                 'SPI clk mask
    spiimask LONG 0                 'SPI in mask
    spio LONG 0                     'SPI out pin
    spic LONG 0                     'SPI clk pin   
    r1attempts long 1000
    
    dta res 1                       'Data location
    dt res 1                        'Data location pointer
    nbytesloc res 1                 'Location of number of bytes to write
    ctr res 1                       'Byte counter
    ctrsub res 1                    'Bit counter
    r1 res 1
    r2 res 1
    flg res 1                     'Loop Flag
    spiwrite res 1
    spiread res 1
    crc res 1
    
    FIT 496
    
    cogon long 0
    cog byte 0
    data long 0[3]
      
      {
       ASMDATA
       
       data[0]= hub data location ptr for read/write
       data[1]= num of bytes to write/readfunction 
       data[2]= function flag
      } 
    
  • This is incredible! Thanks :D I can't wait to see about implementing this trick in PropWare's SPI routines. Sorry that I cannot be of any help to your original question.
  • Yep, I love the counters in the prop. The propeller is great at building complex projects where you need both speed and large programs. This speed is best seen in SRAM, and some kinds of flash because you don't have to wait for the device to pull or write the data like you do in an SD Card. the final speeds with FAT filesystem and SD card overhead is 108KBps write and 250KBps read(This is with all possible features, so it could be much faster if you removed features or used only a single kind of card), but with sram, you can get up to about 2MBps with overhead(which is not much). :)
  • Peter JakackiPeter Jakacki Posts: 10,193
    edited 2016-09-23 02:10
    I don't understand why you are even talking about obsolete cards such as 256M and even 1G? I know I can buy 2GB cards still, but there are hard to get and cost more than an 8GB SDHC. I am aware of using the counters but I think that you may have to buffer the signals to help with read/write setup and hold times. BTW, with a simple (and small) software loop in Tachyon I get read speeds of 250kB/sec but the main problem is having to wait for the R1 response which takes the same amount of time at whatever speed you clock it at. Increasing the block size or performing a multi-block read is another way of increasing the read speed.

    EDIT: I may just hookup the 100MHz scope and check those setup and hold times to be sure.
  • I am worried about the small cards for compatibility reasons. Also, no one is going to format larger than a 2GB card in FAT16, which is the current filesystem I am working on. I can try to use a buffer and see if that works, but I can't imagine I would see a huge difference. Another item I am wondering about is if there is something to do with interference depending on how it is connected, be it via wire or via a PCB. The interface I use to test some of the big old cards is connected via wires whereas the micro is direct on the PCB. Will have to test a 64MB microSD on the PCB to see what the speed tests as.
  • AribaAriba Posts: 2,682
    @handermanntr

    What I see in your code as a possible problem is that you don't set the phase of the clock generated by the Counter A. You just enable and disable the counter with movi frqa.... This does not affect the current PHSA value so the SPI-clock has a random phaseoffset to the read time point.
    All the counter SPI methodes that I know sets first the PHSA then enables the counter with setting the mode in CTRA. FRQA is only set once at begin. It needs some try and error to find the right PHSA value, I've seen $E000_0000 and $6000_0000 in my objects collection.

    You should look at the low level drivers for FSRW (in the OBEX) they do all the counter tricks and also a read-ahead and write-behind of blocks with a buffer in cogram.
    There is the sdspi_safe driver with 10 MHz read, but also a faster one with a very nifty code from Kuroneko that can read with 20 MBit/s. (This works not with all cog/pin combinations, it really depends on the location of the cog on the die relative to the pin position).

    Andy
  • handermanntrhandermanntr Posts: 18
    edited 2016-09-26 13:42
    Thanks, that is a good point, I will do some testing and see what I can get. Can you supply me with a link for the 20MHz read?
Sign In or Register to comment.