sosandroid / FRAM_MB85RC_I2C

Arduino library for I2C FRAM - Fujitsu MB85RC & Cypress FM24, CY15B

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

always reading from memory address 0b000XXXXXXXX on <64kb device

Palatis opened this issue · comments

datasheet says, when doing the read, you first request write to I2C slave 0x50 | ((MEM_ADDR >> 8) & 0x7), and write MEM_ADDR & 0xFF, then request read from the SAME SLAVE ADDRESS to read the data back.

but in the current implementation, we first transfer the MEM_ADDR by calling FRAM_MB85RC_I2C::I2CAddressAdapt which calculates the correct I2C slave address, but then request read from i2c_addr.
this leads to always reading from 0x000XXXXXX on <64kb devices.

see Palatis@bf7076c for a fix.

Hi,
Thank you for this feedback and the pull requests.
I lack a bit time those days but I'll do my homework asap.

Thank too for the support given to issue #9 , I think I have to embeed this solution too and make the lib rock solid.

I am only not yet easy with reinterpret_cast() which breaks the bakward compatibility as mentionned.

reinterpret_cast<> will provide faster code.

AVR is basically BE architecture, the current implementation is actually having byte order wrong, resulting weird bit-byte order.

LE machine (higher bits -> lower bits)
[  byte 3  ][  byte 2  ][  byte 1  ][  byte 0  ]
[ 76543210 ][ 76543210 ][ 76543210 ][ 76543210 ]

BE machine (lower bits -> higher bits)
[  byte 0  ][  byte 1  ][  byte 2  ][  byte 3  ]
[ 01234567 ][ 01234567 ][ 01234567 ][ 01234567 ]

current (mix of the two...)
[  byte 3  ][  byte 2  ][  byte 1  ][  byte 0  ] // LE format
[ 01234567 ][ 01234567 ][ 01234567 ][ 01234567 ] // BE format

i think "correcting" them as soon as possible is a good idea, anyway......

ps. lets keep the discussion over that PR? :-)

I infact don't think embed that readArray() thing into the lib is a good idea.
client codes should be aware of TWI_BUFFER_SIZE problem, and have an option to decide what to do.

maybe have them both in the lib would be another good idea, and have the user choose what to do.

also, i'm planning on templatizing the transport layer, so we can support both SPI and I2C FRAMs... (maybe?)

Hi Palatis,

Nice idea the template with reinterpret cast. I developed a transactional SPI version for FRAM Fujitsu chips which is working now. I'm just need to solve a bug on array reading. You can have a look here : https://github.com/christophepersoz/FRAM_MB85RS_SPI

The point is adressing is really different between I2C and SPI, so I don't know which solution is the best. Two protocol in one library or two distinct library...

some pseudu-code just to show the idea (only showed read()):

class WireTransportT {
public:
  // for device id probing, if we're certain that it will be a 4, 16, 128, or 256 kb device, 
  // we can just use `uint16_t` for this to save code footprint and memory space.
  typedef uint32_t address_t;

  public WireTransportT(uint8_t const i2c_addr, bool const manual, uint32_t const &size):
    _i2c_addr(i2c_addr), _manual(manual), _maxaddress(size >> 4)
  {}

  void begin() {
    // probe for device ID and sizes
  }

  uint8_t readArray(address_t const &framAddr, uint8_t const items, uint8_t * const out) {
    _addressAdept(framAddr);
    Wire.endTransmission();
    uint8_t result = Wire.requestFrom(i2c_addr, items);
    for (uint8_t i = 0;i < items;++i)
      out[i] = Wire.read();
    return result;
  }

private:
  uint8_t _addressAdept(address_t const &framAddr) {
    uint8_t result = ERROR_SUCCESS;
    if (_maxaddress < 16384 >> 4) {
      _i2c_addr = (i2c_addr & 0b11111110) | ((framAddr >> 8) & 0b00000001);
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write(framAddr & 0xFF);
    } else if (_maxaddress < 65536 >> 4)
      _i2c_addr = (i2c_addr & 0b11111000) | ((framAddr >> 8) & 0b00000111);
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write(framAddr & 0xFF);
    else if (_maxaddress < 524288 >> 4) {
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write((framAddr >> 8) & 0xFF);
      Wire.write(framAddr & 0xFF);
    } else /* 1M device */ {
      _i2c_addr = (i2c_addr & 0b11111110) | ((framAddr >> 16) & 0b00000001);
      result Wire.beginTransmission(_i2c_addr);
      Wire.write((framAddr >> 8) & 0xFF);
      Wire.write(framAddr & 0xFF);
    }
    return result;
  }

