That topic closed a year ago. I am trying to use FRAM and really need to use the fast code that was shown in that old topic.
One contributor, RedIron, shared a code snippet that does exactly what I want if only I could get it to compile. When I try to compile it none of the low level I2C command mnemonics are recognised.
I am obviously missing something simple, and would welcome a little help.
This is my test project: FRAM_Test.ino (4.4 KB)
Hi, thanks for reply. I'm using a TTGO T-Display which has a Wrover ESP32 MCU with two cores. I have attached the FRAM breakout via I2C. I originally wrote an APP using FreeRTOS and the Adafruit_FRAM_I2C.h library to access the FRAM. It worked fine but reading and writing was painfully slow. (7ms for 12Bytes). I found the topic i mentioned with the code snippet that used low level I2C commands to access the FRAM at high speed. But the snippet didn't give enough information for me to compile it error free. All the I2C stuff like:
I think I may have found what I am looking for in:
"ArduinoData\packages\arduino\hardware\avr\1.8.5\libraries\Wire\src\utility\twi.h"
and
"ArduinoData\packages\arduino\hardware\avr\1.8.5\libraries\Wire\src\utility\twi.c"
But I don't know how to include it in my APP.
Then your problem is the I2C frequency. You measured 128 bytes in 7ms, that's the default 100kHz. With 400kHz it takes 2.5ms even with high level code. Faster only if the controller accepts higher frequencies.
I wrote a sketch to read/write FRAM using Adafruit_FRAM_I2C.h but found it rather slower than I expected. After hunting around on the forum I found a code snippet posted by RedIron which I think would solve my problem. Unfortunately it won't compile because some vital declarations are missing. TWBR, TWCR etc need to be declared presumably in some library or other. If anyone can help supply the missing code I would greatly appreciate it.
The code snippet:
void setup(void) {
Serial.begin(9600);
TWBR = 12;
test();
}
void loop() {
}
void test(void)
{
struct DATA {
uint8_t data01;
uint16_t data02;
uint32_t data03;
char data04;
float data05;
float data06;
uint32_t data07;
};
struct DATA src;
struct DATA *ptrSrc = &src;
struct DATA dst;
struct DATA *ptrDst = &dst;
// populate src data
ptrSrc->data01 = 66;
ptrSrc->data02 = 7788;
ptrSrc->data03 = 0x12345678;
ptrSrc->data04 = 'A';
ptrSrc->data05 = 190.425986;
ptrSrc->data06 = 666.123456;
ptrSrc->data07 = 0xFFAAFFAA;
// write data to FRAM
i2c_write(0xA0, 789, (uint8_t *) ptrSrc, sizeof(src));
// read data from FRAM
i2c_reads(0xA0, 789, (uint8_t *) ptrDst, sizeof(dst));
// display content of destination struct and see if it matches source struct
Serial.print(dst.data01); Serial.print(",");
Serial.print(dst.data02); Serial.print(",");
Serial.print(dst.data03); Serial.print(",");
Serial.print(dst.data04); Serial.print(",");
Serial.print(dst.data05); Serial.print(",");
Serial.print(dst.data06); Serial.print(",");
Serial.print(dst.data07); Serial.print(",");
}
int i2c_write(uint8_t slave, uint16_t fram_address, uint8_t *data, uint16_t length)
{
uint8_t tmp = 0;
int rtn = 0;
// get FRAM memory address to start writing to
uint8_t MSB = (uint8_t) ( (fram_address & 0xFF00) >> 8 );
uint8_t LSB = (uint8_t) ( (fram_address & 0x00FF) >> 0 );
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while ((TWSR & (0x08)) == 0);
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// set MASTER WRITE ADDRESS
TWDR = slave;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// get status
tmp = TWSR;
if (tmp == 0x18) // if a SLAVE responded
{
/**********************
Address byte MSB
**********************/
TWDR = MSB;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
tmp = TWSR;
if (tmp == 0x28) // ACKed
{
rtn++;
}
else if (tmp == 0x30) // NACKed
{
rtn = -1;
}
/**********************
Address byte LSB
**********************/
TWDR = LSB;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
tmp = TWSR;
if (tmp == 0x28) // ACKed
{
rtn++;
}
else if (tmp == 0x30) // NACKed
{
rtn = -1;
}
// proceed to write bytes to SLAVE
for (uint16_t count = 0; count < length; count++)
{
TWDR = data[count];
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
tmp = TWSR;
if (tmp == 0x28) { // ACKed
rtn++;
}
else if (tmp == 0x30) { // NACKed
rtn = -1;
break;
}
}
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}
else if (tmp == 0x20) // no SLAVE response or bus error
{
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
rtn = -1;
}
else {
rtn = tmp;
}
return rtn;
}
int i2c_reads(uint8_t slave, uint16_t fram_address, uint8_t * data, uint16_t length)
{
uint8_t tmp = 0;
int rtn = 0;
// first send address to read from
i2c_write(slave, fram_address, &tmp, 0);
// maybe not needed
for (uint8_t count = 0; count < 64; count++) {
asm("NOP");
}
// send START condition
TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
while ((TWSR & (0x08)) == 0);
// maybe not needed
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// set MASTER READ ADDRESS
TWDR = slave | 0x01;
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
// maybe not needed
for (uint8_t count = 0; count < 32; count++) {
asm("NOP");
}
// get status
tmp = TWSR;
// SLAVE responded
if (tmp == 0x40)
{
for (uint16_t count = 0; count < (length - 1); count++)
{
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
while ((TWCR & 0x80) == 0);
data[count] = TWDR;
}
// last byte
TWCR = (1 << TWINT) | (1 << TWEN);
while ((TWCR & 0x80) == 0);
data[length - 1] = TWDR;
// send STOP
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}
else if (tmp == 0x48) { // bus error
TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}
return rtn;
}
The default clock speed for I2C is only 100kHz, which is SLOW. SOME devices allow much higher speeds, but that must be set explicitly, usually in setup() after initalizing the Wire library. And, the device itself must support whatever higher speed you use. In the code provided, I don't even see Wire.begin() called anywhere, much less any call to change the clock rate, so I would expect the code, as it is, to be rather slow, since the DATA struct is 20 bytes.
TTGO T-Display which uses the ESP32 Wrover chipset. I use FreeRTOS because I need to use both cores, one for a time critical task and the other core for background tasks. I was using the Adafruit_FRAM_I2C library. That library seems to send one byte at a time with a start and end. The code snippet circumvents that restriction. I tried your suggestion to include the Adafruit library but it still won't compile the TWBR declaration isn't visible.
The code uses things like TWBR which obviously aren't declared. What I need to know is the vital #include statements missing from the snippet. Once I can get it to compile and run I will fiddle with the I@C speed, 400kHz should work. The topic showed a considerable performance using that code because of the way the transfers were done.
Since that code is bypassing the Wire library, and directly reading/writing the TWI registers, I have no clue what you need to change or add. That would take considerable time and effort studying the chip datasheet. I question WHY they do not simply use the Wire library. It would certainly simplify the code.
Thanks for the replies Ray. I have used the wire library and it was simple to code and it does work - but not fast enough. That is why I am desperate to compile RedIron's code.
I wish there was a way to contact him through these forums.
@DickyOZ if you need to move a topic to another forum category, just click the pencil icon to the right of the topic title. In addition to editing the title, that icon also allows you to select any category on the forum to move your topic to.
Were you setting the maximum clock rate the chip supports?
Not necessarily. I have used I2C EEPROM chips that run at 2MHz clock rate. I would expect there are likely FRAM and other similar chips that can do the same.
The spec says this FRAM chip will run at 1MHz so I just tried it. It runs OK and now takes 2431µSec to write my data.
This is fast enough for what I want, but I would still very much like to get the low level code working.
I'll have to delve into the I2C spec to see if I can write something to access the registers at the low level. I might be tearing out what little is left of my hair over the next few week but at least I won't get bored.