USB EEPROM Memory Stick

Forebrain's LPC1343 has a very neat USB MSC (mass storage class) feature hidden away in its ROM.  This allows Forebrain to be turned into a USB memory stick.  Forebrain also has a 256kbit (32kbyte) EEPROM built onto the board, which can be used as the memory device for the USB MSC feature.  This may be a useful way to extract data off the EEPROM in a data logging example.  We wouldn't actually recommend using something like this as a USB drive since the EEPROM's write speed is very slow (read speed is reasonable).

 

Example 1: simple USB MSC with EEPROM

The following example creates a 32kb drive and supplies the MSCRead and MSCWrite functions which will run the corresponding EEPROM functions to read and write data from the EEPROM.

{syntaxhighlighter brush: cpp}
#include "uafunc.h"

void setup() {
    unsigned int i;
    LEDInit(ALL);
    I2CInit(1000, MASTER);
    MSCInit(32 * KBYTE);
}

void loop() {

}

void MSCRead(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        MSCData[i] = EEPROMReadByte(MSCOffset+i);
    }
}

void MSCWrite(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        EEPROMWriteByte(MSCOffset+i, MSCData[i]);
    }
}{/syntaxhighlighter}

Running this code on Forebrain will cause an uninitialised device to appear.  Since the EEPROM is either blank or contains non-filesyste data, most operating systems will not be able to directly use the drive.  The disk needs to be formatted first in order to be used.  Because of the small size of the drive, some operating systems (such as newer versions of Windows) will be unable to format the drive.  However, the drive can still be read using raw drive editors such as HxD for Windows.

Example 2: Preloading a filesystem.

Download the binary firmware

One solution to getting around the issue of filesystems is to have Forebrain format the memory as required with a pre-written disk image containing the filesystem.  The following example loads a FAT12 filesystem which has a relatively simple boot sector, onto the EEPROM.  Under normal use, and with larger memories, it is possible to allow the operating system to directly format the drive and write a proper filesystem onto it.  This example is provided to illustrate the possibility of saving a template disk image in the firmware for formatting the memory.

{syntaxhighlighter brush: cpp}
#include "uafunc.h"

// These are the raw "disk image" necessary to make a 32kb filesystem on the EEPROM
// The disk image has been chopped up into chunks since the rest of the data
// in the intervening spaces are zeros.
const unsigned char dImgPt1[130] = {
0xeb,0x3c,0x90,0x6d,0x6b,0x64,0x6f,0x73,0x66,0x73,0x00,0x00,0x02,0x04,0x01,0x00,
0x02,0x00,0x02,0x62,0x00,0xf8,0x01,0x00,0x20,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xf4,0x21,0x04,0x42,0x46,0x42,0x52,0x20,0x45,
0x45,0x50,0x52,0x4f,0x4d,0x20,0x46,0x41,0x54,0x31,0x32,0x20,0x20,0x20,0x0e,0x1f,
0xbe,0x5b,0x7c,0xac,0x22,0xc0,0x74,0x0b,0x56,0xb4,0x0e,0xbb,0x07,0x00,0xcd,0x10,
0x5e,0xeb,0xf0,0x32,0xe4,0xcd,0x16,0xcd,0x19,0xeb,0xfe,0x22,0x36,0x34,0x30,0x4b,
0x20,0x6f,0x75,0x67,0x68,0x74,0x20,0x74,0x6f,0x20,0x62,0x65,0x20,0x65,0x6e,0x6f,
0x75,0x67,0x68,0x20,0x66,0x6f,0x72,0x20,0x61,0x6e,0x79,0x62,0x6f,0x64,0x79,0x22,
0x0d,0x0a};
const unsigned char dImgPt2[5] = {0x55,0xaa,0xf8,0xff,0xff};
const unsigned char dImgPt3[3] = {0xf8, 0xff, 0xff};
const unsigned char dImgPt4[26] = {0x46,0x42,0x52,0x20,0x45,0x45,0x50,0x52,0x4f,0x4d,
0x20,0x08,0x00,0x00,0x10,0x99,0x1c,0x3f,0x1c,0x3f,0x00,0x00,0x10,0x99,0x1c,0x3f};

// Some functions to make the LEDs flash in sequence
unsigned int ledFlashPrescale;
void LEDRollUp(void);
void LEDRollDown(void);

// *******
// ****** Start of Initialisation
// *******
void setup() {
    unsigned int i;
    LEDInit(ALL);
    I2CInit(1000, MASTER);
    
    // Check if EEPROM contains valid filesystem, this is crudely done by checking
    // if the first byte of the EEPROM matches up with the first byte of the filesystem's
    if(EEPROMReadByte(0) != dImgPt1[0]) {
        // Zeros the EEPROM data (this is very slow), takes a few minutes
        LEDOn(LED0 | LED2);
        for(i=0; i<0x8000; i++) {
            EEPROMWriteByte(i, 0);
            LEDRollDown();
        }
        LEDOn(ALL);
        // Write the disk image to the EEPROM
        // Note: I don't currently trust EEPROMWrite (page write) to be error free,
        // so am using single byte writes.
        for(i=0; i<130; i++) EEPROMWriteByte(i, dImgPt1[i]);
        for(i=0; i<5; i++)EEPROMWriteByte(i+0x1fe, dImgPt2[i]);
        for(i=0; i<3; i++)EEPROMWriteByte(i+0x400, dImgPt3[i]);
        for(i=0; i<26; i++)EEPROMWriteByte(i+0x600, dImgPt4[i]);
        LEDOff(ALL);
    }
    
    MSCInit(32 * KBYTE);
    LEDOn(LED0);
}

void loop() {

}

// *******
// ****** Memory read and write functions
// *******
void MSCRead(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        MSCData[i] = EEPROMReadByte(MSCOffset+i);
        LEDRollDown();
    }
}

void MSCWrite(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        EEPROMWriteByte(MSCOffset+i, MSCData[i]);
        LEDRollUp();
    }
}

// *******
// ****** Some functions to make pretty LEDs
// *******
void LEDRollUp(void) {
    if(++ledFlashPrescale >= 128) {
        ledFlashPrescale = 0;
        LEDWrite(~(((Port3Data() << 1) & 0xe) | ((Port3Data() >> 3) & 0x1)));
    }
}

void LEDRollDown(void) {
    if(++ledFlashPrescale >= 128) {
        ledFlashPrescale = 0;
        LEDWrite(~(((Port3Data() >> 1) & 0x7) | ((Port3Data() << 3) & 0x8)));
    }
}{/syntaxhighlighter}

Part 1: Disk Image

