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:
4:d8f51b136dbd
Parent:
3:b2a132553b02
Child:
12:d9a44fb3b9a6

File content as of revision 4:d8f51b136dbd:

/* 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
{

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

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)
{
    // Check the address and size fit onto the chip.
    if (!checkSpace(address, 1))
        return 0;
    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)
{
    // Check the address and size fit onto the chip.
    if (!checkSpace(address, size))
        return 0;
    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)
{
    // Check the address and size fit onto the chip.
    if (!checkSpace(address, 1))
        return 0;
    char values[3];
    convert_address(address,values);
    values[2] = value;
    if (m_i2c.write(m_i2cAddress, values, 3) != 0) {
        return 0;
    }

    waitForWrite();
    return 1;
}

size_t I2CEeprom::ll_write(size_t address, const char *buffer, size_t size)
{
    // TODO: change i2c address bits dependent on eeprom data address
    // if size > 64k I 
    if (m_i2c.write(m_i2cAddress, buffer, size) != 0) {
        std::cout << "EE i2c write failed\n";
        return 0;
    }
    waitForWrite();
    return size;
}

size_t I2CEeprom::write(size_t address, const char *buffer, size_t size)
{
    if (!checkSpace(address, size)) {
        return 0;
    }
    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;
}

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);
    }
}