[Wiki] Using PokittoDisk (Saving & Loading)


#1

Using PokittoDisk for Saving and Loading

Games that reset highscore every time you restart your Pokitto are good and all, but what if you want a persistent highscore on your game? The PokittoDisk library can do this and much more by allowing the program to interact with files on the SD card.

Files

First of all, you’ll need a file to save to. I recommend making a .txt file as they are easy to edit. If you’re using hardware to run your game, put your file on the SD card in the same folder as your Pokitto BIN (please correct me if that isn’t the correct place, as I haven’t actually tried using PD on HW yet). If you’re running your game on the simulator, navigate to /Pokitto/POKITTO_SIM/bin/Debug/ and put your file there.

Next, we need to fill the file with dummy data. For each variable you would like to store, enter:

  • 1 character for a char or a bool
  • 2 characters for a short, an int, or a uint16_t
  • 4 characters for a long, a uint32_t, or a float
  • 8 characters for a long long or a double

These characters can be whatever you would like.
For this example, I will be using a char for my highscore, and my file will be hiscore.txt containing 0.

The Setup

For this, I’ll assume that I have a highscore variable called char hiscore, and that when I get a new highscore, I call the void saveHiscore() function. I also will assume I have a function called when I open the game called char readHiscore().

Saving Highscore

Here’s the code for saving highscore:

void saveHiscore() {
  fileOpen("hiscore.txt", FILE_MODE_READWRITE);
  fileSetPosition(0);
  filePutChar(hiscore);
  fileClose();
}

Let’s go through all of this:

fileOpen("hiscore.txt", FILE_MODE_READWRITE);
What this line of code is doing is opening “hiscore.txt” in the read/write file mode. The read/write file mode is one of many file modes, but this particular one allows the program to both read and write to a file.

fileSetPosition(0);
This line is setting the file “cursor’s” position to the very first character. Notice how the characters start counting at 0 – this is similar to arrays!

filePutChar(hiscore);
This line is where all the magic happens. The code puts the ASCII character with the same number as the value of hiscore into the location of the file “cursor.” This will be able to be read later.

fileClose();
This tells the program that we are done using “hiscore.txt,” so we can use other files if need be. It’s good to always close a file to prevent some problems from happening.

Reading Saved Highscores

Here’s the code for readHiscore():

char readHiscore() {
  char tempHiscore;
  fileOpen("hiscore.txt",FILE_MODE_READWRITE);
  fileSetPosition(0);
  tempHiscore = (char)fileGetChar();
  fileClose();
  return tempHiscore;
}

I won’t go through the functions I already mentioned in the last section, but here are the new ones:

char tempHiscore;
This is a declaration of a new variable. We’re creating this variable so we can return our highscore without having to forgo closing the file.

tempHiscore = (char)fileGetChar();
Here we’re setting tempHiscore to the char found in the file at position 0. We have to cast to a char because for some reason, fileGetChar() returns an int.

return tempHiscore;
This is fairly self-explanatory. We’re returning the value we found in the file so we can use it for something (probably setting hiscore to the saved highscore!).

Conclusion

We now have a working highscore saving mechanism. If we integrate this into our game appropriately, we should be able to not have all our progress deleted when we restart the Pokitto, which is obviously a good thing!


[Game]cPong [WIP]
#2

Hope you don’t mind me adjusting the formatting, you can undo it or change bits back if there’s changes you don’t like.


#3

I want to save an unsigned short, what exactly must stand in the file (it should be 2 characters) and how I write this in filesetposition() ?


#4

I think fileWriteBytes and fileReadBytes might be better for that but they haven’t been covered in the tutorial.

Don’t quote me on this but the code should be something like:

uint16_t data;
fileWriteBytes(reinterpret_cast<uint8_t *>(&data), static_cast<uint16_t>(sizeof(data)));

And to read:

uint16_t data;
uint16_t bytesRead = fileReadBytes(reinterpret_cast<uint8_t *>(&data), static_cast<uint16_t>(sizeof(data)));
// May want to assert that bytesRead == sizeof(data) to be safe

You can replace static_cast<uint16_t>(sizeof(data)) with 2 since you know uint16_t/unsigned short is 2 bytes wide, but using static_cast<uint16_t>(sizeof(data)) means you don’t have to remember to change the number if you decide to change to using uint32_t or something


#5

Yeah, I’ve only used char so far, so I’ll update the tutorial when I know how to use multiple-byte data.


#6

If I get chance to test to make sure this works I might make a fork, add a templated function and see if I can get it added to the official library.

Making templated functions like this:

template < typename T >
void fileWriteObject(const T & value)
{
  fileWriteBytes(reinterpret_cast<const uint8_t *>(&value), static_cast<uint16_t>(sizeof(T)));
}

template < typename T >
uint16_t fileReadObject(T & value)
{
  return fileReadBytes(reinterpret_cast<uint8_t *>(&data), static_cast<uint16_t>(sizeof(T)));
}

Which you’d use like:

// Write code
uint16_t dataIn = someValue;
fileWriteObject(dataIn);

// Read code
uint16_t dataOut;
uint16_t bytesRead = fileReadObject(dataOut);

