Using templates in a library (SOLVED)

I wrote a library for the External Memory interface module on the mega1280/2560. I wanted to incorperate templates into the library so I can pass any type as an argument to the functions. Someone suggested EEPROMWriteAnything from the Arduino Playground. I used the template parts of it in my library. My library and sketch are below. I really don't understand templates. I just copied that part into my library. I got it to compile and work, almost. In the sketch I write to external memory twice. The first time I pass a uint8_t type (20) to the write function, the second time I pass a uint16_t type (256). Then I read back the data from the same two addresses I had written to. In the case of the uint8_t type the data returns correctly (20). In the case of the uint16_t type it returns incorrectly (0).

Here's the sketch:

#include <ExMem.h>

void setup()
{
    Serial.begin(115200);
    // Enables the interface with two sectors with the upper/lower boundry at 0x8000 and no wait-states
    ExMem.enable(16, 0x8000, 0, 0); 
    uint8_t x1 = 20;
    uint16_t y1 = 256;
    ExMem.write(0, 0x0020, x1); // write to lower sector address 0x0020
    ExMem.write(0, 0x0030, y1); // write to lower sector address 0x0030
    // varibles to hold data read
    uint8_t x2; 
    uint16_t y2;
    // The function places the data in the supplied variable
    // and returns the number of bytes read.
    Serial.println(ExMem.read(0, 0x0020, x2), DEC);
    Serial.println(x2, DEC);
    Serial.println(ExMem.read(0, 0x0030, y2), DEC);
    Serial.println(y2, DEC);
}

void loop()
{
}

Sorry, I have to post twice, due to the size of the library.

Here's the library:

#ifndef EXMEM_H
#define EXMEM_H

#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#include <inttypes.h>
#include <avr/io.h>

class exmem
{
    public:
        exmem();
        void enable(uint8_t addressWidth = 16,
        uint16_t lowerLimit = 0x0000,
        uint8_t upperSectorWaitState = 0x00,
        uint8_t lowerSectorWaitState = 0x00);
        void disable(void);
        template <class TYPE> uint16_t write(bool sector, uint16_t address, const TYPE &data);
        template <class TYPE> uint16_t read(bool sector, uint16_t address, TYPE &store);
};


extern exmem ExMem;

static uint16_t upperSectorOffset = 0x0000;
static uint16_t upperLimit = 0xFFFF;
static uint16_t upperSize = 0xDE00;
static uint16_t lowerSize = 0xDE00;

exmem::exmem()
{
}    

void exmem::enable(uint8_t addressWidth, uint16_t upperSectorBoundry,
                   uint8_t upperSectorWaitState, uint8_t lowerSectorWaitState)
{
    switch (addressWidth)
    {
        XMCRB &= 0xF8;
        case 8:
            upperLimit = 0x00FF;
            XMCRB = 0x07;
            break;
        case 10:
            upperLimit = 0x03FF;
            XMCRB = 0x06;
            break;
        case 11:
            upperLimit = 0x07FF;
            XMCRB = 0x05;
            break;
        case 12:
            upperLimit = 0x0FFF;
            XMCRB = 0x04;
            break;
        case 13:
            upperLimit = 0x1FFF;
            XMCRB = 0x03;
            break;
        case 14:
            upperLimit = 0x3FFF;
            XMCRB = 0x02;
            break;
        case 15:
            upperLimit = 0x7FFF;
            XMCRB = 0x01;
            break;
        default:
            upperLimit = 0xFFFF;
            break;
    }
    uint16_t boundry;
    if (addressWidth < 15)
    {
        boundry = 0x0000;
    }
    else if (addressWidth < 16)
    {
        boundry = constrain(upperSectorBoundry, 0x0000, 0x6000);
    }
    else
    {
        boundry = upperSectorBoundry;
    }
    XMCRA &= 0x8F;
    switch (boundry)
    {
        case 0x4000:
            upperSectorOffset = 0x1E00;
            XMCRA |= 0x20;
            break;
        case 0x6000:
            upperSectorOffset = 0x3E00;
            XMCRA |= 0x30;
            break;
        case 0x8000:
            upperSectorOffset = 0x5E00;
            XMCRA |= 0x40;
            break;
        case 0xA000:
            upperSectorOffset = 0x7E00;
            XMCRA |= 0x50;
            break;
        case 0xC000:
            upperSectorOffset = 0x9E00;
            XMCRA |= 0x60;
            break;
        case 0xE000:
            upperSectorOffset = 0xBE00;
            XMCRA |= 0x70;
            break;
        default:
            upperSectorOffset = 0x0000;
            break;
    }
    if (0 < upperSectorOffset)
    {
        lowerSize = upperSectorOffset;
        upperSize = ((1 + upperLimit) - (0x2200 + upperSectorOffset));
    }
    else
    {
        lowerSize = upperSize = ((1 + upperLimit) - 0x2200);
    }
    switch (upperSectorWaitState)
    {
        XMCRA &= 0xF3;
        case 1:
            XMCRA |= 0x04;
            break;
        case 2:
            XMCRA |= 0x08;
            break;
        case 3:
            XMCRA |= 0x0C;
            break;
        default:
            break;
    }
    switch (lowerSectorWaitState)
    {
        
            XMCRA &= 0xFC;
        case 1:
            XMCRA |= 0x01;
            break;
        case 2:
            XMCRA |= 0x02;
            break;
        case 3:
            XMCRA |= 0x03;
            break;
        default:
            break;
    }
    XMCRA |= 0x80;
}

