The Minty Firmware is the software burned into the PIC microcontroller and which does eveything but decode the mp3's themselves. The code is divided into subsections: the menu system for debugging/MintyComm interaction, the Compact Flash controller, the FAT16 decoder & file reader/writer, the MP3 play loop, the button query procedure and STA013 initialization and configuration.

It's written in CCS PICC, which means its very portable and can easily be recompiled or translated for other microcontrollers. If you don't want to pay for the compiler, I include the HEX firmware file and asm file.

All of the code is written by moi and is published under the MIT license, which is a derivative of the BSD license.


This file contains basic constants like the crystal frequency (20MHz recommended), the serial speed (115kbps will make file transfers just barely reasonable.) It also has the pin assignments. So if you want to change the hardware around I'd peek here. Make sure that the firmware version matches the hardware version or you are majorly bummin'.

byte buttonquery() checks the buttons and returns a value corresponding to whether any are pressed. For some reason I haven't determined, button 3 doesnt work. It might have something to do with the fact that I'm sharing that pin with the UART. Theres a timeout to reduce button bouncing (100ms for volume change, 250ms for track change.)

void convertToFATName(char *name) takes a 8.3 format name and makes into an 11 character name as FAT understands them.

boolean processCommand() runs a menu that will test the various aspects of the card, to get to it, strike a key during the "waiting for char" part of the booting process.

void main() sets up the chip, turns off the ADC, turns on the timer #2 interrupt, configures the ports, boots up the hardware, and resets the volume. It then waits for 3s to hit a key and go to the menu or else just starts playing through all the files in the root directory. It also handles next/prev track requests.


This file contains the bitbanging and manipulation of the Compact flash card, below the level of FAT format. The card is used in 8 bit mode, 8 data lines, 3 address lines, and a few manipulation lines.

boolean is_CF_Out() checks the CF_CD (card detect) line, which is pulled low whenever the card is inserted

void CF_SetAddr(byte addr) puts the bottom 3 bits of the byte onto the addr lines to the CF

void CF_TaskFileWrite(byte addr, byte data) writes data in a given mode

byte CF_TaskFileRead(byte addr) reads data in a given mode

boolean CF_isBusy(void) checks the task file to see if the compact flash is busy doing something

boolean CF_isReady(void) checks if the CF is ready (apparently not the same as it not being busy)

void CF_ResetCard(void) resets the card by pulling the RST line high for 255 ms

void CF_Disable(void) sets the default CF unused configuration

byte CF_Enable(void) resets and enables the card for reading

void CF_Identify(void) sends the ATA Identify command and spits out (in big-endian) the 1kb of response. There is a lot of currently-unparsed data in there about the card but if you just look at the output you'll see some stuff that will reassure you that things are 'ok' For example, this is a Toshiba 128MB card:

mp3> i
Identifying CF.. „ŠB ÒSTCB21M8102YA07491A51.1 TOSHIBA THNCF128MAA ÒÒ

void CF_WriteByte(byte data) Strobes one byte of data on the data port

byte CF_ReadByte() Strobes one byte from the data port

CF_Write/Read16/32() does the above, but a few times in succession

void CF_StartSectorRead/Write() Readies the CF card to start reading from/writing to the sector specified in CF_CurrSector

void CF_Read/WriteSectorIntoBuff() reads/writes the contents of the sectorbuff array from/into the CF card.

void CF_DumpSector() dumps the sector specified in CF_CurrSector to stdout


This file contains the firmware for accessing the FAT16 filesystem on a compact flash card

boolean FAT_Init() fetches the MBR (sector 0x0 on the CF) and determines where the FAT and root directory are and formatting information such as sectors/cluster and the number of reserved sectors. It then initializes the player to the root directory

int16 FAT_findFreeClusters(void) goes through the FAT and counts the number of free clusters (which can then be translated into free bytes)

void FAT_ListDir(void) prints out file information for all the files in the current working directory (CWD)

void FAT_PrintCurrFileInfo() prints out the currently loaded file's info

boolean FAT_ChangeDir(char *dir) changes the CWD to be the subdirectory given (or the special un-subdirectories '..' & '.') It works, but I'm not using/supporting it.

boolean FAT_loadFileWithName (char *filename) loads the information for the given file named in the CWD

byte FAT_LoadFileInCWD(int16 filenumber) loads the information for the file which holds that slot number in the current directory, which is not necessarily the n'th file since there are slots taken up by deleted files, and long filename data.

boolean FAT_StartReadFileNum(int num) loads the file in slot number num and starts the microcontroller to read the first sector.

byte FAT_ReadNextSectorIntoBuff() reads the current sector of the currently loaded file and into the sector-size buffer, then it increments to the next sector.

byte FAT_ReadNextFileByte() returns the next byte of a file (as counted by File_CurrByte)

void FAT_ReadFile(char *name) dumps the named file to stdout

boolean FAT_CreateFile(char *name, int16 startcluster, int32 len) creates a new entry in the root directory for a file, then, starting with the given cluster, starts dumping data from stdin into the file until len bytes have been read. For best performance, give it precisely 512 bytes (or less) at a time, otherwise the uart buffer fills and it gets unhappy.

int16 FAT_findNextUnusedCluster(int16 oldcluster) returns the next unused cluster after the one specified. To find the first one, use 0x0002 as the first cluster.

void FAT_QuickFormat() wipes the FAT and root dir. You probably don't want to do this since it removes the 'bad sector' markings from the FAT.

void FAT_DeleteFile(char *name) Removes the given file from the root directory and clears all of its used clusters.

uint32 FAT_ConvertClusterToSector(uint16 cluster) Does the math to convert a cluster number into a sector number (there are multiple sectors per cluster, for FAT16 there can only be 2^16 clusters so if you want to address 2^18 sectors, there will be 4 sectors per cluster. This is all in the boot block in the MBR defined when you format the card.

uint16 FAT_GetNextCluster(uint16 cluster) Looks in the FAT to find the cluster that follows this one.


This file enables/configures/talks to the STA013 mp3 decoder chip, it also takes care of the actual playing of mp3's

void sta_enable/disable() pulls the enable line high/low

boolean sta_identify() requests to read the contents of register 0x1, which should be 0xAC if the STA013 is alive and kicking

byte sta_readreg(byte reg) returns the value in the STA's register reg

byte sta_writereg(byte reg, byte data) writes the data to the STA's register

byte sta_loadconfig() loads the data in config.c into the chip, necessary for it to function properly. Nobody seems to know whats in the file, but it might be a firmware upgrade that allows VBR/high bitrate playing. Also configures it for a 10MHz clock, see the STA013 datasheet/website for more info on what should be changed for different clocks

void sta_setvolume(byte v) sets the software volume, 0 is no attentuation, 255 is completely attenuated

byte sta_getvolume() gets the current software volume

byte sta_playfilenum(byte num) starts playing the file in the root directory in the nth directory slot.

byte sta_playfilename(char *name) same as above, but by name.


Contains the necessary firmware configuration data for the STA013, straight from STA's recommended datafile. The compiler didn't like it stored in one big block, so its in a few 1k pieces. Eit.


This file contains the code for I2C and SPI bit banging. the i2c code plays with the port C tristates, so if you change the pins around, make sure this is still valid.
This guide was first published on Apr 21, 2013. It was last updated on Apr 21, 2013. This page (Firmware) was last updated on Aug 06, 2019.