And by the magic of templates it would just work for any* type.
(* Exceptions apply.)

Untested of course, but theoretically should work.
This sort of trick works for EEPROM at least.
The only thing I can think that would mess it up is alignment.
(And naturally saving structures with pointers and/or untagged unions would be a bad idea.)


#7
void Racers::save(short hscore)
{
    fileOpen("savestates.txt", FILE_MODE_READWRITE);
    fileSetPosition(0);
    fileWriteBytes(reinterpret_cast<uint8_t *>(&hscore), static_cast<uint16_t>(sizeof(hscore)));
    fileClose();
}

short Racers::load(bool load)
{
    short score = 0;

    fileOpen("savestates.txt", FILE_MODE_READWRITE);
    fileSetPosition(0);
    score = fileReadBytes(reinterpret_cast<uint8_t *>(&score), static_cast<uint16_t>(sizeof(score)));
    fileClose();

    return score;
}

I copied it into my Racers code but every time I load the game is highscore = 2, whatever it was bevor, it changes nothing.


#8

That’s because you’re doing this:

The function doesn’t return the value, it returns how many bytes have been read, so you’re reading the value and then overwriting it with how many bytes have been read (which will always be 2 if it’s worked properly).

That’s why in my example:

I am using two different variables, one of which is named bytesRead because it’s the number of bytes that have been read.


#9

I’m attempting to save a screenshot of my game on hardware

I assume the following should work, but I’m getting nothing…

[code]
bool saveRAW() {
pokInitSD();

fileOpen("image1.raw",FILE_MODE_READWRITE);

if(fileOK() != 0) {
	// File hasn't opened properly,
	// handle this.
	return false;
}

fileSetPosition(0);

// Save immediate area
for (char y = 0; y < 88; y++) {
	for (char x = 0; x < 110; x++) {
        char pix = game.display.getPixel(x,y);
		char color = game.display.palette[pix];
		char r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
        char g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6;
        char b = (((color & 0x1F) * 527) + 23) >> 6;

/*
filePutChar®;
filePutChar(g);
filePutChar(b);
*/
fileWriteBytes(reinterpret_cast<uint8_t *>(&r), static_cast<uint16_t>(sizeof®));
fileWriteBytes(reinterpret_cast<uint8_t *>(&g), static_cast<uint16_t>(sizeof(g)));
fileWriteBytes(reinterpret_cast<uint8_t *>(&b), static_cast<uint16_t>(sizeof(b)));
}
}

fileClose();
return true;

}[/code]


#10

I’m guessing you’re using the older SD library? (petitfatfs)

The old SD library requires the file to already exist and be the correct size. It won’t create, rename or resize files.
The newer SD library (SDFileSystem) will fix this.


Also, what are the multiplications and adding for?:

The numbers seem a bit arbitrary.


#11

656 to 888 rgb conversion


#12

Will as in, not available yet?


#13

I was hoping for an explanation of the magic numbers.
I’m guessing that means you don’t know how the numbers are derived?
(And subsequently I’ll have to look elsewhere.)

That’s the latest topic I could find about the matter, so I’m assuming yes - it’s announced but not released.

Until then, you can pad a file to the right size and only store a limited number of screenshots.

I double checked, seems it’s been added to the lib:

But there’s no Pokitto-specific tutorial yet, so you’ll have to read the documentation or check to see if there’s a tutorial for the original.

There’s a Pokitto example here though:


#14

Yeah, I just googled that, if it worked, i’d probably change it, but I don’t seem to be able to write anything to a pre-existing file.

however if I wrote it myself it would be…

char r = ((color >> 11) & 0x1F) *8; // first 5 bits * 8
char g = ((color >> 5) & 0x3F)*6; // next 6 bits * 6
char b = (color & 0x1F)*8; // last 5 bits * 8

Might be more readable and may give the same reults


#15

That is a fully working example. Just copy the settings to your project, and use fprintf and other common file accessing calls. In addition, it is a clean implementation of the SDFilesSystem, so you can use any examples from mbed.com without changes.


#16

Fair enough.

Most likely the person who wrote it either wanted to make use of a fused shift and add instruction or there’s some mathematically significant reason involving advanced colour theory (like spreading the colour across the colour plane better).

For what it’s worth though, it might be better to just do the inverse of the Pokitto’s Display::RGBto565 function, which is defined as:

uint16_t Display::RGBto565(uint8_t R,uint8_t G,uint8_t B) {
    uint16_t color;
    color = B>>3;
    color |= ((G >> 2) << 5);
    color |= ((R >> 3) << 11);
    return color;
}

Hence the inverse would be (excuse the stylistic differences)

uint32_t rgb565ToRgb888(uint16_t colour)
{
    uint8_t b = static_cast<uint8_t>(((colour >> 0) & 0x1F) << 3);
    uint8_t g = static_cast<uint8_t>(((colour >> 5) & 0x3F) << 2);
    uint8_t r = static_cast<uint8_t>(((colour >> 11) & 0x1F) << 3);
    return static_cast<uint32_t>(r << 16 | g << 8 | b << 0);
}

