[Wiki]Everything there is to know about SD Card access

Troubleshooting

Something not working? Make sure your Pokitto’s battery is charged. Some cards can be picky about voltage.
Still not working? Try another card. FAT is brittle.

Available APIs

There are three ways to access the SD card from your code. Each has its pros and cons:

- PokittoDisk

Based on PetitFatFs (PFFS).
Pros: The smallest of the three libs.
Cons: Limited to one file at a time, 8-char filenames only.

#include <Pokitto.h>
void readExample(char *buffer, int length){
  pokInitSD();
  if(fileOpen("filename", FILE_MODE_READONLY) == 0)
    fileReadBytes(buffer, length);
  fileClose();
}

- SDFileSystem (SDFS)

Based on FatFs.
Pros: The most feature-complete.
Cons: The slowest, largest of the three. API is problematic. Not available in simulator.
Notes:

  • Use of the C fopen function results in a memory leak. Use the SDFS directly, like in the example below.
#include <SDFileSystem.h>
...
void readExample(char *buffer, int amount){
  auto sdfs = new SDFileSystem(P0_9, P0_8, P0_6, P0_7, "sd", NC, SDFileSystem::SWITCH_NONE, 24000000 );
  auto file = sdfs->open("filename", O_RDONLY);
  if(file){
    file->read(buffer, amount);
    file->close();
  }
}

- File

Also based on FatFs.
Pros: Faster than the other two. Simple API. Works in simulator, hardware and emulator.

#include <File>
void readExample(char *buffer, int amount){
  File file;
  if(file.openRO("filename"))
    file.read(buffer, amount);
}

You can use the File API together with SDFS by using the following in your My_settings.h:
#define PROJ_SDFS_STREAMING
It will initialize SDFS automatically, just create a File and use it. Always include File. You shouldn’t include the implementation headers (SIMFile/YAPFSFile/SDFSFile) directly.

7 Likes

Looks very good! Can I create a directory to the SD card using the File API?

What does this mean? Why there is 2 different “File” APIs: File and SDFSFile?

Not yet. mkdir was disabled to save space. I plan to make this configurable using a My_settings flag.

There is one API with multiple implementations. Generally, you just include File and it will pick the correct implementation depending on if you are in the simulator or if you are using SDFS already. This allows you to use the safer, leak-free, API even if some library in the same project wants to use SDFS directly.

(edit: updated the post for clarity)

1 Like

OK, I guess I need to use SDFileSystem until that has been implemented. Do you know if SDFileSystem works in the Simulator in FemtoIDE?

No, SDFS does not have Simulator support.

1 Like

ok, then I could use File for everything else and SDFileSystem just for mkdir. For the simulator I need to pre-create folders.
So File and SDFilesystem can be used in the same binary? Can I do the initialization normally like this:

`sdFs =new SDFileSystem( P0_9, P0_8, P0_6, P0_7, "sd", NC, SDFileSystem::SWITCH_NONE, 25000000 );`

Yes, but you need to have PROJ_SDFS_STREAMING in My_settings.h, otherwise the two libs are going to conflict with each other.

Yes, just make sure you do that before you try to use File, otherwise File is going to do that for you.

1 Like

Ok, good.

Btw. Is the File API still Simulator compatible if it is using SDFSFile? I suppose SDFSFile is a HW implementation, right?

Oh, right… no, that’s not going to work, SDFS is hardware-only. To create a directory in the Sim you’re going to have to call the native method directly.

Ok, better to use totally different API for the simulator then.

I am using the File approach but it doesn’t seem to work in the emulator. Should it?

My code:

#include "Pokitto.h"

#include <File>

void readExample(uint8_t *buffer, int amount){

  File file;
  printf("before buffer[0] %i\n", buffer[2]);   
  
  if(file.openRO("Smile.xxx"))
    file.read(buffer, amount);
  
  printf("after buffer[2] %i\n", buffer[2]);    

}

int main(){
    
    uint8_t bit[((16*16)/2)+2];
    
    using PC=Pokitto::Core;
    using PD=Pokitto::Display;
    PC::begin();
    PD::persistence = true;
    PD::invisiblecolor = 0;
    
    readExample(bit, 130);

    
    while( PC::isRunning() ){
        
        if( !PC::update(true) )  continue;

            PD::directBitmap(12, 12, bit, 4, 1);

    }
    
    return 0;
}

I am also trying to stream a 16 colour bitmap from the SD into an array for rendering with directBitmap(). How are people converting images (from png) to an appropriate file format?

1 Like

It should work, but it requires the file getting copied to a fake SD card image (.img file) and having that passed to the emulator. For my pinball testing I had to manually create one in linux and mount it as a loop device then pass it with the -I /path/to/sd.img to have it work. Problem is writing to the mounted folder doesn’t always seem to update the image file.