void exmem::disable(void)
{
    XMCRA &= 0x7F;
}

template <class TYPE>
uint16_t exmem::write(bool sector, uint16_t address, const TYPE &data)
{
    uint8_t *pExtMemory = reinterpret_cast <uint8_t *> (0x2200);
    const uint8_t *pData = (const uint8_t *)(const void *)&data;
    uint16_t i;
    i = 0;
    if (sector)
    {
        if (address >= upperSize)
        {
            return i;
        }
    }
    else
    {
        if (address >= lowerSize)
        {
            return i;
        }
    }
    for (; i < sizeof(data); i++)
    {
        if (sector)
        {
            uint8_t *pMemory;
            pMemory = (uint8_t *)(pExtMemory + upperSectorOffset + address++);
            *pMemory = (uint8_t)(*pData);
        }
        else
        {
            uint8_t *pMemory;
            pMemory = (uint8_t *)(pExtMemory + address++);
            *pMemory = (uint8_t)(*pData);           
        }
    }
    return i;
}

template <class TYPE>
uint16_t exmem::read(bool sector, uint16_t address, TYPE &store)
{
    uint8_t *pExtMemory = reinterpret_cast <uint8_t *> (0x2200);
    uint8_t *pStore = (uint8_t *)(void *)&store;
    uint16_t i;
    i = 0;
    if (sector)
    {
        if (address >= upperSize)
        {
            return i;
        }
    }
    else
    {
        if (address >= lowerSize)
        {
            return i;
        }
    }
    for (; i < sizeof(store); i++)
    {
        if (sector)
        {
            uint8_t *pMemory = (pExtMemory + upperSectorOffset + address++);
            *pStore++ = (TYPE)(*pMemory);
        }
        else
        {
            uint8_t *pMemory = (pExtMemory + address++);
            *pStore++ = (TYPE)(*pMemory);
        }
    }
    return i;
}

exmem ExMem = exmem();

#endif

Can someone please tell me why the second read returns incorrectly.

Thanks for your time,
DJ

In the serial monitor, when I run the sketch, I get

1
20
2
0

I should get

1
20
2
256

If I change the second write from uint16_t to uint8_t type it returns correctly. As I stated earlier templates are very confusing to me, but I almost have this working. Frustrating! :stuck_out_tongue_closed_eyes: I really do apreciate any help on this. The functions in question are the last two in the library. The rest of the library works fine.

Again thanks for your time,
DJ

If I break up the uint16_t type into two uint8_t types and write them seperately. It will read back in a uint16_t type as it should. So, the problem is in the write function. I've almost got it. I'll test some more and post back.

DJ

Not 100% sure about what's wrong here, but this line in read()

 *pStore++ = (TYPE)(*pMemory);

looks suspicious to me.
You are "reconstructing" the type byte-by-byte, so you don't want to cast the bytes you read from the external memory to the full type.

Also, I noticed in write() you don't increase pData. I suspect you're writing only the first byte of "data".

Finally (this might be harmless):

const uint8_t *pData = (const uint8_t *)(const void *)&data;

Why a double cast ?

Disclaimer: I could have misunderstood your code... :stuck_out_tongue:

@tuxduino

:smiley: Thank you very much! It finally works. As for the double cast, I'm not sure. Copied it from EEPROMWriteAnything. You've been a big help. If there's anything I can do for you...

Thanks again,
DigitalJohnson