Or to optimise it (if you don’t trust the compiler to optimise it for you, though I’m willing to bet this version results in the exact same code):

uint32_t rgb565ToRgb888(uint16_t colour)
{
    return static_cast<uint32_t>
        (
        ((colour & 0x001Fu) << 3) |
        ((colour & 0x07E0u) << 5) |
        ((colour& 0xF800u) << 8)
        );
}

(I tested to check, they give the same results.)

Obviously this leaves the last 2-3 bits truncated, but that can’t be fixed without looking into how the rgb565 values are interprepted by the screen, and to be honest it’s probably not crucial.

If in doubt, fill the 2-bit cases with 0x2 or 0x1 and the 3-bit cases with 0x4 or 0x3.


For what it’s worth, I did some testing and the code you’re using doesn’t line up with the way the Pokitto converts to 565.
I can provide the code I threw together to test if you want (it’s in C#, but there’s no reason it wouldn’t behave the same in C++ as far as I can tell).
I’m guessing it’s some sort of ‘corrected’ system designed to shift the colours more evenly across the colour plane or something.


#17

Hi @spinal I am also looking into saving and loading something to/from the SD card. However I was wondering if there is an error in this piece of your code:

If the file is opened correctly, fileOk() will return true so saveRaw() will do no nothing and return false I think.
Maybe this is the reason why it wasn’t working?


#18

I should have came back to this sooner, I got it working, the code is still in my Pokittaire game. I think the issue was something about not including the correct headers, or the build settings were slightly wrong or something like that.

bool saveBMP(char* filename){
// no longer used
char w=110, h=88;

FILE *f;
int filesize = 54 + 3*w*h;  //w is your image width, h is image height, both int

unsigned char bmpfileheader[14] = {'B','M', 0,0,0,0, 0,0, 0,0, 54,0,0,0};
unsigned char bmpinfoheader[40] = {40,0,0,0, 0,0,0,0, 0,0,0,0, 1,0, 24,0};
unsigned char bmppad[3] = {0,0,0};

bmpfileheader[ 2] = (unsigned char)(filesize    );
bmpfileheader[ 3] = (unsigned char)(filesize>> 8);
bmpfileheader[ 4] = (unsigned char)(filesize>>16);
bmpfileheader[ 5] = (unsigned char)(filesize>>24);

bmpinfoheader[ 4] = (unsigned char)(       w    );
bmpinfoheader[ 5] = (unsigned char)(       w>> 8);
bmpinfoheader[ 6] = (unsigned char)(       w>>16);
bmpinfoheader[ 7] = (unsigned char)(       w>>24);
bmpinfoheader[ 8] = (unsigned char)(       h    );
bmpinfoheader[ 9] = (unsigned char)(       h>> 8);
bmpinfoheader[10] = (unsigned char)(       h>>16);
bmpinfoheader[11] = (unsigned char)(       h>>24);

f = fopen(filename,"wb");
fwrite(bmpfileheader,1,14,f);
fwrite(bmpinfoheader,1,40,f);


for(int y=0; y<h; y++){
    for(int x=0; x<w; x++){
        int pix = game.display.getPixel(x,h-y-1);
        int color = game.display.palette[pix];
        char r = ((((color >> 11) & 0x1F) * 527) + 23) >> 6;
        char g = ((((color >> 5) & 0x3F) * 259) + 33) >> 6;
        char b = (((color & 0x1F) * 527) + 23) >> 6;
        fwrite(&b , 1 , sizeof(b) , f);
        fwrite(&g , 1 , sizeof(g) , f);
        fwrite(&r , 1 , sizeof(r) , f);
    }
    fwrite(bmppad,1,(4-(w*3)%4)%4,f);
}

fclose(f);
}

#19
#include "Pokitto.h"

Pokitto::Core game;

bool saved = false;
bool success = false;

uint16_t counter = 0;
uint16_t readByte = 0;
int reader = 0;
int helper = 0;

int main()
{
    game.begin();

    while (game.isRunning())
    {
        if (game.update())
        {
            if (!saved)
            {
                if (fileOpen("data.txt", FILE_MODE_READWRITE)) success = true;
                while (counter <= 50)
                {
                    fileSetPosition(sizeof(uint16_t) * counter);
                    fileWriteBytes((uint8_t*)&counter, sizeof(uint16_t));
                    counter++;
                    game.display.print((int)counter);
                    game.display.update();
                }
                fileClose();
                saved = true;
            }
            else
            {
                if (game.buttons.released(BTN_A))
                {
                    fileOpen("data.txt", FILE_MODE_READONLY);
                    fileSetPosition(sizeof(uint16_t) * reader);
                    fileReadBytes((uint8_t*)&readByte, sizeof(uint16_t));
                    fileClose();
                    reader++;
                    helper++;
                }
                game.display.print(readByte);
                game.display.setCursor(0,20);
                game.display.print(success);
            }
        }
    }
    return 0;
}

I can’t figure out what mistake I made. It’s just showing a 0 every time. Any ideas?


#20

Zero bytes in reading? Have you checked that the file in sd contains data?