Showing the files on an SD card


#1

Hi David ,

Do you think it’s possible to read the directory structure from an sdcard ?
Only the root would be enough.

Kind regards,
Ronny


#2

The Arduino SD library provides two functions to help with this:

File entry = dir.openNextFile();

opens the next file in the current directory, or returns 0 if there are no more files, and:

entry.name()

returns the name of the current file. I could provide uLisp equivalents of these functions.

Alternatively, a better solution might be to provide something more like Common Lisp:

(directory)

which could return a list of the filenames of the files in the current directory.


#3

Hi David ,

I searched on this and found an example on following link on the arduino site :

I used the ‘list files’ program , did the chip select en spi1 change and it works fine on my challenger board !

Initializing SD card…initialization done.
sunrise-sunset.fs 7567
Greeting.txt 9
ULISP.IMG 320
test.fs 26
done!

Maybe this could be implemented in ulisp ?
What do you think.

Kind regards,
Ronny


#4

Funnily enough I was just putting something together as a uLisp extension. Here it is:

/*
 SD Card Extension
*/
  
object *fn_directory (object *args, object *env) {
  (void) env;
  SD.begin(SDCARD_SS_PIN);
  File root = SD.open("/");
  if (!root) error2(PSTR("problem reading from SD card"));
  object *result = cons(NULL, NULL);
  object *ptr = result;
  while (true) {
    File entry = root.openNextFile();
    if (!entry) break;
    object *filename = lispstring(entry.name());
    cdr(ptr) = cons(filename, NULL);
    ptr = cdr(ptr);
  };
  root.close();
  return cdr(result);
}

// Symbol names
const char stringdirectory[] PROGMEM = "directory";

// Documentation strings
const char docdirectory[] PROGMEM = "(directory)\n"
"Reads the directory at the top level of an SD card and returns\n"
"a list of the filenames.";

// Symbol lookup table
const tbl_entry_t lookup_table2[] PROGMEM = {
  { stringdirectory, fn_directory, 0200, docdirectory },
};

// Table cross-reference functions

tbl_entry_t *tables[] = {lookup_table, lookup_table2};
const unsigned int tablesizes[] = { arraysize(lookup_table), arraysize(lookup_table2) };

const tbl_entry_t *table (int n) {
  return tables[n];
}

unsigned int tablesize (int n) {
  return tablesizes[n];
}

To use this:

  • Put it in a file Directory.ino in the same folder as the uLisp source file for your platform.
  • Uncomment #define extensions at the start of the main uLisp source file.
  • Compile and upload uLisp.

An example of listing the files on an SD card:

> (directory)
("CARDS2.GIF" "SPOTLI~1" "YELLOW.GIF" "CARDS8.GIF" "_YELLO~1.GIF")

#5

Hi David ,

Thanks , but it seems to throw an error :
Compilation error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
for this line in directory.ino
object *filename = lispstring(entry.name());


#6

What platform are you using it on? I tested it on an Arduino MRRZero and it was fine.


#7

Oh yes, I remember now - it’s the RP2040 Challenger. I’ll test it on RP2040 and see if I get the same problem.


#8

yes the challenger


#9

David ,

I even tried the original 4.5 version for rpi pico , but always the same error …
I don’t really have a clue what causes this.
Regards,
Ronny


#10

I think you just have to change:

object *filename = lispstring(entry.name());

to:

object *filename = lispstring((char*)entry.name());

Let me know if that works!


#11

Oh, in your case you have to change:

SD.begin(SDCARD_SS_PIN);

to:

SD.begin(SDCARD_SS_PIN,SPI1);

#12

Hi David ,

That change object filename = lispstring((char)entry.name()); did it !
I already had changed the spi1 , so that was not a problem.
Thanks again , you’re marvelous as always :-)

uLisp 4.5
22820> (directory)
(“sunrise-sunset.fs” “Greeting.txt” “ULISP.IMG” “test.fs”)

22820>


#13

Hi David ,

One more question :
Is it possible to print the files each on a new line ?

Kind regards,
Ronny


#14

I thought it would be best for (directory) to return a list of filenames that you can do what you want with. So you could do:

