Class to provide simple access to I2C EEPROM chiles like Microchip's 24LC range or AMTELS AT24C range. Chips up to 64Kb in size are directly supported. Updated to Mbed OS v 5.1

Dependents:   storage_test

I2CEeprom.cpp

Committer:
skyscraper
Date:
2020-03-28
Revision:
12:d9a44fb3b9a6
Parent:
4:d8f51b136dbd

File content as of revision 12:d9a44fb3b9a6:

/* Simple access class for I2C EEPROM chips like Microchip 24LC
 * Copyright (c) 2015 Robin Hourahane
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <iostream>
#include <algorithm>

#include "I2CEeprom.h"

namespace
{

//return true if system is little-endian
inline bool little_endian()
{
    int n = 1;
    // little endian if true
    return(*(char *)&n == 1);
}

// Put EEprom address into 2 char array
// in corrcet endiannness
void convert_address(size_t const & addressIn, char* arOut)
{
    constexpr size_t int_size = sizeof(size_t);
    union uu {
        uu(size_t a) : address{a} {}
        size_t address;
        const char ar[int_size];
    } u{addressIn};
    static_assert(sizeof(u) == int_size,"");
    if( little_endian()) {
        arOut[1] = u.ar[0];
        arOut[0] = u.ar[1];
    } else {
        //  std::cout << "big endian\n";
        arOut[1] = u.ar[int_size - 1]; // lsb
        arOut[0] = u.ar[int_size - 2]; //nextsb
    }
}
}

I2CEeprom::I2CEeprom(
    I2C & i2c,
    int address,
    size_t pageSize,
    size_t chipSize,
    uint8_t writeCycleTime_ms
):
    m_i2c{i2c},
    m_i2cAddress(address),
    m_chipSize(chipSize),
    m_pageSize(pageSize),
    m_writeCycleTime_ms{writeCycleTime_ms}
{}

size_t I2CEeprom::read(size_t address, char &value)
{
    if (checkSpace(address, 1)) {
        char values[2];
        convert_address(address,values);
        if (m_i2c.write(m_i2cAddress, values, 2) == 0) {
            if (m_i2c.read(m_i2cAddress, &value, 1) == 0) {
                return 1;
            }
        }
    }
    return 0;
}

size_t I2CEeprom::read(size_t address, char *buffer, size_t size)
{
    if (checkSpace(address, size)) {
        char values[2];
        convert_address(address,values);
        if (m_i2c.write(m_i2cAddress, values, 2) == 0) {
            if (m_i2c.read(m_i2cAddress, buffer, size) == 0) {
                return size;
            }
        }
    }
    return 0;
}

size_t I2CEeprom::write(size_t address, char value)
{
    if (checkSpace(address, 1)) {
        char values[3];
        convert_address(address,values);
        values[2] = value;
        if (m_i2c.write(m_i2cAddress, values, 3) != 0) {
            return 0;
        }
        waitForWrite();
        return 1;
    } else {
        return 0;
    }
}

size_t I2CEeprom::ll_write(size_t address, const char *buffer, size_t size)
{
    if (m_i2c.write(m_i2cAddress, buffer, size) == 0) {
        waitForWrite();
        return size;
    } else {
        std::cout << "EE i2c write failed\n";
        return 0;
    }
}

size_t I2CEeprom::write(size_t address, const char *buffer, size_t size)
{
    if (checkSpace(address, size)) {
        size_t const malloc_size = std::min(size,m_pageSize) + 2U;
        char * tempBuffer = new char [malloc_size];
        if ( tempBuffer == nullptr) {
            std::cout << "EE i2c buf malloc failed\n";
            return 0;
        }
        size_t bytesLeft = size;
        size_t numBytesToWrite
            = std::min( size, m_pageSize - (address % m_pageSize));
        while( bytesLeft ) {
            convert_address(address,tempBuffer);
            memcpy(tempBuffer + 2,buffer, numBytesToWrite);
            if ( ll_write(address,tempBuffer, numBytesToWrite + 2U)
                    == numBytesToWrite + 2U) {
                buffer += numBytesToWrite;
                address += numBytesToWrite;
                bytesLeft -= numBytesToWrite;
                numBytesToWrite = std::min(bytesLeft,m_pageSize);
            } else {
                std::cout << "EE i2c write failed\n";
                break;
            }
        }
        delete [] tempBuffer;
        return size - bytesLeft;
    } else {
        return 0;
    }
}

void I2CEeprom::waitForWrite()
{
    ThisThread::sleep_for(m_writeCycleTime_ms);
    // The chip doesn't ACK while writing to the actual EEPROM
    // so loop trying to do a zero byte write until it is ACKed
    // by the chip.
    while (m_i2c.write(m_i2cAddress, 0, 0) != 0) {
        ThisThread::sleep_for(1);
    }
}