Fast data logging using FRAM

Hi guys,

I've been looking for various solutions for fast and reliable data logging.
In another topic, I've shown my results using an SD card for logging data.
https://forum.arduino.cc/index.php?topic=718318.0

I recently got an "Adafruit I2C Non-Volatile FRAM Breakout - 256Kbit / 32KByte" and I've tried logging data on to it. My goal is to compare FRAM vs SD card logging speed.

Using an arduino pro mini (5v version), an standard SD card + breakout (over SPI), I'm able to log 33Kbytes of data in 221 ms.
Using an arduino nana (5v version), Adafruit's FRAM (over I2C), I'm able to log 33Kbyte of data in 13'995 ms.

I'm quite surprised by the results of the FRAM logging speed, which is much slower than the SC card. I've read that FRAM was supposed to by quicker.

  • Could someone explain why, in my exemple, it is 63 times slower ?
  • Is there any ways to make is faster ? Like increasing I2C speed?
  • Is it even fair to compare SPI vs I2C ? Or is FRAM really that much slower regardless of IC2 or SPI?

I've attached both codes.
Any help is appreciated, thanks everyone.

z_SD_card_speed_test_0.ino (3.26 KB)

z_Fram_I2C_speed_test_0.ino (1.06 KB)

1 Like

More members will see your code if posted properly. Read the how get the most out of this forum sticky to see how to properly post code. Remove useless white space and format the code with the IDE autoformat tool (crtl-t or Tools, Auto Format) before posting code.

The max SPI frequency from 16MHz Arduino would be 8MHz, while the I2C hardware can go up to 400kHz. If you are talking about pure transmission speed, SPI hands down is faster than I2C. Now if you include the time it takes to do block/page writes on an SD card, compared to the FRAM, then yes, FRAM would beat the SD card.

For your SD card test, you have total_Time declared as an int, try changing that to unsigned long. You are also recording the time before closing the file, that will miss the time needed to write out the last buffer.

The FRAM test is writing to 33,000 addresses on a chip that only has 32K (32,768).

For the timing test to be valid, the same sketch should be used for both tests, just figuring out the amount of data being written takes a bit of work when the FRAM test directly writes one byte at a time, while the SD card test is writing out a struct multiple times (I'm not going to take the time to add up the size of the struct and multiply it out to see how many bytes are actually being written).

Hi guys, thanks for all your great inputs.

@ groundFungus : You are right, it's better for everyone. I also simplify the codes so that only the data logging part are shown, everything else doesn't matter for now.

@ hzrnbgy : I didn't took that into account while buying the FRAM module, should have taken the SPI one.

@ david_2018: 33'000 addresses....yeah should have known better. Why should I use unsigned long instead of int ? I estimate that it should take less than 32'767 milliseconds that's why I chose int. Is it false to int it that example?

I did the test again, using the codes below, and still found a large difference. I'm saving 20Bytes in each test.
Code used with the SD card, arduino pro mini. I'm using struct because it's the fastest way I've found to save different types of data. For this example, I'm only saving int, but I might have to save floats or longs.

#include <SPI.h>
#include "SdFat.h"
SdFat SD;

const int chipSelect = 8;
File dataFile;

struct datastore {
  int variable1;
  int variable2;
  int variable3;
  int variable4;
  int variable5;
  int variable6;
  int variable7;
  int variable8;
  int variable9;
  int variable10;
}; // 10*2bytes = 20bytes

unsigned long millis_at_Start;
unsigned long total_Time;

void setup() {
  Serial.begin(9600);

  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    return;
  }
  dataFile = SD.open("log.txt", FILE_WRITE);

  // Start timer;
  millis_at_Start =  millis();

  // Save 20Bytes as fast as possible, 20bytes * 1000 = 20Kbytes
  for (int counter = 0; counter < 1000; counter++) {
    struct datastore myData;
    myData.variable1 = myData.variable2 = myData.variable3 = myData.variable4 = myData.variable5 = myData.variable6 = myData.variable7 = myData.variable8 = myData.variable9 = myData.variable10 = 42;
    dataFile.write((const uint8_t *)&myData, sizeof(myData));
  }

  // Print time is took to save 20Kbytes
  dataFile.close();
  total_Time = millis() - millis_at_Start;
  Serial.print("Total time to save 20Bytes : ");
  Serial.print(total_Time);
  Serial.print(" ms");
}

void loop() {
}

Code used with the FRAM module, arduino pro mini.

#include <Wire.h>
#include "Adafruit_FRAM_I2C.h"
Adafruit_FRAM_I2C fram     = Adafruit_FRAM_I2C();
uint16_t          framAddr = 0;
int long x = 0;
uint16_t address;
unsigned long millis_at_Start;
unsigned long total_Time;

