Some questions concerning reading and writing on/to external memory

Hi People,
I copied a piece of code of which I cannot get my head around.
I'm planning to store integers of data to be examined later.
Therefore I connected an AT24C256 EEPROM data chip on the I2C line which works fine.
At least... The EEprom is detected in the I2C line, due to an I2C test sketch.
Programming the chip is the next step.

I saw an informative clip on Youtube, but I'm missing out on certain explanations.

// Function to write to EEPROOM
void writeEEPROM(int address, byte val, int i2c_address)

If I understand this part correctly, this is a function with the name writeEEPROM.
Whenever this name is called upon, this function will start.
I do not get why, in this matter, the function starts with a void, while in the next part,
a similar part starts with byte (somewhere below).

{
  // Begin transmission to I2C EEPROM
  Wire.beginTransmission(i2c_address);
 
  // Send memory address as two 8-bit bytes
  Wire.write((int)(address >> 8));   // MSB
  Wire.write((int)(address & 0xFF)); // LSB

For instance, this part above...
I guess the "Wire.write (...)" are codewords from the "wire.h" Library, correct?
Secondly, I do not exactly get what is going on above.
What I guess is: An Integer is 16 bits. Writing to a memory address can only be done in bytes,
so the integer is split. It shifts 8 positions, but I do not know how...
And it seems the second part is masked with which HEX FF, which is binary 1111 1111.

  // Send data to be stored
  Wire.write(val);
 
  // End the transmission
  Wire.endTransmission();
 
  // Add 5ms delay for EEPROM
  delay(5);
}

Here below I do not get why it is a variable (byte) "readEEPROM"
First of all: I guess because the program needs that byte of data to work with.
I just did not know a byte could be a function.
Secondly: Why or how does this fit? There was an integer split in half to get it fixed in 2 bytes.
But here it seems to be stored in 1 byte...

// Function to read from EEPROM
byte readEEPROM(int address, int i2c_address)
{
  // Define byte for received data
  byte rcvData = 0xFF;
 
  // Begin transmission to I2C EEPROM
  Wire.beginTransmission(i2c_address);

Here I'm lost... (above) "Begin transmission to I2C EEPROM"
The EEprom needed to send data, when I need it, right?

  // Send memory address as two 8-bit bytes
  Wire.write((int)(address >> 8));   // MSB
  Wire.write((int)(address & 0xFF)); // LSB

Or is it to command (as a master) the EEprom, beeing a slave, to send stored data?
How does the EEprom determine which data to send??

  // End the transmission
  Wire.endTransmission();
 
  // Request one byte of data at current memory address
  Wire.requestFrom(i2c_address, 1);
 
  // Read the data and assign to variable
  rcvData =  Wire.read();

And how come that the transmission is ended, but there is still data requested 1 line below.

  // Return the data as function output
  return rcvData;
}

And I again, assume the requested data is loaded in the rvcData, for further use?

Hard to crack this one in my mind... :wink:

My feeling is, all the things you are asking about are easier to understand as they are used in much simpler code. It seems to me that you just lack a basic understanding of how functions are declared and used. You could get some tutelage on that from C++ reference sites.

An I2C EEPROM IC is not a simple device (at least for a beginner). It's not reasonable to expect to be able to understand device driver code written for it, without some background embedded hardware/software experience.

SO, first off going to answer the 500 questions one at a time... Wire.write() is a class method from wire.h, yes. It writes to the stream one byte (8-bits) at a time.

Wire.write((int)(address >> 8)); // MSB
Wire.write((int)(address & 0xFF)); // LSB

This is a very common way to take a 16-bit variable and split it into a high byte and a low byte. take the 16-bit number and shift right 8 positions and you have the upper 8 in the lower 8, with the upper 8 now being 0s. Take the 16-bit number and "and" it with a bit mask so the upper 8 bits are now 0 and the lower bytes contain the lower 8-bits.

void writeEEPROM(int address, byte val, int i2c_address)The word void in this function declaration is telling the compiler that this function returns void. in other words, a call to this function should not expect a return value, unlike "byte" below. When a function returns a value you can do something like....

byte i;
i = thisFunctionReturnsByte();

So, if you were to try

byte i;
i = writeEEPROM(address, val, i2c_address);

You would get a compiler error.

Thanks for taking the effort to look into this code.
It sure looks like a lot of questions, but I determined some of the chunks of code and gave my view of understanding and hoped you could correct my answers if wrong, and acknowledge when right. :confused:

I determined some of the chunks of code

Honestly, I think it would have been more productive for you to take each one of these questions separately, and just researched it thoroughly. It's how I learned. There aren't very many true mysteries about C++, it's been around for almost 50 years.

Sometimes we all get to a point where we need clarification on some point. But if they are numerous, it reflects a need to study generally.

The last part is the specific behavior of the code you are looking at and it's intended use by it's author... no one else can know why... But, I would assume transmission is send, so it sends, then finishes sending, then waits for a respsonse, then returns the response as a byte :wink:

Perehama:
SO, first off going to answer the 500 questions one at a time... Wire.write() is a class method from wire.h, yes. It writes to the stream one byte (8-bits) at a time.

Wire.write((int)(address >> 8)); // MSB

Wire.write((int)(address & 0xFF)); // LSB



This is a very common way to take a 16-bit variable and split it into a high byte and a low byte. take the 16-bit number and shift right 8 positions and you have the upper 8 in the lower 8, with the upper 8 now being 0s. Take the 16-bit number and "and" it with a bit mask so the upper 8 bits are now 0 and the lower bytes contain the lower 8-bits.

OK! But If I get that correctly, only the (originally) high 8 bits are parsed.
I thought the upper byte and the lower byte were separated and send in two parts of 8 bits.
That figures!
The next question that rises is why there was an integer originally, when the only thing to be sent, was one byte.
No worries here for now. I look into that lateron.

aarg:
Honestly, I think it would have been more productive for you to take each one of these questions separately,

Sorry for that, aarg,
But in my experience, I always end up with the question of very helpful people who ask me to send the whole code for them to examine. Therefore I already posted that code, with the questions fitted in.
I did quite some "studying" on the reference page of this site and I learned a lot.
And I watched a bunch of youtube videos, just to find out after 20 minutes of explanation that it did not cover my problem.

You are going to the wrong place for technical information when you go to YouTube. Use Google to find reference sites. As an example you could use something like "C++ function" as key words. Fine tune to suit. Buy a C++ reference book. I did.

Also, what is your "problem"? Do you really have to understand the internal workings of a device library just to use it?

Perehama:

void writeEEPROM(int address, byte val, int i2c_address)

The word void in this function declaration is telling the compiler that this function returns void. in other words, a call to this function should not expect a return value, unlike "byte" below. When a function returns a value you can do something like....

byte i;

i = thisFunctionReturnsByte();



So, if you were to try


byte i;
i = writeEEPROM(address, val, i2c_address);



You would get a compiler error.

Ah... Of course!
Because a write function does not require data back, it is done with the **void **function.
When data is expected to return, the function is named byte (or **int **or float for that matter?)

This was the first time I encountered functions in my own programming, other than the void loop () and void setup().

aarg:
Also, what is your "problem"? Do you really have to understand the internal workings of a device library just to use it?

Sorry that I'm not you, aarg. :frowning:

There are more ways to learn stuff.
I have a book on C++ which is a huge overkill if all I will ever use are small particles from that book, of which I never know in advance which they are.
I create my own brainwaves, read, watch youtube videos, try, fail, adjust and succeed.
And as my projects grow bigger, or more advanced for that matter, sometimes I get stuck.
Reading a book does not always answer the questions. And books don't answer back if I have a question which is somewhat more specific.

And actually... I think that most users who start with Arduino at a later age work that way.
Do you take a study in France language when you're only on holiday once a year? 8)
Or do you go there, carrying along a dictionary to use when you want to ask directions or some marmalade to go along with the croissant? :wink:
If, after 3 years I still need that dictionary for askin directions, I'd better stay in my own country.

I hope you do not take offense to my metaphor... :zipper_mouth_face:

FTMZ:
Sorry that I'm not you, aarg. :frowning:

No one is expecting you to be more than you are. What aarg is saying is that you are asking a bunch of questions as if they are isolated, but in fact the pattern reveals you don't have much experience with functions. You say you have only used setup() and loop(), and you probably never noticed that the keyword "void" was in front of them. They are in fact void functions, called from the "main" function within main.cpp deep within the Arduino core. Setup is called one time. Then, from within a "for ( ; ; )" loop, a loop that is forever true, loop() is called repeatedly. You have been coddled by the Arduino process, same as all beginners. But, all this time, you've used functions. If you look at the Arduino reference, the bulk of the documentation is dedicated to functions (some are class methods, which are almost the same thing). On each reference page, you are instructed if the function returns a value and what variable type it returns. So don't take it the wrong way because aarg is trying to help you just as much. Take some time to break out the book or google a bit, read a bit on functions, and start to organize your own code into functions.

FTMZ:
OK! But If I get that correctly, only the (originally) high 8 bits are parsed.

No.
First the high 8 are sent, bit shifting them to the lower 8. the function takes a byte as a parameter. The mask is not needed because as bits are shifted right, 0s are shifted in behind them so the upper 8 also contain 0,
Second, the lower 8 are sent, but to insure the value is true, the upper 8 are masked to 0.
If the value is higher than 255, say the value is 1024, how does the compiler round to 8 bytes? We might be able to know, and maybe it's the same way for all compilers, but we don't have to know if we do the bit shift then bit mask. Perhaps it is easier to understand in steps.

