Master request Array/Slave to send

I can’t see what I 'm doing wrong…

“MasterBasic”

/*I want this to control slave data sending.
 * First the slave will get a request to send data
 * and then slave will respond with data
 *Thats all for now
 */
#include <Wire.h>
const byte MASTER_ADDRESS = 50;
byte SLAVE_ADDRESS;

byte specArray[]={0};

void setup() 
{
  
SLAVE_ADDRESS = 10;
Wire.begin(MASTER_ADDRESS);
Serial.begin(9600);
Serial.println("Setup OK");

}

void loop() 
{
  Serial.println("In the Loop");

byte index = 0;
Wire.requestFrom(SLAVE_ADDRESS, 6);  //Wire.requestFrom(intAddress, intLength, boolStop)
while(Wire.available() > 0 && index < 6)
{
  specArray[index] = Wire.read();
  Serial.println(specArray[index]);
  index++;
  delay(500);
}
  
 
}

and here is “SlaveBasic”

#include <Wire.h>
const byte MASTER_ADDRESS = 50;
const byte SLAVE_ADDRESS = 10;

byte specArray[] = {0,0,0,0,0,0};


void setup() 
{
  
Wire.begin(SLAVE_ADDRESS);
Serial.begin(9600);
Wire.onRequest(requestHandler);
Serial.println("Setup OK");
delay(5);
}

void loop() 
{

byte specArray[] = {1,2,3,4,5,6}; //test only
Serial.println(specArray[0]);

}

void requestHandler()
{
  Wire.beginTransmission(50);
  Wire.write(specArray,6);
  if (Wire.endTransmission () == 0)
    {
    // success!
    Serial.println("Got Sent");
    }
  else
    {
    // failure ... maybe slave wasn't ready or not connected
    Serial.print("Failed");
    
    }
}

The goal is to see the array elements appear in the Master serial monitor as it indexes through the wire.read statment. I have read all the examples I can find but this still has me stumped.

I'm using 2 unos and they are wired correctly...I coped I2C Scan from Nicks page and they show up

If you give Wire.begin() an address then it means you are a slave. The master doesn't have an address. It is a limitation of the Wire library. The protocol allows the master to have an address but it's too complex for the Arduino library to do that type of operation.

Thanks Morgan, I'll try taking it out...but some tutorials say it is optional. it was a clutched straw.

Remove the delay(500) from the Master.
The Wire.read() reads data from the receive buffer in the Wire library, and Wire.available() checks how many data is in the receive buffer (the receive buffer in the Wire library), there is no need to wait.

Remove the Wire.beginTransmission, Wire.endTransmission, Serial.println, Serial.print from the requestFrom() function. Only a single Wire.write is needed. I know that calling the Serial functions from the requestFrom() is in the examples, but don't do it. The requestFrom() is an interrupt handler, and the Serial functions use interrupts, that is bound to go wrong.

Show us the new code, so we can check it.

Man, i could just hug you guys…in a manly sorta way. I made the changes and here is the Master monitor output:

Setup OK
1
2
3
4
5
6

What a great feeling, i’ve put a lot of hours into this simple task…but learned something so not a loss. Serial functions use interrupts! I will remember that one. I have 4 questions:

I wish to continuously collect data but only at a rate of 1 array of 6 bytes every 30 seconds or so. Exact timing is not important. Is it ok to put a delay in the Master Loop?

Master monitor shows a single read through. I expected it to continuously repeat. What is stopping the sketch?

About assigning an address to Master…I wish to eventually expand the network to 4 slaves, all running the same tasks in different locations (within the limits of I2C cable run). Don’t i need to somehow tag the I2C data stream so Master knows it is for him. Oh, I think the penny just dropped… Master will “address” each slave in turn and know who is replying?

Also, please guide me as to when is wire.beginTransmission (and end) supposed to be used? I’m compiling some notes on Wire.h and its usage

Here is the code that transmits array data from Slave and Serial.prints on Master monitor:

Master