void setup(void) {
  Serial.begin(9600);
  Serial.println("");

  Wire.begin();
  Wire.setClock(400000);

  fram.begin(0x50);

  // Start timer
  millis_at_Start = millis();

  // Save 20Kbytes as fast as possible
  for (address = 0; address < 20000; address++) {   //write a number into the fram
    fram.write8(address, 42);
  }

  // Print time is took to save 20Kytes
  total_Time = millis() - millis_at_Start;
  Serial.print("Total time to save 20Bytes : ");
  Serial.print(total_Time);
  Serial.print(" ms");
}

void loop() {
}

There is still a massive difference between the two tests, I'm get 149ms for the SD card test and 8481ms for the FRAM.

  • Considering that I2C is 400Khz, why does it takes so long ? I'm saving 20Kbytes at max 400Kbytes, so quick math (there are certainly a lot of things I still do not understand) would give about 50ms to store 20Kbytes. Why does it takes over 150 times longer ?
  • Is there a faster library ? Or a faster way to store data on a fram ?
  • I have not seen a difference, in using wire.setClock(400000) or not ,which might indicate that the FRAM module itself is the limiting part regading data logging speed, am I right ?

Thanks again for your help.

Here's the FRAM function you're using:

void Adafruit_FRAM_I2C::write8(uint16_t framAddr, uint8_t value) {
  Wire.beginTransmission(i2c_addr);
  Wire.write(framAddr >> 8);
  Wire.write(framAddr & 0xFF);
  Wire.write(value);
  Wire.endTransmission();
}

So to write one byte to the fram, that function puts three bytes on the wire, plus whatever overhead begin and end transmission bring to the party.

The SD library buffers in 512 byte chunks. I suspect that it writes those bytes as a chunk or chunks, not as single bytes.

I believe the FRAM chip (MB85RC256V) will let you write bytes sequentially beginning at an intial address, without having to beginTransmission, endTransmission, or send the next address. But I don't know if the library will let you do that.

ShermanP:
But I don't know if the library will let you do that.

It won't, it's really bare bones.

If speed is a concern, then SPI FRAM would probably be a better choice:

I don't have the FRAM module to test but the device itself is pretty simple so I re-wrote one of my library to work with the module

Can you replace this part

just with

int XXX = i2c_write(0xA0, 20000);

and add this subroutine below void loop(){}. It replaces the inefficient Wire.h library with low level code I2C driver for the Atmega328P and uses page write for the FRAM instead of byte write that incurs lots of overhead

int i2c_write(uint8_t slave, uint16_t length)
{
	uint8_t tmp = 0;
	int rtn = 0;

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

	// set MASTER WRITE ADDRESS
	TWDR = slave;
	TWCR = (1<<TWINT) | (1<<TWEN);
	while((TWCR & 0x80) == 0);

	// get status
	tmp = TWSR;

	if(tmp == 0x18)										// if a SLAVE responded
	{
		/**********************
		Address byte MSB
		**********************/
		TWDR = 0x00;
		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 = 0x00;
		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 = 0x51;								// Q
			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;
	}

	return rtn;
}

If I wrote it properly, the value of XXX after the test should be 20002 (total ACKed message from the SLAVE) and you can read anything between 0 and 20000 and you will get 'Q'. If you get -1, then something is wrong with the code and we can try fix it

See if it works...

@ wildbil
I see, it's pretty ineffecient.

@ShermanP
I've read something like that one but never tested it. Apparently it do not work as wildbil says.

@hzrnbgy
Thanks for your help.
For what I understand you are writing directly to registers (I assume the Fram ones) but my understanding ends here ^^.
I've tried to change the code and included your subroutine.

#include <Wire.h>
#include "Adafruit_FRAM_I2C.h"
Adafruit_FRAM_I2C fram     = Adafruit_FRAM_I2C();
uint16_t          framAddr = 0;
int long x = 0;
uint16_t address;
unsigned long millis_at_Start;
unsigned long total_Time;

void setup(void) {
  Serial.begin(9600);
  Serial.println("");

  Wire.begin();
  Wire.setClock(400000);

  fram.begin(0x50);

  // Start timer
  millis_at_Start = micros();

  // Save 20Kbytes as fast as possible
  int XXX = i2c_write(0xA0, 20000);

  // Print time is took to save 20Kytes
  total_Time = micros() - millis_at_Start;
  Serial.print("XXX "); Serial.println(XXX);
  Serial.print("Total time to save 20Bytes : ");
  Serial.print(total_Time);
  Serial.print(" microseconds");
}

void loop() {
}

