Sunday, May 12, 2013

Implementing EEPROM Storage

In a previous blog, we looked at how you can store a bunch of records of varying types but all of fixed length in an EEPROM on an Olimex ethernet SBC. Now let us take a real world example, well, my world anyway, and store that in an EEPROM. As it is on an ethernet SBC, we will also write code to allow the records to be stored and received using a browser. The data to be stored is the IR signals required to control one or more audio or video components like TVs, DVD Players, etc.

The Problem at hand

Let us look at the way IR signals are to be stored. The formats for IR signals have already been discussed in another blog. We have also looked at storing records on an EEPROM. Now let us bring the two together.

Each Manufacturer (e.g. Sony, Panasonic) makes one or more Device Types (e.g. TV, DVD Player). All IR signals from these devices have a Coding Scheme (e.g. Sony 12 bit). Each Device Type has several Functions (e.g. Play, Power On, Vol Up) associated with it. For a given Manufacturer and Function (a Device Type is implied), there is a Coding Scheme. In addition, there is the Command Data associated with it.

Data Structures

Database designers use an Entity Relationship Model to show how the items are related. This is what such a model would look like. Each box is an entity. Lines connecting the entities are relationships. The attributes inside each box are the bare minimum. Each relationship implies that an additional attribute will be added to one of the entities to store the reference to the parent record. For example, the Function will have a name and a reference to the Device.

Translating this to a set of typdefs is the next step. References to other records is stored as an index in a word. This index is the sequence of the parent record. Each record type is assigned a single letter.

// Manufacturer                         // Device Type
typedef struct {                        typedef struct {  
    char recType;                            char recType;    
    char name[REC_SIZE_STRING];              char name[REC_SIZE_STRING];
} REC_M;                                } REC_D;


// Function                               // Coding Scheme
typedef struct {                        typedef struct {
    char recType;                            char recType;
    WORD deviceId;                           char name[REC_SIZE_STRING];
    char name[REC_SIZE_STRING];              BYTE codeType;
} REC_F;                                     BYTE dataBits;
                                             WORD headerMark;
                                             WORD headerSpace;
// Command Data                              BYTE zeroMark;
typedef struct {                             BYTE zeroSpace;
    char recType;                            BYTE oneMark;
    WORD manufId;                            BYTE oneSpace;
    WORD functionId;                         BYTE val1;
    WORD codingSchemeId;                     BYTE val2;
    BYTE data[4];                            BYTE stopMark;
} REC_C;                                     WORD gap;
                                             BYTE rep;
                                        } REC_S;

The recType field holds the single character corresponding to the record type. I may look at not storing this field in the EEPROM as this does not seem useful and takes up an extra byte. The standard string size is 16 bytes and is defined using REC_SIZE_STRING. Each EEPROM page is 264 bytes. The first 3 bytes are used by the page header. The remaining are used to hold records. The list of record types, the number of records stored and the number of pages required for each record type are listed below.

Record Type Code Size Recs/Page Rec Count Pages
Manufacturer M 17 15 20 2
Device Type D 17 15 20 2
Coding Scheme S 30 8 20 3
Function F 19 13 400 31
Command Data C 11 23 2500 109
T O T A L 147

The 147 pages take up about 38K of the 128K. Apart from the small amount of storage required for the TCP/IP stack itself, this leaves about 90K for web pages. This should be more than enough.

Web Interface

All update and retrieval of the records in the EEPROM is done using the web interface. Each function will have a URL associated with it. As it is expected that eventually these URL requests will be used in Ajax requests within more elaborate web pages, the responses are plain text and short.

All web requests use the same file mem.cgi. The parameters passed control the action. The function to be executed is sent as the parameter f and a record, if needed is sent as the parameter r. The record has fields delimited by a vertical bar(|). The response is usually a simple OK or a dump of data in the same delimited format. And while I am at it, I also added a URL to dump any page of the EEPROM. This will help me debug the EEPROM code. The URL requests and the expected responses are listed below.

mem.cgi?f=gta
    Get all records
  Response:
    <type>|<val1>|<val2>|...
    <type>|<val1>|<val2>|...

mem.cgi?f=set&r=<rec type>|<val1>|<val2>|...
    Create a new record
  Response:
    OK

mem.cgi?f=clr
    Clear the record storage completely
  Response:
    OK

ee.cgi?p=<page no.>
    Dump the contents of the page
  Response:
    xx xx xx ...
    xx xx xx ...
    ...

The first three requests use the same file. The file contains just a variable ~ir~. This means all requests will end up calling the routine HTTPPrint_ir(). The last one is similar - it calls the file ee.cgi that just has ~ee~ which uses the routine HTTPPrint_ee(). It is a simpler self-contained routine, so let us look at that first.