(progn (mapc #'print (directory)) nothing)

#15

Thanks very much !!

Ronny


#16

The directory function is now built in to the latest releases of uLisp.


#17

David, thank you very much!

Dear friends, i decided adding search pattern parameter near to standard. Maybe someone is interested. I suggest to use it.

/*
  (directory [pattern])
  Returns a list of the filenames of the files on the SD card.
  Pattern is string which contains '*' symbols.
*/
 //  (directory "/home/*/")  - search directories 
 //  (directory "/home/*")  ("/home/*.*")  ("/home/*.txt") search files 

object *fn_directory (object *args, object *env)
{
  int selection(char *name, char *filemask );

#if defined(sdcardsupport)
  (void) env;
  int type = 0x4 | 0x8 ;  // Files and directories
  char pattern_string[256] = "*" ;
  char dirname_string[256] = "/";

if (args != NULL)
{   //  Directory name
    if(stringp(car(args)))
    {
      cstring(car(args), dirname_string, 256) ;
      if(dirname_string[strlen(dirname_string)-1] == '/')
      {
          dirname_string[strlen(dirname_string)-1] = 0x0 ;
          type = 0x4 ;
      }
      else type = 0x8 ;

      char *pattern_bgn = strchr(dirname_string,'*') ;
      if(pattern_bgn)
      {   // There is pattern string with '*'-symbols
          while((pattern_bgn!=dirname_string)&&(*pattern_bgn!='/')) pattern_bgn -- ;
          if(*pattern_bgn=='/')
          {
              pattern_bgn ++ ;
              strcpy(pattern_string, pattern_bgn);
              *pattern_bgn = 0x0 ; // set 0x00 into dirname_string 
          }
          else
          {
              strcpy(pattern_string, pattern_bgn);
              strcpy(dirname_string, "/"); 
          }
        
          if(!(*dirname_string))
              strcpy(dirname_string, "/"); // Dir name "/" restore
      }
    }
}


object *result = cons(NULL, NULL);
object *ptr = result;

SD.begin(SDCARD_SS_PIN);
File root = SD.open(dirname_string);
if (!root){  pfstring("problem reading from SD card", pserial); return nil; }

while (true) {
    File entry = root.openNextFile();
    if(!entry) break;

    if( (entry.isDirectory() && (type&0x4)) || (!entry.isDirectory() && (type&0x8)) )
       if(selection((char*)entry.name(), pattern_string ))
   {
     object *filename = lispstring((char*)entry.name());
     cdr(ptr) = cons(filename, NULL);
     ptr = cdr(ptr);
   }
}

root.close();

return cdr(result);
#else
 (void) args, (void) env;
 error2("not supported");
return nil;
#endif
}



//   File search using '*'-type patterns
int fillpattern(char *mask, char *pattern){
    int i = 0 ;
    if(*mask==0) return -1 ;
    while((*mask!=0)&&(*mask!='*')) {
        *pattern++ = *mask++ ;
        i++ ;
    }
    *pattern = 0 ;

    return i ;
}

int findpattern(char *pattern, char *name) {
    int i = 0 , lenp, lenn ;
    if(*pattern==0) return -1 ;
    lenp = strlen(pattern) ;
    lenn = strlen(name) ;
    while(lenp<=lenn) {
        if(strncmp(name,pattern,lenp)==0) return i;
        name++ ;
        lenn-- ;
        i++ ;
    }
    return -1 ;
}


//   use file_pattern  
int selection(char *name, char *filemask )
{
    char file_pattern[256] ;
    int i;
    int imaskpos = 0, inamepos = 0 ;
    i = fillpattern(filemask, file_pattern);
    if(i==-1) return -1 ;

    if(i>0)
    {
        if(strncmp(file_pattern,name,i)!=0) return 0 ;
    }

    imaskpos += i+1 ; // next position after '*'
    inamepos += i ;
    do{
        // take mask next fragment between '*' symbols
        i = fillpattern(&filemask[imaskpos], file_pattern);  
        if(i == -1 ) return 1 ;
 
        int k = findpattern(file_pattern, &name[inamepos]);
        if(k==-1) return 0 ;
        imaskpos += i ;
        inamepos += k ;
        if(filemask[imaskpos] == '*') imaskpos++ ;
        else
            if(name[inamepos+i] != 0) return 0 ;  
            // the end of pattern but not end of name
    
    }while(1) ;
return 0;
}

#18

Dear friends, any functions may be useful for somedody. These function can be placed to “extensions.ino”. You can use its for you needs.

(probe-file name) function tests exists specified file or not.
(delete-file name) remove file (or directory).
(rename-file oldname newname) rename or move a file
(ensure-directories-exist dir) test and create directory

const char string_probefile[] = "probe-file";

const char doc_probefile[] = "(probe-file pathspec)\n"
"tests whether a file exists.\n"
" Returns nil if there is no file named pathspec,"
" and otherwise returns the truename of pathspec.";

object *fn_probefile (object *args, object *env) {
#if defined(sdcardsupport)
  (void) env;
  char pattern_string[256]  ;

  if (args != NULL)
  {   //  file name
      if(stringp(car(args))) {
            cstring(car(args), pattern_string, 256) ;
      } else { 
            pfstring("\nprobe-file: First argument must be string.", pserial);
            return nil; }
  }

  SD.begin(SDCARD_SS_PIN);
  if(SD.exists(pattern_string))  return tee; 


  return nil;
#else
  (void) args, (void) env;
  error2("not supported");
  return nil;
#endif
}



const char string_deletefile[] = "delete-file";

const char doc_deletefile[] = "(delete-file pathspec)\n"
"delete specified file.\n"
" Returns true if success and otherwise returns nil.";

object *fn_deletefile (object *args, object *env) {
#if defined(sdcardsupport)
  (void) env;
  char pattern_string[256]  ;

  if (args != NULL)
  {   //  Directory name
      if(stringp(car(args))) {
        cstring(car(args), pattern_string, 256) ;
      }
      else {
         pfstring("\ndelete-file: First argument must be string.",pserial);
         return nil; }
  }

  SD.begin(SDCARD_SS_PIN);
  if(SD.exists(pattern_string))
  {
    if(SD.remove(pattern_string)) return tee;
  }
  else return tee;

  return nil;
#else
  (void) args, (void) env;
  error2("not supported");
  return nil;
#endif
}



const char string_renamefile[] = "rename-file";

const char doc_renamefile[] = "(rename-file filespec newfile)\n"
"rename or moving specified file.\n"
" Returns true if success and otherwise returns nil.";


object *fn_renamefile (object *args, object *env) {
#if defined(sdcardsupport)
  (void) env;
  char filename_string[256]  ;
  char newname_string[256]  ;

  if (args != NULL)
  {   //  Directory name
      if(stringp(car(args)))
      {
        cstring(car(args), filename_string, 256) ;
        args = cdr(args);
        if (args != NULL){
            if(stringp(car(args)))
            cstring(car(args), newname_string, 256) ;
            else  {
                pfstring("\nrename-file: Second argument must be string.", pserial);
                return nil; }
      }
      else  {
          pfstring("\nrename-file: First argument must be string.", pserial);
          return nil; }
  }

  SD.begin(SDCARD_SS_PIN);
  if (!SD.exists(filename_string)) {  
        pfstring("file not exists", pserial);   return nil; }

  File fp_source = SD.open(filename_string, FILE_READ);

  if (SD.exists(newname_string)) SD.remove(newname_string) ;
  File fp_dest = SD.open(newname_string, FILE_WRITE);
  if (!fp_dest) {  
    pfstring("Cannot open destination file.\n", pserial);     return nil; }

  uint32_t i, sz ;
  sz = fp_source.size();

  for(i=0; i<sz;i++) fp_dest.write(fp_source.read()) ;

  fp_source.close();
  fp_dest.close();
  SD.remove(filename_string) ;

  return tee;
#else
  (void) args, (void) env;
  error2("not supported");
  return nil;
#endif
}



const char string_ensuredirectoriesexist[] = "ensure-directories-exist";

const char doc_ensuredirectoriesexist[] = "(ensure-directories-exist pathspec)\n"
"Tests whether the directories containing the specified file actually     exist,"
" and attempts to create them if they do not.\n"
" Returns true if success and otherwise returns nil.";

object *fn_ensuredirectoriesexist(object *args, object *env) {
#if defined(sdcardsupport)
 (void) env;
 char pattern_string[256] ;

 if(stringp(car(args))) cstring(car(args), pattern_string, 256) ;
 else  {  pfstring("\nensure-directories-exist: argument must be string\n", pserial); return nil; }

 SD.begin(SDCARD_SS_PIN);
 if(!SD.exists(pattern_string))
 {
     if(SD.mkdir(pattern_string)) return tee;
 }
 else return tee;

 return nil;

#else
  (void) args, (void) env;
  error2("not supported");
  return nil;
#endif
}

Next strings must be add
to lookup table:

{ string_probefile, fn_probefile, 0211, doc_probefile },
{ string_renamefile, fn_renamefile, 0222, doc_renamefile },
{ string_deletefile, fn_deletefile, 0211, doc_deletefile },
{ string_ensuredirectoriesexist, fn_ensuredirectoriesexist, 0211, doc_ensuredirectoriesexist },

#19

Dear friends, i found one difficulty with adding new functions if their total number exceeds 255. To solve this we can to correct the line

#define fntypef(x)    (getminmax((uint8_t)(x))>>6) 

replace it with

#define fntypef(x)    (getminmax((uint16_t)(x))>>6)

#20

Dear friends, I found one difficulty with adding new functions if their total number exceeds 255.

Yes, that’s a bug - thanks for spotting it. Will be corrected …