int i2c_write(uint8_t slave, uint16_t length)
{
  uint8_t tmp = 0;
  int rtn = 0;

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

  // set MASTER WRITE ADDRESS
  TWDR = slave;
  TWCR = (1 << TWINT) | (1 << TWEN);
  while ((TWCR & 0x80) == 0);

  // get status
  tmp = TWSR;

  if (tmp == 0x18)                  // if a SLAVE responded
  {
    /**********************
      Address byte MSB
    **********************/
    TWDR = 0x00;
    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 = 0x00;
    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 = 0x51;                // Q
      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;
  }

  return rtn;
}

And I get this result:

XXX 0
Total time to save 20Bytes : 16 microseconds

I'm a doing something wrong ? Because XXX isn't 20002 and 16 microseconds seems to fast.
I must say I've never seen a code like that so it's difficult to understand what you are actually doing and to modify the code.
Are you "page write" ? What should I google to understand what you are doing ? Are there any beginner tutorial on that topic ?

Once more thanks to all of you for your help and suggestions.

It's just directly accessing the hardware instead of going thru the wire.h library. If you dig enough on the library, you'll see something similar albeit implemented in a less efficient way.

XXX=0 means it didn't even go thru the two "if" statements I had in there. I've seen something like this before so try include the following

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

It will introduce a few cycle of delay in between the start bit and address byte. See if you get anything other than XXX=0 this time.

I'm still getting XXX 0 even after I included the following.

That's a bummer. Alright, we are going to bypass Adafruit's I2C driver entirely. Remove/comment the following. Also added to return the value of the I2C status register so you don't get a zero but some number instead. Make sure you update the i2c_write() function to include that

/****************
#include <Wire.h>
#include "Adafruit_FRAM_I2C.h"
Adafruit_FRAM_I2C fram     = Adafruit_FRAM_I2C();
****************/
	uint16_t	framAddr = 0;
	int long x = 0;
	uint16_t address;
	unsigned long millis_at_Start;
	unsigned long total_Time;

void setup(void)
{
	Serial.begin(9600);
	Serial.println("");

/*******************
  Wire.begin();
  Wire.setClock(400000);

  fram.begin(0x50);
*******************/

	// set I2C baud rate to 400 kHz
	TWBR = 12;
	
	// Start timer
	millis_at_Start = millis();
	
	// start test
	int XXX = i2c_write(0xA0, 20000);

	// Print time is took to save 20Kytes
	total_Time = millis() - millis_at_Start;
	Serial.print("Total time to save 20Bytes : ");
	Serial.print(total_Time);
	Serial.print(" ms");
}

void loop() {
}

int i2c_write(uint8_t slave, uint16_t length)
{
	uint8_t tmp = 0;
	int rtn = 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 = 0x00;
		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 = 0x00;
		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 = 0x51;								// Q
			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 {
                // return status register to see what's up
                TWCR = (1<<TWINT) | (1<<TWEN) | (1<<TWSTO);
		rtn = tmp;
	}

	return rtn;
}

Hi hzrnbgy,

I've tried the new code and it works now, XXX = 20002 and it saved 20kBytes in 476 milliseconds which is quicker than the previous 8481 milliseconds (using the Adafruit and wire libraries). So your approch to not use the wire nor the adafruit library is correct, it is indeed much faster.

This code is basically a speed test, showing how fast you can save 20Kbytes of data onto FRAM. What values are you actually writing in each address ? I thought at first that you wrote the address itself (like address: 50, write 50) but since you go up to 20'000 it can't be the case since it only accepts byte type values.

In a another code I'm working on, I've been able to save data on a SD Card using struct like so:

struct datastore {
  int variable0;
  int variable1;
  int variable2;
  int variable3;
  int variable4;
  int variable5;
  int variable6;
  int variable7;
  int variable8;
  int variable9;
;
};
struct datastore myData;
 
In the loop: 
myFile.write((const uint8_t *)&myData, sizeof(myData));

And it work rather good. I like the approch using struc because it is easy to add or take out variables without having to change a lot in the code.

My goal in the end is to do the same but using Fram and not a SD Card. What must be changed in the code in order the be able to save specific values or even struct type?

476 ms is just about right for 20 Kbytes or 160 Kbits

(160 000 bits) / (400 000 bps) = 400 ms (the 76 ms is the overhead)

It's easy to modify the code

For writing sequential data to the FRAM

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_write(uint8_t slave, uint16_t fram_address, uint8_t *data, uint16_t length)

The slave address is the I2C slave write address. In your case it's 0xA0 (or in Adafruits term 0x50 << 1)

The fram_address is the starting point in the FRAM memory where you want to start writing data to. So it would be from 0x00 to 32K

This part breaks it down to the upper 8 bits and lower 8 bits

	// get FRAM memory address to start writing to
	uint8_t MSB = (uint8_t) ( (fram_addres & 0xFF00) >> 8 );
	uint8_t LSB = (uint8_t) ( (fram_addres & 0x00FF) >> 0 );

And sends it to the FRAM memory as the starting point

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