void HTTPPrint_ee(void) {
    BYTE i, b;
    char *ptr;
    
    if (curHTTP.callbackPos == 0) {
        ptr = HTTPGetROMArg(curHTTP.data, (ROM BYTE *)"p");
        // start EEPROM read, set page
        XEEInit();
        XEEBeginRead(Convert_String_to_Int(ptr) * 264);
        curHTTP.callbackPos = 68;
        return;
    }

    if(TCPIsPutReady(sktHTTP) >= 25u)    {
        if (curHTTP.callbackPos % 4 == 0) {
            TCPPut(sktHTTP, '\n');
            TCPPut(sktHTTP, toHex(((BYTE)((68 - curHTTP.callbackPos)/4)) >> 4));
            TCPPut(sktHTTP, toHex(((BYTE)((68 - curHTTP.callbackPos)/4)) & 0xf));
            TCPPut(sktHTTP, '0');
            TCPPut(sktHTTP, ':');
            TCPPut(sktHTTP, ' ');
        }
        else if (curHTTP.callbackPos % 4 == 1) {
        }
        else {
            for(i = 0; i < 8; i++) {
                // Read a byte
                b = XEERead();
                TCPPut(sktHTTP, toHex(b >> 4));
                TCPPut(sktHTTP, toHex(b & 0xf));
                TCPPut(sktHTTP, ' ');
            }
            if (curHTTP.callbackPos % 4 == 3) {
                TCPPut(sktHTTP, ' ');
            }
        }
        if (--curHTTP.callbackPos == 0) {
            XEEEndRead();
        }
    }
    return;
}

On the first call curHTTP.callbackPos will be 0. Get the page from the URL and set the start address for the EEPROM read routine. These routines are part of the TCP/IP library. Set curHTTP.callbackPos to 68 - this is 8 for each line and 4 for the last one. Remember the page size is 264 i.e. 256 + 8. With each count, a small part of the job is done. On the 0th call put out the address, on the 1st call do nothing, on the 2nd and 3rd call put out 8 bytes each. That will happen about 16 and a half times and the page is completely output. When curHTP.callbackPos reaches 0, finish the EEPROM write and return. You will not be bothered again till the next request.

This call is incorporated into an Ajax call in an HTML page. The resulting page is shown below.

Now to have a look at the HTTPPrint_ir() routine.

#define HTTP_FUNC_GTA  1
#define HTTP_FUNC_CLR  2
#define HTTP_FUNC_SET  3

void HTTPPrint_ir(void) {
    if (curHTTP.callbackPos == 0) {
        PageInit();
        if (!memcmppgm2ram((char*)HTTPGetROMArg(
            curHTTP.data, (ROM BYTE *)"f"), "gta", 3)) {
            // f=gta
            initNextRecord();
            curHTTP.callbackPos = HTTP_FUNC_GTA;
        }
        else if (!memcmppgm2ram((char*)HTTPGetROMArg(
            curHTTP.data, (ROM BYTE *)"f"), "clr", 3)) {
            // f=clr
            clearAll();
            curHTTP.callbackPos = HTTP_FUNC_CLR;
        }
        else if (!memcmppgm2ram((char*)HTTPGetROMArg(
            curHTTP.data, (ROM BYTE *)"f"), "set", 3)) {
            // f=set, put rec in buf and save record
            strcpy(recString, (char*)HTTPGetROMArg(
                curHTTP.data, (ROM BYTE *)"r"));
            saveRecord();
            curHTTP.callbackPos = HTTP_FUNC_SET;
        }
        return;
    }
    else if (curHTTP.callbackPos == HTTP_FUNC_GTA) {
        if (TCPIsPutReady(sktHTTP) >= sizeof(recString)) {
            // still dumping records
            if (getNextRecord() != 0) {
                // No more records, finish call
                curHTTP.callbackPos = 0;
            }
            else {
                TCPPutString(sktHTTP, recString);
                TCPPut(sktHTTP, '\n');
            }
        }
    }
    else if (curHTTP.callbackPos == HTTP_FUNC_CLR
        || curHTTP.callbackPos == HTTP_FUNC_SET) {
        if (TCPIsPutReady(sktHTTP) >= 3) {
            TCPPutROMString(sktHTTP, (ROM BYTE*)"OK\n");
            curHTTP.callbackPos = 0;
        }
    }
}

For the Get All function, the routine to get records one by one is initialised and the curHTTP.callbackPos function is set to a non-zero value. With each successive call, a record is retrieved and output. When there are no more records, the callbackPos is set to 0.

For the Set Record function, the record contents are saved in a buffer and the save routine is called. The callbackPos is set to a non-zero value. When there is enough space to send an OK, the callbackPos is set to 0.

For the Clear All function, the clearing function is called. When there is enough space to send an OK, the callbackPos is set to 0.

Ideally, all EEPROM functions should be executed in the main routine rather than the web request routine. To keep it simple, I have done it in the request itself. Web requests taking a few milliseconds are OK and not unusual.

All these requests have very basic input and output data. This is because they are expected to be used in more elaborate web pages in the form of Ajax calls. The web pages themselves have not been written yet and I hope to blog about them when they are done. But that will take a long time and I shall set callbackPos to 1. Check back later for a 0.

No comments:

Post a Comment