<SOLVED>Re: Fast data logging using FRAM

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)

Dicky.

Which controller are you using? Does it have the FreeRTOS firmware installed?

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:

  // send START condition
  TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
  while ((TWSR & (0x08)) == 0);

fails to compile because TWCR etc. are not defined anywhere. I was hoping someone would know a library I need to include.

This may indicate that the code was written for a different controller. I have no idea of ESP nor FreeRTOS.

Which highest I2C frequency is supported by the ESP, selectable with setClock() at high level?
Would you show your slow code?

1 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.

Highest speed is 400kHz I think.

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 will try to move this topic to the programming questions category.

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

Dicky.

What board are you using?

The posted code compiles for Uno (IDE version 1.8.19, Win 10)

Download and install the Adafruit_FRAM_I2C library from here.

How to install the library.

There is example code with the library.

1 Like

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.

1 Like

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.

Cross-posts merged.

@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.

Perhaps SPI FRAM memory would be more suitable. Certainly faster than I2C.

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.

1 Like

Thank you, I will remember that for future posts.

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. :grinning: