Shop OBEX P1 Docs P2 Docs Learn Events
SD card question — Parallax Forums

SD card question

g3cwig3cwi Posts: 262
edited 2012-04-22 08:52 in Propeller 1
Hi there

I need to read records from a card, manipulate and select a few and write them to a new file. Mike Green suggested that I need to have two copies of fsrw running to do this efficiently (at least I understood it that way). Do I therefore declare two objects i.e.

SDR: "fsrw"
SDW: "fsrw"

Do I need to mount the card etc for each? Can a card be opened for reading and writing by two instances of fsrw at the same time? If not, why do I need two running?

No doubt this is obvious to someone - but not to me!

Regards

Richard

Comments

  • Mike GMike G Posts: 2,702
    edited 2012-04-21 05:21
    I don't think you need two instances of fsrw.
    • Open the source file database
    • Read the record(s) to memory
    • Close the source file database
    • Process the records
    • Open the destination file
    • Write the data
    • Close the destination file
  • g3cwig3cwi Posts: 262
    edited 2012-04-21 06:44
    Thanks Mike.

    I am now trying to use the GPS_IO_Mini technique to read in the records for processing. The first thing I spotted was an error in GPS_IO_Mini whereby the array gps_buff is declared as a byte but cleared using longfill - in fact longfill seems to have the incorrect syntax anyway as using the correct syntax crashes the program. This has all sorts of potentially unwanted consequences. My code is:
    CON
            _clkmode        = xtal1 + pll16x
            _xinfreq        = 5_000_000
    obj
       term: "tv_text"
       sd: "fsrw"
    
    
    var
       byte tbuf      [20]
       long altitude  [10]
       long latitude  [10]
       long longitude [10]
       long RECORD
       long comma_counter
    
    
       long r_stack[10] 
       byte Recordb[68]  
       long Recorda[20]  
    
    
    
    
       long r_buff[100],Rx',cksum
       long cog,cptr,ptr,arg,j
       long Null[1]
       byte latlong_buf[10]
       
    pub go | x
    waitcnt(clkfreq*2 + cnt)
       x := \start
       term.str(string("Returned from start", 13))
       term.dec(x)
       term.out(13)
    pub start | r, sta, bytes
    
    
       term.start(12)
       term.str(string("Mounting.", 13))
       
       waitcnt(clkfreq/10 + cnt)
               
       sd.mount(0)
    
    
        
       term.str(string("Mounted.", 13))
       waitcnt(clkfreq/10 + cnt)
    
    
       
       term.str(string("Dir: ", 13))
       waitcnt(clkfreq/10 + cnt)
       
       sd.opendir
       
       'Print list of files in directory
       repeat while 0 == sd.nextfile(@tbuf)
          term.str(@tbuf)
          term.out(13)
          
       term.str(string("That's the dir", 13))
       term.out(13)
       r := sd.popen(string("APRS-SHT.CSV"), "r")
       term.str(string("Opening returned 1 "))
       term.dec(r)
       term.out(13)
    
    
       read_record  'read the first record in the file
    
    
       term.out (summit_ref)
      
       sd.pclose
    
    
       term.out(13)
       term.str(string("That's, all, folks! 3", 13))
    
    
    PUB read_record
    
    
      NULL[0] := 0
      
      repeat
      
        longfill(@r_buff,0,100) 'clear the buffer
                         '   
        cptr := 0
    
    
         repeat while Rx <>= 13   'continue to collect data until the end of the record 
           Rx := sd.pgetc         'get character from Rx Buffer
           
           if Rx == ","
             r_buff[cptr++] := 0  'If "," replace the character with 0
           else
             r_buff[cptr++] := Rx  'else save the character
               
           term.out (r_buff)
           waitcnt(clkfreq + cnt)   
                   
     copy_buffer(@Recordb, @Recorda)
      
                   
    pub copy_buffer ( buffer,args)
             bytemove(buffer,@r_buff,cptr) '  copy received data to buffer
             ptr := buffer
             arg := 0
             repeat j from 0 to 3           ' build array of pointers
              if byte[ptr] == 0               ' to each
                 if byte[ptr+1] == 0           ' record
                    long[args][arg] := NULL     ' in 
                 else                            ' the
                    long[args][arg] := ptr+1     ' data buffer
                 arg++
              ptr++
              
    ' now we just need to return the pointer to the desired record
              
    pub summit_ref
       return Recorda[0]
    
    
    pub summit_height
       return Recorda[1]
          
    pub Proc_Lat
       return Recorda[2]
    
    
    pub Proc_Long
       return Recorda[3]
    
    

    I was sort of hoping this would (as a test) read the dats from the CSV file:

    G/SP-004,559,1779895,1432603
    G/SP-013,385,1779537,1431506
    G/SP-015,343,1778549,1431703
    G/SP-001,636,1781278,1433843
    G/SP-002,582,1781162,1435389

    and print G/SP-004 to the TV. It does not work though, it just prints "G" lots of times. :(

    Any ideas?

    Cheers

    Richard
  • Mike GMike G Posts: 2,702
    edited 2012-04-21 06:59
    g3cwi, I'm not familiar with GPS_IO_Mini.

    How you save records is also how you read records. Did you save the data as a longs or as ASCII bytes? Post your database file.
  • g3cwig3cwi Posts: 262
    edited 2012-04-21 07:03
  • Mike GMike G Posts: 2,702
    edited 2012-04-21 07:17
    In the copy_buffer method , there's a bytemove. It moves a record that contains zero terminated fields to buffer. The intention of the repeat loop is to create pointers to each field? If so, you need to loop through the entire record set to find all the zero terminated strings. Looping 4 times only gets you to the first 4 bytes of a record.
    pub copy_buffer ( buffer,args)
             bytemove(buffer,@r_buff,cptr) '  copy received data to buffer
             ptr := buffer
             arg := 0
             repeat j from 0 to 3           ' build array of pointers
              if byte[ptr] == 0               ' to each
                 if byte[ptr+1] == 0           ' record
                    long[args][arg] := NULL     ' in 
                 else                            ' the
                    long[args][arg] := ptr+1     ' data buffer
                 arg++
              ptr++
    
  • Mike GMike G Posts: 2,702
    edited 2012-04-21 07:27
    Also each record ends with an 0xOD 0X0A. I do not see the read_record method accounting for the 0x0A. That will mess you up when handling multiple records.
    PUB read_record
    
    
      NULL[0] := 0
      
      repeat
      
        longfill(@r_buff,0,100) 'clear the buffer
                         '   
        cptr := 0
    
    
         repeat while Rx <>= 13   'continue to collect data until the end of the record 
           Rx := sd.pgetc         'get character from Rx Buffer
           
           if Rx == ","
             r_buff[cptr++] := 0  'If "," replace the character with 0
           else
             r_buff[cptr++] := Rx  'else save the character
               
           term.out (r_buff)
           waitcnt(clkfreq + cnt)   
                   
     copy_buffer(@Recordb, @Recorda)
    
  • g3cwig3cwi Posts: 262
    edited 2012-04-21 07:40
    Thanks Mike.

    Some good clues.

    Processing the first record:

    Recorda[0] = 559 and Recorda[1] = 1779895

    I expected that Recorda[0] would be "G/SP-004" but for some reason it is not - but I feel close now!

    Regards

    Richard
  • JonnyMacJonnyMac Posts: 9,197
    edited 2012-04-21 11:01
    I've been experimenting with parsing files for an animation control system. I applied a bit of the work I've been doing to your file structure -- maybe the attached project will be helpful.
  • g3cwig3cwi Posts: 262
    edited 2012-04-21 12:20
    Thanks JonnyMac. I will have a play with your code. I very nearly have my code working but it missed the first field for some strange reason.

    Regards

    Richard
  • g3cwig3cwi Posts: 262
    edited 2012-04-22 00:16
    Hooray! Got my code running. Feel good about that.

    Now the next challenge. My code processes 50,000 records (each with four fields). For each record it does a calculation (three calculations actually). Depending on the result of the calculation the record is either disguarded or needs to be kept in a new file on the SD card. The code that identifies the records is working fine.

    There could be up to about 100 records that I need to keep. The SD card is read by fsrw and I cant see a pointer involved so I am guessing it is being read by clocking each byte out? If I detect a record that I want to save and try to save it, I am guessing that I will loose my place in the read file? So I need a method of saving records with one file open for reading and another open for writing.

    Is this where I need two instances of fsrw or cant I have two files open on an SD card?

    Expert advice welcome.

    Thanks


    Richard

    PS my code takes 6 minutes to test all the records - seems mighty slow!
  • g3cwig3cwi Posts: 262
    edited 2012-04-22 06:34
    Two problems here: the line (100)

    bytemove(sel_buff[selcnt],@r_buff,cptr)

    ...causes the program to crash

    As does the end of file detection (EOF). Any thoughts on how best to do these?
    CON
            _clkmode        = xtal1 + pll16x
            _xinfreq        = 5_000_000
    
            CR     = 13
            LF     = 10
            Null   = 0
            COMMA  = 44
            SPACE  = 32
            TV_PIN = 12
    
            Current_Lat  = 1759224
            Current_Long = 1430687
            
    obj
       debug : "tv_text"
       sd    : "fsrw"
       FS    : "FloatString"
       Num   : "Numbers"
    
    var
       byte tbuf      [20]
      
       long r_stack[10] 
       byte Recordb[68]  
       long Recorda[20]
       long sel_buff[100]
       long SelCnt  
    
       Byte Rx
       Byte r_buff[100]
       long cog,cptr,ptr,arg,j
       Byte EOF
       long diff, lat_dec, long_dec, lat_dec_diff, long_dec_diff
      
    pub go | x
    waitcnt(clkfreq*2 + cnt)
       x := \start
       debug.str(string("Returned from start", CR))
       debug.dec(x)
       debug.out(CR)
       
    pub start | i,w,r, sta, bytes
    
       r:= 99
    
       selcnt:=0
       
       repeat i from 0 to 99
         bytefill(@sel_buff[i], NULL, 100)
    
       debug.start(TV_PIN)
    
       sd.mount(0) 
     
       debug.str(string("Mounted.", CR))
       waitcnt(clkfreq/10 + cnt)
      
       debug.str(string("Dir: ", CR))
       waitcnt(clkfreq/10 + cnt)
       
       sd.opendir
       
       'Print list of files in directory
       repeat while 0 == sd.nextfile(@tbuf)
          debug.str(@tbuf)
          debug.out(CR)
          
       debug.str(string("That's the dir", CR))
       debug.out(CR)
       
       r := sd.popen(string("APRS-SHT.CSV"), "r")
       
    
      
       waitcnt(clkfreq/10 + cnt)
       debug.str(string("Opening returned "))
       debug.dec(r)
       debug.dec(w)
       EOF := 0
       
       'test code
       repeat
        
          read_record  'read the records in the file
          
          If EOF == 1
            quit        
          
          lat_dec := Num.FromStr(Recorda[2], Num#DEC)   
          lat_dec_diff := lat_dec - current_lat
        
          long_dec := Num.FromStr(Recorda[3], Num#DEC)
          long_dec_diff := long_dec - current_long
        
          diff := ||lat_dec_diff + ||long_dec_diff
        
        if diff < 2500 'range filter
        
          bytemove(sel_buff[selcnt],@r_buff,cptr) 'store record in array
          selcnt++  'increment array record counter
          debug.out (CR)
          debug.str(recorda[0])
          debug.out(SPACE)
          debug.dec(diff)
         
    
      sd.pclose
    
      debug.out(CR)
      debug.str(string("End", CR))
    
    PUB read_record  'reads one record
    
        bytefill(@r_buff, NULL, 100) 'clear the buffer with nulls
        EOF := 0 'reset end-of-file flag
                          '   
        cptr := 1 'start pointer after first null
    
         'Fill the array r_buff with the first record
         repeat while Rx <>= CR   'continue to collect data until the end of the record
          
           Rx := sd.pgetc  'get character from SD Card
           'detect end of file
           If Rx == -1 'if eof
              EOF := 1 'set eof flag
              Quit     'crash out of loop
            
           'Replace special characters
           Case Rx
            LF    : r_buff[cptr] := SPACE
            COMMA : r_buff[cptr++] := NULL  'null-delimit strings
            Other : r_buff[cptr++] := Rx  'else save the character
              
       If EOF == 0
          copy_buffer(@Recordb, @Recorda)
      
                   
    pub copy_buffer (buffer,args)
    
             bytemove(buffer,@r_buff,cptr) '  copy received data to buffer
         
             ptr := buffer 
            
             arg := 0
           
             'Build array of pointers to the
             'data strings in the buffer
             '***not sure how this works!***        
             repeat j from 0 to cptr           
             
               if byte[ptr] == NULL
                           
                 if byte[ptr+1] == NULL
                      
                    long[args][arg] := NULL
                      
                 else
                 
                    long[args][arg] := ptr+1
                       
                 arg++
                 
              ptr++
              
    ' now we just need to return the pointer to the desired record
              
    pub summit_ref
       return Recorda[0]
    
    pub summit_height
       return Recorda[1]
          
    pub Proc_Lat
       return Recorda[2]
    
    pub Proc_Long
       return Recorda[3]
    

    Regards

    Richard
  • Mike GMike G Posts: 2,702
    edited 2012-04-22 07:56
    According to the fsrw documentation only one file can be open at a time. You'll need to keep a pointer of the current location in the source file. Then seek back to that address and read the next byte. Otherwise, yes you'll need two instances of fsrw - I'm not sure if the object supports two instances - looks like it does.
  • g3cwig3cwi Posts: 262
    edited 2012-04-22 08:14
    Thanks Mike. I had not noticed the single file limitation - sorry. I can see no reference to a method that delivers a pointer or any way of passing one back to fsrw. Thus I was thinking of assembling an array of records that I could write back to a new file after the whole file was processed in read-mode. So far this has not worked though.

    Regards

    Richard
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-04-22 08:21
    sel_buff[] is a long array. You need an "@" operator in front of it when using it with bytemove. FSRW does support multiple instances. Each instance supports a single opened file. I think there are a few other problems with you program because it doesn't make sense to do the bytemove you described and then increment the array index by one, unless the bytemove length is always 4.
  • Mike GMike G Posts: 2,702
    edited 2012-04-22 08:32
    The fsrw object has a "seek" method that take an index as a parameter. Seek has a file size limit.

    I agree with Dave. You should take a closer look at your code.
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-04-22 08:36
    This code in your start routine doesn't make sense:
       repeat i from 0 to 99
         bytefill(@sel_buff[i], NULL, 100)
    
    sel_buff is a long array. This loop is zeroing out 100 bytes in sel_buff each loop, but your startng address increments by 4 bytes each loop. Also, your last loop will zero out the 96 bytes after the sel_buff array.

    It appears that you're treating sel_buff as if it were a 2 dimensional array of 100 elements with 100 bytes each. That would make it 10,000 bytes in size. Spin doesn't support 2 dimensional arrays, so you would need to do the 2D indexing yourself. You could declare sel_buff as "long sel_buff[2500]", and address each 100-byte chunk as @sel_buff[index*25], where index would have the range of 0 to 99. Is that what you intended?
  • Dave HeinDave Hein Posts: 6,347
    edited 2012-04-22 08:52
    You could simplify your program a lot if you do what you suggested in the initial post. Just use 2 instances of FSRW. You should call the mount function only once. FSRW contains common information in a DAT section, and file-specific information in VAR sections.
Sign In or Register to comment.