PS. I'll share for anyone that can use it.

External Memory library

ExMem.h

#ifndef EXMEM_H
#define EXMEM_H

#if (ARDUINO >= 100)
#include <Arduino.h>
#else
#include <WProgram.h>
#endif

#include <inttypes.h>
#include <avr/io.h>

class exmem
{
    public:
    exmem();
    void enable(uint8_t addressWidth = 16,
    uint16_t lowerLimit = 0x0000,
    uint8_t upperSectorWaitState = 0x00,
    uint8_t lowerSectorWaitState = 0x00);
    void disable(void);
    template <class TYPE> uint16_t write(bool sector, uint16_t address, const TYPE &data);
    template <class TYPE> uint16_t read(bool sector, uint16_t address, TYPE &store);
};


extern exmem ExMem;

static uint16_t upperSectorOffset = 0x0000;
static uint16_t upperLimit = 0xFFFF;
static uint16_t upperSize = 0xDE00;
static uint16_t lowerSize = 0xDE00;

exmem::exmem()
{
}

void exmem::enable(uint8_t addressWidth, uint16_t upperSectorBoundry,
uint8_t upperSectorWaitState, uint8_t lowerSectorWaitState)
{
    switch (addressWidth)
    {
        XMCRB &= 0xF8;
        case 8:
        upperLimit = 0x00FF;
        XMCRB = 0x07;
        break;
        case 10:
        upperLimit = 0x03FF;
        XMCRB = 0x06;
        break;
        case 11:
        upperLimit = 0x07FF;
        XMCRB = 0x05;
        break;
        case 12:
        upperLimit = 0x0FFF;
        XMCRB = 0x04;
        break;
        case 13:
        upperLimit = 0x1FFF;
        XMCRB = 0x03;
        break;
        case 14:
        upperLimit = 0x3FFF;
        XMCRB = 0x02;
        break;
        case 15:
        upperLimit = 0x7FFF;
        XMCRB = 0x01;
        break;
        default:
        upperLimit = 0xFFFF;
        break;
    }
    uint16_t boundry;
    if (addressWidth < 15)
    {
        boundry = 0x0000;
    }
    else if (addressWidth < 16)
    {
        boundry = constrain(upperSectorBoundry, 0x0000, 0x6000);
    }
    else
    {
        boundry = upperSectorBoundry;
    }
    XMCRA &= 0x8F;
    switch (boundry)
    {
        case 0x4000:
        upperSectorOffset = 0x1E00;
        XMCRA |= 0x20;
        break;
        case 0x6000:
        upperSectorOffset = 0x3E00;
        XMCRA |= 0x30;
        break;
        case 0x8000:
        upperSectorOffset = 0x5E00;
        XMCRA |= 0x40;
        break;
        case 0xA000:
        upperSectorOffset = 0x7E00;
        XMCRA |= 0x50;
        break;
        case 0xC000:
        upperSectorOffset = 0x9E00;
        XMCRA |= 0x60;
        break;
        case 0xE000:
        upperSectorOffset = 0xBE00;
        XMCRA |= 0x70;
        break;
        default:
        upperSectorOffset = 0x0000;
        break;
    }
    if (0 < upperSectorOffset)
    {
        lowerSize = upperSectorOffset;
        upperSize = ((1 + upperLimit) - (0x2200 + upperSectorOffset));
    }
    else
    {
        lowerSize = upperSize = ((1 + upperLimit) - 0x2200);
    }
    switch (upperSectorWaitState)
    {
        XMCRA &= 0xF3;
        case 1:
        XMCRA |= 0x04;
        break;
        case 2:
        XMCRA |= 0x08;
        break;
        case 3:
        XMCRA |= 0x0C;
        break;
        default:
        break;
    }
    switch (lowerSectorWaitState)
    {
        
        XMCRA &= 0xFC;
        case 1:
        XMCRA |= 0x01;
        break;
        case 2:
        XMCRA |= 0x02;
        break;
        case 3:
        XMCRA |= 0x03;
        break;
        default:
        break;
    }
    XMCRA |= 0x80;
}

void exmem::disable(void)
{
    XMCRA &= 0x7F;
}