#include <Wire.h>
byte SLAVE_ADDRESS;
byte specArray[]={0};

void setup() 
{
SLAVE_ADDRESS = 10;
Wire.begin();
Serial.begin(9600);
Serial.println("Setup OK");
}

void loop() 
{
byte index = 0;
Wire.requestFrom(SLAVE_ADDRESS, 6);           //Wire.requestFrom(intAddress, intLength, boolStop)

while(Wire.available() > 0 && index < 6)
{
  specArray[index] = Wire.read();
  Serial.println(specArray[index]);
  index++
}
 
}

SLAVE

#include <Wire.h>
const byte MASTER_ADDRESS = 50;
const byte SLAVE_ADDRESS = 10;

byte specArray[] = {0,0,0,0,0,0};


void setup() 
{
Wire.begin(SLAVE_ADDRESS);
Serial.begin(9600);
Wire.onRequest(requestHandler);
Serial.println("Setup OK");
delay(5);
}

void loop() 
{
 byte specArray[] = {1,2,3,4,5,6};          //test only
 Serial.println(specArray[0]);                  //does println "1" successfully
}

void requestHandler()
{
  Wire.write(specArray,6);
}

I think you posted the output from the slave, not the master. Where it writes "Setup OK", make it different on the two programs.

The reason is that you have two arrays called specArray in the slave program. One is local to the loop and the other is global. Which one do you think that it will actually send from the interrupt?

I think you posted the output from the slave, not the master. Where it writes "Setup OK", make it different on the two programs.

The reason is that you have two arrays called specArray in the slave program. One is local to the loop and the other is global. Which one do you think that it will actually send from the interrupt?
[/quote]

