Closed Palatis closed 6 years ago
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
datasheet says, when doing the read, you first request write to I2C slave
0x50 | ((MEM_ADDR >> 8) & 0x7)
, and writeMEM_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 fromi2c_addr
. this leads to always reading from 0x000XXXXXX on <64kb devices.see https://github.com/Palatis/FRAM_MB85RC_I2C/commit/bf7076ceb03a7a542c8412b6e71257f354e199e5 for a fix.