There might be something I’m doing slightly wrong with my setup. IIRC you should be able to right-click a file in FemtoIDE and check the box Copy to SD and it should do the rest for you, but I’m not sure on that approach as I haven’t done it.

file.openRO expects an absolute path to the file (including on the emulator, not sure about simulator), so it should be "/path/to/Smile.xxx for example my pinball table is stored in the pinball folder on the root of the sd so it would be /pinball/dragonrush.dat

I usually just write my own, since often I spend less time writing some quick 2-second tool then I spend finding the right tool then learning how to use it. I’m sure someone else can point you in the right direction there though.

1 Like

In the example I have give, the file is in the SD root folder.

1 Like

It should be enough to right-click on the file name, check Copy to SD then build.

Hmmm … that’s what I did.

Edit: A clean before building worked on that ! Silly me.

3 Likes

So … one gotcha that came out of my testing. The FemtoIDE interprets the file extension type before copying the file to the SD card. If it is an extension it does not recognise or one that it thinks should be text which breaks characters that are above 128 (hex 80).

In my example, I was using .xxx but had I used .bin (or any of these EEPROM, RAW, PNG, GIF, MAP, MP3, OGG, WAV, JPG, IMG, MOD, XM, PMF) the code works as expected.

7 Likes

Is there a list of functions for manipulate file data using File lib?

I see I can read and write a certain number of bytes, but without reinventing the wheel, can I read a full line of text, split it in token and parse the values?

For the later I assume strok and atoi is the way, but I’m not sure I can use f_get or other c functions that accept a *file as parameter.

Also suggestions for write formatted data back are welcome.

There is the source code:

If you want something more handy, you could try running Doxygen on it. There wouldn’t be any nice descriptions, but you’d at least get a presentable list of functions out of it.

Be careful with std::strtok, it destroys its input, and it relies on a static buffer so you have to finish using one load of input before it can start work on another load. You probably would be better off reinventing the wheel as far as std::strtok is concerned since you can likely invent something more efficient with minimal effort.

std::atoi isn’t quite as bad, but it has no decent way of signalling its own failure. std::from_chars is the better, more modern equivalent, so prefer that if it’s available.

I’m going to go out on a limb and presume FILE and File are two completely unrelated types, so std::fgets/std::fgetc likely won’t work with File.

If you can handle reading in a line of text yourself, i.e. by either reading one byte at a time or reading a buffer of N bytes (e.g. 1024 bytes) and seeking back when needed, then you can manipulate the characters you read in any way you want.

I.e. the aforementioned standard functions would work as they normally would with any other sequence of characters in memory.

(Though again I expect something hand-written that’s aimed specifically at the format you’re trying to interpret would be more efficient than the standard functions.)

If you’ve already formatted it, isn’t that just a matter of opening a file and then pointing the ‘write’ function to a buffer filled with the data you want to write?

Or do you mean “Would it be cheaper to format first and then write the buffer, or to write the data as it’s formatted?”? (In which case the answer probably depends on the details of what you’re trying to convert.)

1 Like

How Can I read a single byte of data from file using File ?

I was expecting that seek would move the file pointer so I can read on char at time:

      if (file.openRO("data/zmaster/test.txt")) {
            char s_buff[] = " ";
            for (int i = 0; i < file.size(); i++) {
                file.seek(i);
                LOG("Char:", i, " = ", file.read(s_buff, 1), "\n");
            }
            file.close();
        }
Char:0 = 1
Char:1 = 1
Char:2 = 1
Char:3 = 1
Char:4 = 1
Char:5 = 1
Char:6 = 1
Char:7 = 1
Char:8 = 1
....

Keep in mind that string manipulation and file parsing in C/C++ is quite an unexplored topic for me, so seek, teel and other functions are not my breed.
I’m having difficulties to find the correct snippet of code to accomplish my needs as I’m not sure if I have to look to YAPFS, SDFS or FatFS for correct functions reference. File is my choice for file handling as it allow same code for PokittoEmu and HW, so I’m looking for indications specifically for the Pokitto dev environment.

This worked for me:

void Game::readStuff(uint32_t startPos, uint32_t length) { 

    file.openRO("/music/{filename}.img");
    file.seek(startPos);
    file.read(this->imgBuffer, length);
    file.close();
    
}

As you can see, I am reading data into into the screen buffer but any block of memory will do! I am reading the entire string in one read whereas your code is doing it byte by byte.

I think your code is giving the wrong result as file.read() returns the number of bytes read (in your case 1). Had you written yours as:

      if (file.openRO("data/zmaster/test.txt")) {
            char s_buff[] = " ";
            for (int i = 0; i < file.size(); i++) {
                file.seek(i);
                file.read(s_buff, 1);
                LOG("Char:", i, " = ", s_buff, "\n");
            }
            file.close();
        }

I suspect it will work.

3 Likes