template <class TYPE>
uint16_t exmem::write(bool sector, uint16_t address, const TYPE &data)
{
    uint8_t *pExtMemory = reinterpret_cast <uint8_t *> (0x2200);
    const uint8_t *pData = (const uint8_t *)(const void *)&data;
    uint16_t i;
    i = 0;
    if (sector)
    {
        if (address >= upperSize)
        {
            return i;
        }
    }
    else
    {
        if (address >= lowerSize)
        {
            return i;
        }
    }
    for (; i < sizeof(data); i++)
    {
        if (sector)
        {
            uint8_t *pMemory;
            pMemory = (uint8_t *)(pExtMemory + upperSectorOffset + address++);
            *pMemory = (uint8_t)(*pData++);
        }
        else
        {
            uint8_t *pMemory;
            pMemory = (uint8_t *)(pExtMemory + address++);
            *pMemory = (uint8_t)(*pData++);
        }
    }
    return i;
}

template <class TYPE>
uint16_t exmem::read(bool sector, uint16_t address, TYPE &store)
{
    uint8_t *pExtMemory = reinterpret_cast <uint8_t *> (0x2200);
    uint8_t *pStore = (uint8_t *)(void *)&store;
    uint16_t i;
    i = 0;
    if (sector)
    {
        if (address >= upperSize)
        {
            return i;
        }
    }
    else
    {
        if (address >= lowerSize)
        {
            return i;
        }
    }
    for (; i < sizeof(store); i++)
    {
        if (sector)
        {
            uint8_t *pMemory = (pExtMemory + upperSectorOffset + address++);
            *pStore++ = (uint8_t)(*pMemory);
        }
        else
        {
            uint8_t *pMemory = (pExtMemory + address++);
            *pStore++ = (uint8_t)(*pMemory);
        }
    }
    return i;
}

exmem ExMem = exmem();

#endif

After I get all the comments back in I'll post it on google code. Enjoy.

DJ

I'm glad my comment helped. :slight_smile:

I've found a small bug. If I pass data type char* to the write function, say char c1 = "hello world", it will return with a value of 2, meaning it wrote 2 bytes. Then I pass the read function a variable of char, say char c2, and read from the same address written to. It also returns with 2, meaning it read 2 bytes. But when I use Serial.print(c2), It prints "hello world" as it should. Why is it returning a value of 2 when it clearly is more. Float returns 4, as do double, long long returns 8, and uint8_t = 1 and uint16_t = 2, all as expected. So what's with char?

Can anyone shed some light on this?

DJ

it will return with a value of 2, meaning it wrote 2 bytes.

The value 2 means that a pointer is 2 bytes. It has NOTHING to do with the amount of data pointed to.

To verify that, create an array of ints, and pass that, via a pointer, to the class. The array can have 27 elements in it, and you will still get a 2 back.

Hmmm... c1 = "hello world" is not quite right. Sad reality is that c == 'h'. "hello world" is a C-string literal, meaning it's a null-terminated string.
If you want to store a char
you have to work with the C-string convention that says the string ends at the first null byte. At run time, the length of a char
(i.e. the length of a C-string) must be determined with strlen() or equivalent method.
Be careful to store the final null byte or the string length, otherwise at read time you won't know where to stop or how many chars to read.

@PaulS
I suspected it had to do with the pointer to the string. Thanks for the reply.

@tuxduino
With this library I only need to know the location of the first address of the string. The write function, or I believe the string literal variable I pass to the function already adds the terminating null for me and the function writes all of it to memory. All I have to do to read the string back is pass the read function the first address of the string and an empty string literal variable to hold it. The read function puts every byte from the first address to the null byte into the variable. I only need to know the length of the string when writing to keep track of where the next unused address is at for the next write to avoid overlap.

Thanks guys, you've help me a lot,
DJ

BTW How can I add karma for you.

Never mind, just click on karma. Duh. :roll_eyes:

I haven't tried saving strings to eeprom with EEPROMWriteAnything, but I suspect your library needs a special case for char*' data. :slight_smile:

@tuxduino
All I need to do is e.g.:

#include <ExMem.h>

void setup()
{
    Serial.begin(115200);
    ExMem.enable(8, 0x0000, 0, 0);
    char* textToMemory = "Hello World!";
    char* textFromMemory;
    ExMem.write(LOWER, 0x0000, textToMemory); // Will return size of pointer(2).
    ExMem.read(LOWER, 0x0000, textFromMemory); // Will return size of pointer(2).
    Serial.println(textFromMemory);
}

void loop()
{
}

// Will print "Hello World!" to the serial monitor.

The library is on google code here: http://code.google.com/p/arduino-exmem-library/downloads/list. If anyone has the time can you please review or better yet test the library and let me know what you think.

Thanks for your time,
DigitalJohnson