  uint8_t _i2c_addr;
  bool _manual;
  uint32_t _maxaddress;
};

class SPITransportT {
public:
  // if we're certain that it will be a 4, 16, 128, or 256 kb device, 
  // we can just use `uint16_t` for this to save code footprint and memory space.
  typedef uint32_t address_t;

  SPITransportT(uint8_t csPin, bool /* manual */, uint32_t size):
    _csPin(csPin), _maxaddress(size >> 4)
  {}

  inline __attribute((always_inline)) // force inline this empty method
  void begin() { /* does nothing */ }

  uint8_t readArray(address_t const &framAddr, uint8_t const items, uint8_t * const out) {
    digitalWrite(_csPin, HIGH);
    SPI.send(FRAM_READ);
    _addressAdept(framAddr);
    for (uint8_t i = 0;i < items; ++i)
      out[i] = SPI.read();
    digitalWrite(_csPin, LOW);
    return ERROR_SUCCESS;
  }

private:
  uint8_t _addressAdept(address_t const &framAddr) {
    SPI.send(framAddr & 0xFF); // Bits 0 to 7
    SPI.send((framAddr >> 8) & 0xFF); // Bits 8 to 15
    if (_maxaddress >= 65536 >> 4)
      SPI.send((framAddr >> 16) & 0xFF); // Bits 16 to 23
    return ERROR_SUCCESS;
  }

  uint8_t _csPin;
  uint32_t _maxaddress;
};

template < typename TransportT >
class FRAM {
public:
  FRAM(/* blah */):
    _transport(/* blah */);

  inline __attribute((always_inline)) // always inline this one-liner method
  byte readArray(TransportT::address_t const & framAddr, uint8_t const items, uint8_t * const out) {
    _transport.readArray(framAddr, items, out);
  }
private:
  TransportT _transport;
};

and now we can have

typedef FRAM<WireTransportT> FRAM_I2C; // to use i2c
typedef FRAM<SPITransportT> FRAM_SPI; // to use spi

and the user can use

FRAM_I2C fram_i2c(0x50, true, 0); // fram @ 0x50, probing for device ID
FRAM_SPI fram_spi(pinCS, false, 16384); // fram with CSpin, size 16384

the good thing is, if we don't want device id probing, we can just have a fixed-size TransportT, and the if-else branch is optimized out by the compiler, because all of them can be resolved during compile-time.

tempalte < typename addr_t, uint32_t size >
class WireTransportFixedT {
public:
  typedef addr_t address_t;

  public WireTransportT(uint8_t const i2c_addr, bool const /* manual */, uint32_t const & /* size */):
    _i2c_addr(i2c_addr)
  {}

  inline __attribute((always_inline)) // force inline this empty method
  void begin() { /* does nothing */  }

  uint8_t readArray(address_t const &framAddr, uint8_t const items, uint8_t * const out) {
    _addressAdept(framAddr);
    Wire.endTransmission();
    uint8_t result = Wire.requestFrom(i2c_addr, items);
    for (uint8_t i = 0;i < items;++i)
      out[i] = Wire.read();
    return result;
  }

private:
  uint8_t _addressAdept(address_t const &framAddr) {
    uint8_t result = ERROR_UNKNOWN;
    // the if-else branch is optimized out during compile-time.
    if (size < 16384) {
      _i2c_addr = (i2c_addr & 0b11111110) | ((framAddr >> 8) & 0b00000001);
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write(framAddr & 0xFF);
    } else if (size < 65536)
      _i2c_addr = (i2c_addr & 0b11111000) | ((framAddr >> 8) & 0b00000111);
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write(framAddr & 0xFF);
    else if (size < 524288) {
      result = Wire.beginTransmission(_i2c_addr);
      Wire.write((framAddr >> 8) & 0xFF);
      Wire.write(framAddr & 0xFF);
    } else /* 1M device */ {
      _i2c_addr = (i2c_addr & 0b11111110) | ((framAddr >> 16) & 0b00000001);
      result Wire.beginTransmission(_i2c_addr);
      Wire.write((framAddr >> 8) & 0xFF);
      Wire.write(framAddr & 0xFF);
    }
    return result;
  }

  uint8_t _i2c_addr;
};

and the user does FRAM<WireTransportFixedT<uint16_t, 16384>> fram_i2c_16kbit(0x50, false, 0); to instantiate a fixed size i2c fram.

cool, eigh?

Issue fixed