int address = 1024;// 16 bit integer with value greater than 255
int highByte = address >> 8;// 16 bit integer. value is <= 255
Wire.write(highByte);// passing 16-bit integer with value <= 255 as a byte parameter
int lowByte = address & 0xFF;// 16 bit integer. value is <= 255
Wire.write(lowByte); // passing 16-bit integer with value <= 255 as a byte parameter

The only difference is that the steps are done in-line.

Perehama:

int address = 1024;// 16 bit integer with value greater than 255

int highByte = address >> 8;// 16 bit integer. value is <= 255
Wire.write(highByte);// passing 16-bit integer with value <= 255 as a byte parameter
int lowByte = address & 0xFF;// 16 bit integer. value is <= 255
Wire.write(lowByte); // passing 16-bit integer with value <= 255 as a byte parameter

Ok, let's see if I got this correct...
We have an integer address, which covers FF FF
This is split in the var highbyte where the upper byte of the integer is seated, and that one has shifted 8 positions down.
Making it a byte with a value that will eventually be recognized as the higher part of the integer.
That byte is sent first.

Then the second step, you take the low byte by taking that integer again and masking off the first 8 bits by using Boolean (isn't it?), the "&" sign, and sending that as well.

Now, this was an example of how to parse an integer to an 8bit device.
Does that also mean I always have to use 2 addresses of the EEprom?
It takes some arrangements to connect the two bytes to one integer again. :wink:

I will look into that code again, knowing this. Maybe it helps me out in that part.

I want to set something straight here...
Don't get me wrong, I appreciate the effort a lot of people take in helping others out.
I've played around with other microcontrollers and up to a certain level, I get the picture.
Isn't it common to dig in first and ask questions if one gets stuck? That is my experience with forums like this.
And following the right Youtube videos has helped me out quite a bit.

Ok, the way I presented my question might look somewhat odd.
I post a piece of code and comment on the parts on how I suspect it to work. With the intention to get feedback on where I might be wrong.
So I am evaluating that code as a whole.

And I do admit my knowledge about functions is poor. But that is why I wanted to get my understanding of the code evaluated. And you know what? That is the way to learn I guess.
But hey... I Did know about VOID setup() and VOID loop (). I singled out that question in another topic a while ago. 8)

FTMZ:
Ok, let's see if I got this correct...
We have an integer address, which covers FF FF

Yes, we have an integer, named "address", not an integer address.

FTMZ:
This is split in the var highbyte where the upper byte of the integer is seated, and that one has shifted 8 positions down.
Making it a byte with a value that will eventually be recognized as the higher part of the integer.
That byte is sent first.

Not quite.... When you do learn about functions, you will learn about parameters and "pass by value" vs "pass by reference". In the case of Wire.write(uint8_t data) you are passing by value, so up to the point where you call the function and pass a value less than or equal to 255, you are working with 16-bit integers. So, we copy the value of address and manipulate it and set the value of highByte to the minupulated value. Then we call Wire.write() and pass the value of highByte. Then we copy the value of address and manipulate it and set the value of lowByte to the manipulated value. Then we call Wire.write() and pass the value of lowByte. Also, it should be noted that Wire.write() is a public class method, of the TwoWire class for the object Wire of that class. It is essentially a function.

FTMZ:
Then the second step, you take the low byte by taking that integer again and masking off the first 8 bits by using Boolean (isn't it?), the "&" sign, and sending that as well.

"Boolean" used as an adjective as in boolean mask would be correct. With the ampersand and the boolean mask, given a truth table for all possible values of address, the upper 8 bits would be 0 and the lower 8 bits would remain identical to the lower 8 of address.

FTMZ:
Does that also mean I always have to use 2 addresses of the EEprom?
It takes some arrangements to connect the two bytes to one integer again.

Yes. Yes, it does.

Perehama:
Yes, we have an integer, named "address", not an integer address.

I knew that. Indeed makes quotation things more clear

... you are passing by value, so up to the point where you call the function and pass a value less than or equal to 255, you are working with 16-bit integers. So, we copy the value of address and manipulate it and set the value of highByte to the minupulated value. Then we call Wire.write() and pass the value of highByte. Then we copy the value of address and manipulate it and set the value of lowByte to the manipulated value. Then we call Wire.write() and pass the value of lowByte.

:o pff... Hard to get this straight
Do I have to read the word "address" as the name of the integer? (like how I forgot the quotation in the top of my post?) "So, we copy the value of address and..." Or do you mean the physical address?

rgrds
FTMZ

[/quote]

FTMZ:
I knew that. Indeed makes quotation things more clear
:o pff... Hard to get this straight
Do I have to read the word "address" as the name of the integer? (like how I forgot the quotation in the top of my post?) "So, we copy the value of address and..." Or do you mean the physical address?

rgrds
FTMZ

My bad. I made your mistake. Someone gave this variable a horrible name. We copy the value of "address" not the address of "address".

Thanks for all the explanations.
It has helped me somewhat further in understanding.