To avoid having to load the disk image onto the EEPROM manually, we've added the disk image into the flash firmware to be written onto the EEPROM if necessary.  The actual disk image is mostly zeros, so we've split the parts of the filesystem up into chunks with the remaining bytes of the disk image are all zeros, this saves a lot of space, reducing the 32kb disk image to just 164 bytes (smaller if we hadn't added a boot message or drive label).

{syntaxhighlighter brush: cpp; first-line:3}
// These are the raw "disk image" necessary to make a 32kb filesystem on the EEPROM
// The disk image has been chopped up into chunks since the rest of the data
// in the intervening spaces are zeros.
const unsigned char dImgPt1[130] = {
0xeb,0x3c,0x90,0x6d,0x6b,0x64,0x6f,0x73,0x66,0x73,0x00,0x00,0x02,0x04,0x01,0x00,
0x02,0x00,0x02,0x62,0x00,0xf8,0x01,0x00,0x20,0x00,0x40,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xf4,0x21,0x04,0x42,0x46,0x42,0x52,0x20,0x45,
0x45,0x50,0x52,0x4f,0x4d,0x20,0x46,0x41,0x54,0x31,0x32,0x20,0x20,0x20,0x0e,0x1f,
0xbe,0x5b,0x7c,0xac,0x22,0xc0,0x74,0x0b,0x56,0xb4,0x0e,0xbb,0x07,0x00,0xcd,0x10,
0x5e,0xeb,0xf0,0x32,0xe4,0xcd,0x16,0xcd,0x19,0xeb,0xfe,0x22,0x36,0x34,0x30,0x4b,
0x20,0x6f,0x75,0x67,0x68,0x74,0x20,0x74,0x6f,0x20,0x62,0x65,0x20,0x65,0x6e,0x6f,
0x75,0x67,0x68,0x20,0x66,0x6f,0x72,0x20,0x61,0x6e,0x79,0x62,0x6f,0x64,0x79,0x22,
0x0d,0x0a};
const unsigned char dImgPt2[5] = {0x55,0xaa,0xf8,0xff,0xff};
const unsigned char dImgPt3[3] = {0xf8, 0xff, 0xff};
const unsigned char dImgPt4[26] = {0x46,0x42,0x52,0x20,0x45,0x45,0x50,0x52,0x4f,0x4d,
0x20,0x08,0x00,0x00,0x10,0x99,0x1c,0x3f,0x1c,0x3f,0x00,0x00,0x10,0x99,0x1c,0x3f};{/syntaxhighlighter}

Part 2: Flash the disk image if necessary

During the setup, the code first checks to see if the first byte of the EEPROM is the same as that of the disk image (in this case 0xeb).  If it doesn't contain that byte, then we can be reasonably sure that the rest of the EEPROM doesn't contain the disk image, and so we can write in the disk image.  If it does contain the byte, then we make the assumption that the rest of the EEPROM contains a valid disk image and then proceed to initialise the USB MSC function.

Formatting is done by first zeroing the entire EEPROM.  This is by far the most time consuming part of the operation, and is only added her to avoid stray bytes that may already be in the EEPROM.  (The EEPROM also ships from the factory with all the memory sectors containing 0xff).

Once the EEPROM is formatted with the file system, the next time Forebrain is restarted, the code will detect that the first byte of the EEPROM matches that of the disk image, and skip the formatting and go straight onto initialising the MSC functionality.

{syntaxhighlighter brush: cpp; first-line:34;}
    // Check if EEPROM contains valid filesystem, this is crudely done by checking
    // if the first byte of the EEPROM matches up with the first byte of the filesystem's
    if(EEPROMReadByte(0) != dImgPt1[0]) {
        // Zeros the EEPROM data (this is very slow), takes a few minutes
        LEDOn(LED0 | LED2);
        for(i=0; i<0x8000; i++) {
            EEPROMWriteByte(i, 0);
            LEDRollDown();
        }
        LEDOn(ALL);
        // Write the disk image to the EEPROM
        // Note: I don't currently trust EEPROMWrite (page write) to be error free,
        // so am using single byte writes.
        for(i=0; i<130; i++) EEPROMWriteByte(i, dImgPt1[i]);
        for(i=0; i<5; i++)EEPROMWriteByte(i+0x1fe, dImgPt2[i]);
        for(i=0; i<3; i++)EEPROMWriteByte(i+0x400, dImgPt3[i]);
        for(i=0; i<26; i++)EEPROMWriteByte(i+0x600, dImgPt4[i]);
        LEDOff(ALL);
    }{/syntaxhighlighter}

Part 3: The MSCRead and MSCWrite functions

Once MSCInit() is run, Forebrain will connect to the computer and appear as a USB drive.  Reading or writing to this drive from the computer will cause the MSCRead() and MSCWrite() functions to be run.  These functions deal with the data going between the computer and memory device (in this case the EEPROM.

{syntaxhighlighter brush: cpp; first-line:62;}
// *******
// ****** Memory read and write functions
// *******
void MSCRead(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        MSCData[i] = EEPROMReadByte(MSCOffset+i);
        LEDRollDown();
    }
}

void MSCWrite(unsigned int MSCOffset, unsigned char MSCData[], unsigned int MSCLength) {
    unsigned int i;
    for (i=0; i<MSCLength; i++) {
        EEPROMWriteByte(MSCOffset+i, MSCData[i]);
        LEDRollUp();
    }
}{/syntaxhighlighter}

The code is relatively straightforward, in each case looping through the required length of data, and reading or writing data to the EEPROM.  Additional LED functions are added to provide some visual feedback on Forebrain to show that something is happening.

Part 4: LED Eye candy

The LEDs are made to scroll around to indicate that there is something going on.  These don't do anything important other than make the LEDs flash in pattern.

{syntaxhighlighter brush: cpp; first-line:81}
// *******
// ****** Some functions to make pretty LEDs
// *******
void LEDRollUp(void) {
    if(++ledFlashPrescale >= 128) {
        ledFlashPrescale = 0;
        LEDWrite(~(((Port3Data() << 1) & 0xe) | ((Port3Data() >> 3) & 0x1)));
    }
}

void LEDRollDown(void) {
    if(++ledFlashPrescale >= 128) {
        ledFlashPrescale = 0;
        LEDWrite(~(((Port3Data() >> 1) & 0x7) | ((Port3Data() << 3) & 0x8)));
    }
}{/syntaxhighlighter}

 

Roll your own disk image

Download the disk image

To create the disk image, Linux has some built-in tools to create it.  On the command line simply run:

{syntaxhighlighter brush: plain}
[ua@ichneumon ~]$ dd if=/dev/zero of=disk.img count=64
64+0 records in
64+0 records out
32768 bytes (33 kB) copied, 0.00123266 s, 26.6 MB/s
[ua@ichneumon ~]$ mkfs.vfat -F 12 -n "FBR EEPROM" -S 512 disk.img 49
mkfs.vfat 3.0.11 (24 Dec 2010)
Warning: block count mismatch: found 32 but assuming 49.
[ua@ichneumon ~]${/syntaxhighlighter}

This produces a disk image, which can be loaded onto the EEPROM to turn it into a useable filesystem.  We've taken this disk image and used it in Example 2.

© Copyright 2012 Universal Air Ltd.
Universal Air Ltd. is a limited company registered in England and Wales
Company Registration No: 7558530  VAT Registration No: 112372357
Registered Office: 2 Newnham Farm Cottages, Old reading road,
Wallingford, Oxfordshire, OX10 8BW, England