From there, you can send as much data as you want and the FRAM will automatically increment the memory location as you send each byte

The *data is a pointer variable where your data resides in the Arduino and the length is how many bytes do you intend to transfer starting from this address

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

Since you data store is a struct, you can just copy the address of the struct and put it into a uint8_t * data

I think this will work

uint8_t * data = &myData

Since each int variable in the struct is two bytes, the total length (in terms of bytes) would be 20

So for example, you want to save the data starting from FRAM location 0x00

uint8_t * data = &myData;

i2c_write(0xA0, 0x00, data, 20);

Hi hzrnbgy
Thanks for your help and the detailled explanation.

The code won't compile.

uint16_t  framAddr = 0;
int long x = 0;
uint16_t address;
unsigned long millis_at_Start;
unsigned long total_Time;

struct datastore {
  int variable0;
  int variable1;
  int variable2;
  int variable3;
  int variable4;
  int variable5;
  int variable6;
  int variable7;
  int variable8;
  int variable9;
  ;
};
struct datastore myData;

void setup(void) {
  Serial.begin(9600);
  Serial.println("");

  TWBR = 12;

  // Start timer
  millis_at_Start = millis();

  uint8_t * data = &myData;
  i2c_write(0xA0, 0x00, data, 20);

  // Print time is took to save 20Kytes
  total_Time = millis() - millis_at_Start;
  Serial.print("  Total time to save 20KBytes : ");
  Serial.print(total_Time);
  Serial.print(" ms");
}

void loop() {
}

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;
}
exit status 1
cannot convert 'datastore*' to 'uint8_t* {aka unsigned char*}' in initialization

Your code shows how to save the struc once ? If I want to save it multiple times I would have to call the i2c_write() as many times as a want right ? But everytime I would need to increment the uint16_t fram_address.

Would it be easier to, instead of having to calculate 10*int = 20 bytes, make something like sizeof(myData)

i2c_write(0xA0, 0x00, data, 20);

My goal in the end, as for the other topic, is to save data (of different types) for a certain period of time in fram using the struct and than at the end print it all out. In a another topic, I've been able to do exactly that but by using the wire and adafruit library:
https://forum.arduino.cc/index.php?topic=720754.new#newhttps://forum.arduino.cc/index.php?topic=720754.new%23new

How could I print each "line" ?

line 0 : variable0, variable1, variable2....
line 1 : variable0, variable1, variable2....
line 2 : variable0, variable1, variable2....
....
  uint8_t * data = (uint8_t*)&myData;

Your code shows how to save the struc once ? If I want to save it multiple times I would have to call the i2c_write() as many times as a want right ? But everytime I would need to increment the uint16_t fram_address.

The i2c_write() is a generic function. You can call it as many times as you want. It's your application's job to keep track where you save data in the FRAM. The uint16_t fram_address is just a memory location in FRAM where you start to save data. Since you have a 32K FRAM, you can start anywhere from 0 to 32K. If you want to save space on the FRAM, you can do something like

FRAM 00 --> struct01
FRAM 20 --> struct02
FRAM 40 --> struct03
FRAM 60 --> struct04

since the size of your struct is 20 bytes. But nothing prevents you from doing

FRAM 1024 --> struct01
FRAM 320 --> struct02
FRAM 16000 --> struct03
FRAM 24000 --> struct04

where you store your data at random memory locations.

Would it be easier to, instead of having to calculate 10*int = 20 bytes, make something like sizeof(myData)

It's a matter of preference but that would work too. Like I said, it's a generic function. You can have a data length of 1 or a whole 32K worth of data in a single I2C transaction. That's what makes it more efficient than the Adafruit library. A single transaction for every byte of data

@wildbill
Thanks, it works fine now

@hzrnbgy
Thanks for you explanation. I had something like that in mind

FRAM 00 --> struct01[color=#222222][/color]
FRAM 20 --> struct02[color=#222222][/color]
FRAM 40 --> struct03[color=#222222][/color]
FRAM 60 --> struct04

Does is suggest that float or long variable wouldn’t work? If I understand correctly, it is splitting and 16 bit value into 2 8 bit values right ?

	// get FRAM memory address to start writing to[color=#222222][/color]
	uint8_t MSB = (uint8_t) ( (fram_address & 0xFF00) >> 8 );[color=#222222][/color]
	uint8_t LSB = (uint8_t) ( (fram_address & 0x00FF) >> 0 );

I wanted to test it by myself but I really don’t get how I should read the data back since I’m not familiar with this type of writing.

Reply #11 states:

It's just directly
accessing the hardware instead of going thru the wire.h library. If you dig
enough on the library, you'll see something similar albeit implemented in a
less efficient way

I’ve not been able to dig inside the wire library because i do not find anything. All I find are descriptions of what wire.begin() and stuff do. What should I google to find what you are referring to ?