|
TRAXMOD
SD Card Caching Methodology
|
TRAXMOD Forums K9spud.com |
| TRAXMOD |
|
Main Page Support Forum Developer's Area SD Card Interface Microchip MPLAB IDE |
SD Card Caching
The SD Card behaves a bit more like a hard drive than it does RAM. Whenever we want to read data from the SD Card, even just a single byte, the SD Card requires us to read an entire block of 64 bytes minimum. The SD Card requires that you begin reading data on a block boundary, so if the data you need resides in the 64th byte of a 64 byte block, you will have to read in 63 bytes before you can get to the actual byte that you really wanted. You can not 'skip ahead' to the byte you really want.
To work with these limitations, I propose a caching system for loading SD Card data. Here is the general idea of how it should work:
Data Prefetching
The first step is to identify data that is going to be needed sometime in the near future, but hopefully far enough ahead in the future that we will have time to finish reading it in from the SD Card first. In the TRAXMOD music player, every music pattern row processed that starts a new note, modifies the sample offset, or changes the sample number will generate a "SD Cache Prefetch" request.
Each mixing channel will have two "SD Address Requested" variables, a primary and secondary. When the music pattern row processing routine starts a new sample, it will overwrite the "SD Address Requested" variables with the first SD Card block containing the sample data needed, and the next SD Card block containing the next set of sample data that will likely be needed. It will then fire a "SD Cache Prefetch" request.
The "SD Cache Prefetch" routine will scan the table of "SD Address Requested" variables for each channel. It will also scan the "SD Address Cached" table and compare. Any "SD Address Cached" that is no longer being requested will now be free for loading cache data into. If no data is currently being transferred from the SD Card, the routine will begin transfering any "SD Address Requested" that is not yet cached, and update the "SD Address Cached" table appropriately. Primary "SD Address Requested" blocks will have priority over secondary "SD Address Requested" blocks. Channel number will also play a part in the priority of blocks being loaded.
When the SD Card is finished transferring a block, the "SD Cache Prefetch" routine will be fired again to see if there are any more blocks that need caching. If all requested blocks have been cached, the SD Card will stop transferring data for now.
Mixing
By the time the mixing loop is called, the primary address requested block should already be cached in memory. Whenever the mixing loop needs a sample, it will call a routine called "SD GetSample" with the address needed. This routine will scan the "SD Address Cached" table to find the cached block containing the sample address needed. It will do the memory offset calculations to retrieve the correct byte from the cached block and return it.
After the mixing loop is finished, it will check to see if we have finished using the primary "SD Address Requested" block for this channel. If it is finished with that block, the code will assign the secondary "SD Address Requested" into the primary position, determine the next secondary block that will be needed after the new primary block is finished being used, and then generate an "SD Cache Prefetch" request for the new secondary block.
Block Alignment
If you implement the above algorithm by itself, you'll run into a snag. If you write a raw MOD file directly to the SD Card, your sample data will land any where within a block, which can lead to some serious caching problems.
For example, lets say that the begining of a sample happens to land on byte 64 of a 64 byte block. The SD cache will load primary block and then the secondary block that follows. The primary block consists of 63 bytes of data we don't need, and 1 byte of sample data. If the mixing loop needs to use 66 bytes of sample data or more during mixing, we will definitely have a cache miss because we only have the ability to cache 1 + 64 bytes in this example. A quick fix is just to limit the mixing loop to 65 samples at a time.
An even more serious problem can occur when a sample loops. Say for example, that your sample loop happens to start on byte 64 of a 64 byte block, and your sample loop end lands on byte 1 of a 64 byte block. The primary SD cache block will contain 1 byte of sample data at the loop end. The cached secondary block will contain 1 byte of sample data at the loop beginning. Now the mixing loop will definitely have a cache miss after only using 2 bytes of sample data! Yikes!
Here's how to solve the problem. In the first situation, we block align the beginning of samples to block boundaries, instead of just throwing them on the SD Card at any location at random. This unfortunately means that we will no longer be storing raw, unmodified MOD files on the SD Card, but that's the price we have to pay to get it to play smoothly.
In the loop situation, we need to "unroll" the loop until we get to a more healthy situation. Copy one sample from the loop start to the end of the sample loop. Increment the loop start and loop end pointers. Repeat until block alignment of the loop start pointer is achieved. You will now have a worst case scenario of 1 byte of sample data in the primary cached block (loop end) and 64 bytes of sample data in the secondary block.
Thoughts
This caching mechanism should allow us to use the SD Card efficiently. If two channels are playing the same sample at the same time, the caching algorithm will only end up loading the block once instead of wasting time loading it twice for the two separate channels. Instead of hitting the SD Card every time we need the next sample for mixing, we will be hitting the local cache memory instead.
What happens if you have a cache miss? In our application, a cache miss means disaster. The mixing loop is already the biggest consumer of instruction cycles. If it is stalled, waiting on data to arrive from the SD Card, we may end up not making the deadline for the output audio to be played. Hopefully by carefully prioritizing, prefetching, and block aligning in just the right way, the cache will never end up having a cache miss during playback.