I just double checked (and made the suggested change to MasterSetup OK...it really is the Master monitor. So that is good.

Mas4
5
6
Master Setup OK
1
2
3
4
5
6

It seems to remember part of the previous data set when it reboots serial monitor.

I sense that the answer is "I think it will send the LOCAL because IF it send the GLOBAL I would only see zeros in the Master monitor. I intended to declare specArray as a global filled with zeros and then later fill it with bytes in the loop. I take it that is not the way to handle this? I need to read analog pins and load them into specArray.

Procyan:
I sense that the answer is "I think it will send the LOCAL because IF it send the GLOBAL I would only see zeros in the Master monitor. I intended to declare specArray as a global filled with zeros and then later fill it with bytes in the loop. I take it that is not the way to handle this? I need to read analog pins and load them into specArray.

It doesn't. It will send the global variable. That function can't even tell that there's another copy that exists at other times.

byte specArray[]={0};

You declare the array on the master to be of length 1, by creating it with one element. What happens when you write to the 5th element of this array? You write over some other memory that is being used for something else. Your declaration must make the array big enough to hold the biggest data you plan to put into it.

In your Reply #5, I see a “index++” without ‘;’.

This is an alternative way for Wire.requestFrom:

int n = Wire.requestFrom(SLAVE_ADDRESS, 6);           //Wire.requestFrom(intAddress, intLength, boolStop)
if( n == 6)
{
  Wire.readBytes( specArray, 6);
}
else
{
  Serial.println("Not 6 bytes received!");
}

for( int index=0; index<6; index++)
{
  Serial.println(specArray[index]);
}

The Wire.readBytes reads the data into a buffer. I have added an extra check to be sure that 6 bytes are received.

There should be only one specArray array of course. Since it is used in both the loop() and the requestHandler(), it must be global.

The global specArray should be volatile. The ‘volatile’ means that the data will be written into its memory location. Otherwise the data can be kepts in registers and the requestHandler would send old data.

Perhaps it is needed to turn off the interrupts if the specArray is filled. The requestHandler could happen while the specArray is being filled. Maybe index 0,1 are filled and 2,3,4,5 are not yet filled, and then the requestHandler happens.

Koepel, Brilliant...I will put your suggestion into sketch and fix the missing ';'

I am at work but will look at it tonight.

How do I ensure my array is volatile?

also you said:

"Perhaps it is needed to turn off the interrupts if the specArray is filled. The requestHandler could happen while the specArray is being filled. Maybe index 0,1 are filled and 2,3,4,5 are not yet filled, and then the requestHandler happens."

That is a worry to me. Should I try Wire.write a signal back to Master that then stops it sending interrupts until specArray is full of new bytes?

I am very grateful

The ‘volatile’ keyword is like the ‘static’ and ‘const’.
I often use a copy of the volatile data, and fill it with the interrupts turned off. But that depends on how they are filled. This is only needed in the Slave, because the Master does not use the specArray in a interrupt handler.

Master
When a variable is declared, it is set to zero. That is defined by the ‘c’-language. It wasn’t defined 20 years ago, but today it is. That means you don’t have to fill the specArray with zeros, because you can be sure that they are already zero.

You could use the Wire.readBytes(), but you don’t have to.

byte specArray[6];     // Create 6 bytes, as mentioned by MorganS, they will be initialized to zeros.

Slave
Use the ‘volatile’ in the Slave.

#include <Wire.h>

const byte MASTER_ADDRESS = 50;
const byte SLAVE_ADDRESS = 10;

volatile byte specArray[6];  // they will be initialized to zeros

...

void loop()
{
  // Turn off the interrupts, to be sure that this set of data is not interrupted.
  // This complete set of data stays together.
  noInterrupts();
  specArray[0] = 1;
  specArray[1] = 100;
  specArray[2] = 110;
  specArray[3] = 20;
  specArray[4] = 25;
  specArray[5] = 30;
  interrupts();
}

void requestHandler()
{
  Wire.write(specArray,6);
}

Or, as an alternative, use a copy of the data, if gathering the data takes a lot of time.
I have also added the ‘sizeof()’.

...

void loop()
{
  byte data[6];        // an array to collect the data into.

  // fill the data, if that takes some time. For example with a slow sensor, or data from Serial input.
  data[0] = 1;
  data[1] = 100;
  data[2] = 110;
  data[3] = 20;
  data[4] = 25;
  data[5] = 30;
  
  // Turn off the interrupts for a very short time, to keep the Arduino running smooth.
  noInterrupts();
  memcpy( specArray, data, sizeof(specArray));         // destination, source, size
  interrupts();
}

void requestHandler()
{
  Wire.write(specArray, sizeof( specArray));
}

And so I learn Sensei!

I made those changes and my data transfer works without error. If you wish to see the code I am happy to paste. I just copied and pasted your provided code.

I am using the address for Master because my reading tells me it is an option and it makes me "feel" like I am in better control :slight_smile: At length I will have more Uno Slaves on the I2C connection.

I used your alternative suggestion (to copy) because I intend to meld this program with my home-made 3-channel RGB Spectrophotometer sketch. I need the data to be collected slowly and averaged then entered into the specArray. Better to less noise and less data for this application (mixing dyes).

I will use 'volatile' in practice until I get the idea better. I think it may be opposite to 'const'

I am having fun and getting some work done, thanks to you and MorganS. What could be better

Could you tell me a little more about the use of begin and endTransmission? I can't find much and would like to learn more about best ways to use I2C.

I would like to mark this as solved but don't see where the button is

begin and endTransMission
The Wire library is not ideal, it is an implementation that is good enough.
There are many Wire libraries now, some software, some hardware, some for other hardware.

For an Arduino Uno, it is like this:

Wire.beginTransmission() does not do much, it sets some variables to start. Wire.write() still does not do much, it only writes the data into a transmit buffer inside the Wire library. The Wire.endTransmission() is the complete I2C transmission over the I2C bus, it transmits the data, and waits for ACK or NACK and returns when everything is finished.
The Wire.beginTransmission() and Wire.endTransmission should only be used when writing data. The sequence must be this: beginTransmission --- write (multiple writes) --- endTransmission.

The Wire.requestFrom() is also a complete I2C transmission over the I2C bus. Everything after that, for example Wire.available() and Wire.read() and Wire.readBytes() only use the data that is in the receive buffer inside the Wire library.

The requestHandler and receiveHandler are part of an interrupt handler from the Wire library. The requestHandler is something special, it allowes only one Wire.write(), that's just how it is. Call it a bug or a feature, as long as you use only one Wire.write() in the requestHandler.

volatile
The compiler can do many optimizations. If for example a function is called with parameters, the compiler might decide not to use the parameters, because the variables might be in the registers already. Or the compiler might not use a memory location for a variable, and keep it in a register all the time. The Arduino Uno has 32 registers (32 bytes). Therefor the compiler can to fancy tricks and even keep an array of 6 bytes in registers.

The keyword 'volatile' means that the compiler must write any new data to the memory location of that variable, to be sure that the memory location has the newest value.
Without 'volatile' the memory location of the variable is probably updated at the end of a function. Well, even then you can never be sure, because the compiler might even decide to replace that function with inline code.

Some think that "const int pinLed = 13" uses two bytes in ram and "const byte pinLed = 13" uses only one byte. But since they are 'const', they might not use ram, perhaps not even flash rom. The compiler might not use a memory location at all, and put the '13' directly as a constant in the code.

When you look at your sketch, you see variables, functions, while-loop, for-loops, and so on. The compiler can change all of that and make something else :wink:

Solved
You don't have to mark this as Solved. But you can modify the subject of the first post of a topic and add "[Solved]" or something like that.

Arrays are always volatile. Reading any contents of the array must make a memory read from SRAM. If you add the volatile keyword to the array declaration, you make the location of the array volatile.

MorganS, I think there is more to it, but looking at the generated code is very hard for me, even with the normal Arduino optimizations.

I make a sketch, with a volatile array. For the test, I created an output using the ‘compile’ button with ‘volatile’ and without.

volatile byte data[4];

And made a loop:

  for( int i=0; i<10; i++)
  {
    for( int k=0; k<4; k++)
    {
      data[k] += x;
      data[k] /= 2;
      data[k] *= 3;
    }

I looked at the compiler output to see where the tempory files are, and used avr-objdump in linux:

avr-objdump -S <sketch-name>.ino.elf >test.txt

And I used ‘Meld’ to show the differences.

This is the ‘volatile’:

      data[k] += x;
  e2:	f9 01       	movw	r30, r18
  e4:	e6 5d       	subi	r30, 0xD6	; 214
  e6:	fe 4f       	sbci	r31, 0xFE	; 254
  e8:	90 81       	ld	r25, Z
  ea:	98 0f       	add	r25, r24
  ec:	90 83       	st	Z, r25
      data[k] /= 2;
  ee:	90 81       	ld	r25, Z
  f0:	96 95       	lsr	r25
  f2:	90 83       	st	Z, r25
      data[k] *= 3;
  f4:	60 81       	ld	r22, Z
  f6:	96 2f       	mov	r25, r22
  f8:	99 0f       	add	r25, r25
  fa:	96 0f       	add	r25, r22
  fc:	90 83       	st	Z, r25

This is without ‘volatile’:

      data[k] += x;
  e2:	90 81       	ld	r25, Z
  e4:	98 0f       	add	r25, r24
      data[k] /= 2;
  e6:	96 95       	lsr	r25
      data[k] *= 3;
  e8:	49 2f       	mov	r20, r25
  ea:	44 0f       	add	r20, r20
  ec:	49 0f       	add	r20, r25
  ee:	41 93       	st	Z+, r20

The ‘volatile’ version stores every element after every operation, and without it, it keeps it in the register until the element is no longer used.
I understand that an array of 6 elements are not all kept in registers when a loop is used, because that would require seperate code for each element.
When I replace the array with seperate variables.

volatile byte data0;
volatile byte data1;
volatile byte data2;
volatile byte data3;

Then the compiler could make an array of those variables if it was really smart :o but it does not.
With every operation, the variable is loaded and stored, and without ‘volatile’, all 4 are kept in registers.