Sd-card extensions for uLisp-3.3a (-arm, Teensy-4.1 board)


#1

sd-cards are a great addition for using uLisp.
The following text describes how to add four functions to uLisp to be able to see and manipulate files on the sd-card.
At time of writing this procedure is tested for the uLisp-arm version 3.3a only; anyway I think it would work on other uLisp platforms supporting sd-cards as well (uLisp-esp(32) for example).

Platforms supporting sd-cards (esp32, Teensy-4.1 and other) are able to

  • save and load uLisp images (“binary” images of the uLisp workspace) to the sd-card: (save-image [“filename”]), (load-image [“filename”])
  • save uLisp code in a textfile and read it back to the current session: (load “filename”), (save “filename”)
    (uLisp functions for reading and writing files are described in the thread “Loading and saving uLisp code to sd-card”
  • write uLisp code to read and write any data to a file

I added four functions to uLisp:

  • (ls) – list directory – this function has just side effects, printing the file-list at screen
    (a later version should return a uLisp list of files, so they can be handled within uLisp code)
  • (rm “filename”) – removes a file ‘filename’ from the sd-card
  • (rmdir “dirname”) – removes a (empty) directory from the sd-card
  • (mkdir “dirname”) – creates a directory ‘dirname’ on the sd-card

These functions are not compatible to common lisp functions. It may be nice to write functions behaving like common lisp.

To add this functions to uLisp-3.3a-arm, five simple steps needs to be done.
There’s a very good description about “Adding your own functions to uLisp” at the uLisp homepage.

Patching uLisp-3.3a (arm)

To do it, open your arduino-ide, load uLisp-3.3a (arm) and follow the instructions below:

STEP 1:

Insert the code at the end of this text to the uLisp source just behind the line (see section “Function definitions”)

// Insert your own function definitions here

STEP 2:

Add the strings defining the names of the functions in the list below the comment, add the following lines at the end of that list. For other versions of uLisp you may change the number of the string with the next sequential number.

// Built-in functions - stored in PROGMEM
....

// BEGIN KAEF
const char string209[] PROGMEM = "ls";
const char string210[] PROGMEM = "rm";
const char string211[] PROGMEM = "rmdir";
const char string212[] PROGMEM = "mkdir";
// END KAEF

STEP 3:

Add the following entries at the end of lookup_table[] specifying the name of the string you defined, the C function name, and the minimum and maximum permitted number of arguments:

....
{ string208, fn_invertdisplay, 0x11 },  // LINE IS ALREADY THERE!

// Kaef begin
{ string209, fn_listDir, 0x00 },
{ string210, fn_rm, 0x11 },
{ string211, fn_rmdir, 0x11 },
{ string212, fn_mkdir, 0x11 },
// Kaef end
};                                     // LINE IS ALREADY THERE!

STEP 4:

Add the uppercase symbols representing the new functions in the enum function definition near the beginning of the uLisp source file.
These symbols should go just before ENDFUNCTIONS in the list:

FILLSCREEN, SETROTATION, INVERTDISPLAY, // LINE IS ALREADY THERE, INSERT A NEW LINE BEFORE 'ENDFUNCTIONS'
// Kaef: BEG Block
LISTDIR, RM, RMDIR, MKDIR,
// Kaef: END Block
ENDFUNCTIONS };                        // EXISTED ALREADY, JUST INSERT THE THREE LINES ABOVE!

STEP 5:

Recompile uLisp and flash the new version to your Teensy-4.1 board.
Insert a sd-card with some files on it and enter: (ls)
You should see a listing of the files from the card.

Function definitions

Insert the following block of code as described in ‘STEP 1’ section above:

// Insert your own function definitions here
#if (defined sdcardsupport)
//////////////////////////////////////////////////////////
// Kaef: copied from ulisp-esp:
char *cstring (object *form, char *buffer, int buflen) {
    int index = 0;
    form = cdr(form);
    while (form != NULL) {
        int chars = form->integer;
        for (int i = (sizeof(int) - 1) * 8; i >= 0; i = i - 8) {
            char ch = chars >> i & 0xFF;
            if (ch) {
                if (index >= buflen - 1) error2(0, PSTR("no room for string"));
                buffer[index++] = ch;
            }
        }
        form = car(form);
    }
    buffer[index] = '\0';
    return buffer;
}

