Tutorial 5: Using the SD Card to Store Maps

From Uzebox Wiki
Jump to navigation Jump to search

Resources for SD card access (PetitFS)

  • Official documentation: [1]
  • Makefile modifications: [2]
  • Fixed seek bug: [3]

Speed of SD card access.

Fast on the emulator. The hardware can be slower if it does not have solid power. For example, if you are powering the Uzebox via an ISP then you may not actually have enough current and it will take a little over 1 second to activate the SD card and read a chunk of data. Power your Uzebox via the DC power adapter for best performance.

In our example each map is a 706 byte chunk of data. Remember, an SD card needs to be read an entire sector at a time. This means 512 bytes per read even if you only want a few bytes. 706 bytes means it will need to read 2 sectors (1024 bytes). If possible, reduce your reads from the SD to be less than 512 bytes. Additionally, it is faster to read a large chunk of data (such as 512 bytes) than to read 1 byte 512 times.

What should and should not be accessed via SD card.

As speed is the main concern it has been suggested that the tileset itself should not be stored on SD card due to speed. It has also been suggested not to store music on the SD card again because of speed concerns. Also, the AVR chip can only execute data from flash or ram. Within a program, only the bootloader can write to flash. So, whatever you want to read from the SD card will need to be pulled into RAM which is more limited.

Basic example of accessing data from SD card.

Make sure that you can actually access the SD card first. This simple test will check to see if the filesystem can be mounted.

#include "petitfatfs/pff.h"		// Bring in the SD FAT handlers.
FATFS fs;	        		  	// Work area (file system object) for the volume 
void checkSDcard(){
	pf_mount(&fs); pf_mount(NULL);	// Mount then unmount SD
	if((res=pf_mount(&fs)) != 0){	// Try mounting
		pf_mount(NULL);				// Clear the workspace
		Print(4 , 23, PSTR("SD test failed!"));			
		Print(4 , 24, PSTR("Power cycle the Uzebox."));	
		PrintByte(0,0, res, true);	
	} else {pf_mount(NULL);}		// Unmount the card.
}

If it fails and you try to read from the SD card anyway you will either get '0' back or something else that you probably don't want.


This next bit will mount the SD, open a file, seek to a position, copy the requested data into a RAM buffer, and then draw that data to the screen. 'themap' is used as a multiplier. Each map in this case is 706 bytes. Seeking in multiples of 706 bytes will read different screens. The 'animation' is for future use for different loading animations.

void loadRAMmap(unsigned long themap, unsigned char animation){
	// 'themap' must be an unsigned long (DWORD) or the math will fail. This function accepts it as unsigned long.
	pf_mount(&fs); 
	res = pf_open("ow.bin"); 
	//Print(0, 4,  PSTR("2:")); PrintByte(4,4, res, true);
	
	res = pf_lseek(706 * themap); // 706 is size of map. Maps are multiples of 706.
	//Print(19, 4,  PSTR("4:")); PrintByte(23,4, res, true);

	// Zero out the screenbuffer then fill it.
	for(unsigned int findex=0; findex < sizeof(screenbuffer1); findex++){screenbuffer1[findex] = 0;}
	res = pf_read(screenbuffer1, 706, &br);
	//Print(6, 4,   PSTR("3:")); PrintByte(10,4, res, true);
	//PrintLong (29, 3, fs.fptr); Print(19, 3,  PSTR("fptr="));
	//PrintInt (17, 4, br, true); Print(12, 4,  PSTR("b="));
	
	// Close the file.
	pf_mount(NULL); 	
	
	// The first two values in a map array are the width and height.
	unsigned char mapwidth  = screenbuffer1[0];
	unsigned char mapheight = screenbuffer1[1];
	unsigned char tileid ;
	
	if(animation == 7) { // Draw 1 row at a time from the top left.
		// Draw as many X's as needed for the number of Y's in the map.
		for(int y=0; y<mapheight; y++){ 
			for(int x=0; x<mapwidth-2; x++){ // The -2 is to draw 30 tiles instead of 32. 
				tileid = (screenbuffer1[(y*mapwidth)+x+3]); // +2 skips the first two values which are screen dimensions. +3 skips 1 x tile. 
				SetTile(x,y+5,tileid); // The y+5 skips the top 5 rows.
			}
		}
	}
}
  • Important functions of Petit FS to understand:
    • pf_mount: Pass the file pointer to this. This activates the SD card.
    • pf_open: Opens a file from the SD card.
    • pf_lseek: Seeks to a file position.
    • res = pf_read(screenbuffer1, 706, &br); Reads a specified number of bytes into a buffer from the current file position. Take notice of the 3rd argument. That variable will soon contain the number of bytes that have been read. It could be very useful in a check to see if you have reached the end of a file or read as much data as you expected.
    • pf_mount(NULL): This dismounts the file-system.

It seems that you cannot just leave a file open for the entire duration of the program. Whenever a function that reads from the SD card is called you will need to mount, open, seek, read, then close the SD card.

Problems you can expect to encounter.

  • If you flash the Uzebox hardware via ISP you will find that the SD card doesn't operate correctly. The solution is to turn the Uzebox On/Off or just remove the card, reset the Uzebox, replace the card, and then reset the Uzebox. You should always have a check near the beginning of your code that makes sure the SD card is accessible.
  • Using the bootloader to flash a game is usually fine. I flash my Uzebox with an ISP and haven't checked with the bootloader recently.
  • Speed. SD cards may offer large quantities of space but speed is not your friend in this case. Design your programs to read data chunks less than 512 bytes, use data compression if possible, make sure that your SD card isn't fragmented.
  • Fragmentation: An old-school method of defragging a storage medium is to move all data from it and then move it back. If you want to you can format the SD card before moving the data back to it. However, I don't and have not encountered a problem.

Binary vs Text and why you can't just copy your maps to the SD card.

  • The gconvert .inc file stores the maps and tileset as C arrays. This works great because then they can just be #include into our program and accessed like any other array. However, you can't just #include a file from SD card. This would only work during compiling anyway. Technically, it is a text file and is not going to be interpreted correctly once the program has been compiled.
  • You will need to store each array side by side in the same file but with only the array values. You won't have commas (,) or anything else. Just the raw binary values. This conversion isn't trivial. It's not too hard but it requires additional skills.
    • You will need to perform this conversion each time you edit your graphics assets. You will want to create an easy to use system for this such as a batch file. My batch file has a menu and I can choose the conversion option from the list.

Conversion of maps from arrays to binary values.

You'll need to write a c program that runs on your computer. I'll soon put my program up for download on the forums. In the meantime, I will explain what the program needs to do.

  • Take all arrays from the gconvert .inc file and place then side by side in one binary file.
  • That same .inc file will have the tileset data at the bottom. This should be written back to the .inc file and the rest of its data removed. The .inc file will only describe the tileset. No need to store data in flash that is stored on the SD card now, right?

So far, makes sense?

Makefile modifications for PetitFS

You will also need to modify your makefile to have the following code in the ## Compile Kernel files section. Copy and paste this and put it in your Makefile:

mmc.o: $(KERNEL_DIR)/petitfatfs/mmc.c 
	$(CC) $(INCLUDES) $(CFLAGS) -c  $<

pff.o: $(KERNEL_DIR)/petitfatfs/pff.c
	$(CC) $(INCLUDES) $(CFLAGS) -c  $<

// TO DO: The rest of this tutorial. Provide example code and a download of the inc2bin program.