void identDirListing(uint8_t curDirLevel) {
    for (int i = 0; i < curDirLevel; i++)
        pfstring(PSTR("  "), pserial);
}

void listDir (const char * dirname, uint8_t curDirLevel) {
    SD.begin(SDCARD_SS_PIN);
    const uint8_t MAX_DIR_LEVELS = 10;
    identDirListing(curDirLevel);
    pfstring(PSTR("  Listing directory: "), pserial); pfstring(dirname, pserial); pln(pserial);

    File root = SD.open(dirname);
    if (!root) {
        pfstring(PSTR("Failed to open directory"), pserial); pln(pserial);
        return;
    }
    if (!root.isDirectory()) {
        pfstring(PSTR("Not a directory"), pserial); pln(pserial);
        return;
    }

    File file = root.openNextFile();
    while (file) {
        identDirListing(curDirLevel);
        if (file.isDirectory()) {
            pfstring(PSTR("    DIR:  "), pserial);
            pfstring(file.name(), pserial); pln(pserial);
            if (curDirLevel < MAX_DIR_LEVELS) {
                listDir(file.name(), curDirLevel + 1);
            } else {
                pfstring(PSTR("      no more levels shown..."), pserial);
                pln(pserial);
            }
        } else {
            pfstring(PSTR("    FILE: "), pserial);
            pfstring(file.name(), pserial);
            pfstring(PSTR(" ("), pserial); pint(file.size(), pserial);
            pfstring(PSTR(" Bytes)"), pserial);
            pln(pserial);
        }
        file = root.openNextFile();
    }
    identDirListing(curDirLevel);
    pfstring(PSTR("  done"), pserial); pln(pserial);
}
#endif

object *fn_listDir (object *args, object *env) {
    (void) args; (void) env;
#ifdef sdcardsupport
    listDir("/", 0);
#else
    error2(0, PSTR("sdcardsupport not enabled"));
#endif
    return tee;
}

object *fn_rm (object *args, object *env) {
    (void) env;
    bool result = false;
#ifdef sdcardsupport
    object *filename = first(args);
    if (stringp(filename)) {
        char fn[256];
        memset(fn, 0, sizeof(fn));
        fn[0] = '/';
        cstring(filename, &fn[1], sizeof(fn) - 2);
        SD.begin(SDCARD_SS_PIN);
        File root = SD.open(fn);
        if (!root) {
            pfstring(PSTR("Failed to open directory"), pserial); pln(pserial);
            return nil;
        }
        if (!root.isDirectory()) {
            result = SD.remove(fn);
        }
    } else error2((int)args, PSTR("Argument should be string!"));
#else
    error2(0, PSTR("sdcardsupport not defined, function disabled"));
#endif
    return result ? tee : nil;
}

object *fn_rmdir (object *args, object *env) {
    (void) env;
    bool result = false;
#ifdef sdcardsupport
    object *filename = first(args);
    if (stringp(filename)) {
        char fn[256];
        memset(fn, 0, sizeof(fn));
        fn[0] = '/';
        cstring(filename, &fn[1], sizeof(fn) - 2);

        SD.begin(SDCARD_SS_PIN);
        File root = SD.open(fn);
        if (!root) {
            pfstring(PSTR("Failed to open directory"), pserial); pln(pserial);
            return nil;
        }
        if (root.isDirectory()) {
            result = SD.rmdir(fn);
        }
    } else error2((int)args, PSTR("Argument should be string!"));
#else
    error2(0, PSTR("sdcardsupport not defined, function disabled"));
#endif
    return result ? tee : nil;
}

object *fn_mkdir (object *args, object *env) {
    (void) env;
    bool result = false;
#ifdef sdcardsupport
    object *filename = first(args);
    if (stringp(filename)) {
        char fn[256];
        memset(fn, 0, sizeof(fn));
        fn[0] = '/';
        cstring(filename, &fn[1], sizeof(fn) - 2);

        SD.begin(SDCARD_SS_PIN);
        if (!SD.exists(fn)) {
            result = SD.mkdir(fn);
        }
        else error2((int)args, PSTR("already exists!"));
    } else error2((int)args, PSTR("Argument should be string!"));
#else
    error2(0, PSTR("sdcardsupport not defined, function disabled"));
#endif
    return result ? tee : nil;
}