Transmitting an integer over I2C

I’d like to transmit integers via I2C, and have found that other people have been interested in doing so. Using the solution below, I have run into a problem. The loop I run will transmit the integer I am looking for 4 times and then transmit gobbledegook 12 times. While I recognize that it may be because I have two for loops running 4 times each, manipulating these loops to run different times no longer transmits the desired numbers.

To clarify their operation, the sender breaks the number into one bit characters, and the receiver reads individual chars and compiles them into one integer again.

I’ll include the code below, and post the results of my test runs tomorrow when I have access to the arduino boards. Thank you for your help.

This is the Master controller code

#include <Wire.h>
// MAX INT -16382 to 16382
int output = 0;

void setup()
{
  Wire.begin();        // join i2c bus (address optional for master)
  Serial.begin(115200);  // start serial for output
}

void loop()
{
  Wire.requestFrom(1, 1);   // the first byte
  while(Wire.available())
  { 
    char received = Wire.read();
    output = received;
  }
  
  for (int i = 0 ; i < 3 ; i++) // next 3 bytes
  {
     Wire.requestFrom(1, 1);   
     while(Wire.available())
     { 
        char received = Wire.read();
        output |= (received << 8);
     }
  }
  Serial.println(output);
  //Serial.println("loop");
  //Serial.println(".");
  delay(500);
}

This is the slave devices code (a temperature sensor that uses a wheatstone bridge to measure resistance over a temperature resistive element):

#include <Wire.h>
int analogPin1 = 1; // potentiometer wiper (middle terminal) connected to analog pin 3
// outside leads to ground and +5V
int analogPin0 = 0; // (ANALOG)
int inputPin = 2; // 5 volt source (DIGITAL)
int Vout;        // variable to store the output voltage
int deviceID = 1; // I2C address
int byteSending = 1;
int toTransfer;
int Shift = toTransfer;
int mask = 0xFF;
char toSend = 0;

void setup()
{
  Wire.begin(deviceID);
  Wire.onRequest(requestEvent);
  //Serial.begin(115200);             // Setup serial
  pinMode (inputPin,OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  requestEvent();
  //  Serial.println(volta);
  delay(2000);
}

void requestEvent()
{
  int toTransfer = takeVoltage();
  if (byteSending == 1) //send packet 1
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 2;
  }
  else if (byteSending == 2) //send packet 2
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 3;
  }
  else if (byteSending == 3) //send packet 3
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 4;
  }
  else if (byteSending == 4) //send packet 4
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 1;
    //initialization for next turn
    Shift = toTransfer;
    mask = 0xFF;
    toSend = 0;
  }
}
int takeVoltage()
{
  digitalWrite(inputPin,HIGH); // supply voltage to the voltage divider
  int voltageRead1 = analogRead(analogPin1);    // Reads the voltage from Input PIN
  int voltageRead2 = analogRead(analogPin0);     // Reads the voltage from Input PIN
  //delay(1000);
  digitalWrite(inputPin,LOW);
  int rawVoltage = voltageRead1-voltageRead2;
  //Serial.println(rawVoltage);
  //int Voltage = (5.0 / 1023.0) * rawVoltage;
  return rawVoltage;
}

You made several errors in your sketch. The first is that a variable of type “int” is 15 bits in size plus one sign bit. I recommend that you use the explicit types (int16_t, uint32_t, etc.) so you always get the correct size.

In your case you don’t need 32 bit because the result of an analogRead() call is a 10 bit integer.

The next error is in your main loop():

  Wire.requestFrom(1, 1);   // the first byte
  while(Wire.available())
  { 
    char received = Wire.read();
    output = received;
  }
  
  for (int i = 0 ; i < 3 ; i++) // next 3 bytes
  {
     Wire.requestFrom(1, 1);   
     while(Wire.available())
     { 
        char received = Wire.read();
        output |= (received << 8);
     }
  }

The code “received << 8” always produces a result of zero because received is of type char and a char has only 8 bits. You move all 8 bits out of the variable so only 0 is left. If you’re very lucky, the compiler noticed your mistake and switched to a 16 bit value for the result but even then you’re or’ing 3 bytes to the same result byte probably producing very unexpected outputs.

So switch to unsigned 16 bit integers for your sketch and use unions like this:

union {
  uint16_t    intval;
  uint8_t     bytes[2];
};

This way you can fill in the bytes and read out the unsigned integer value.

Thank you for your help so far! I have changed all of the relevant integers in the slave to be int16_t as suggested. The code is shown below:

#include <Wire.h>
int analogPin1 = 1; // potentiometer wiper (middle terminal) connected to analog pin 3
// outside leads to ground and +5V
int analogPin0 = 0; // (ANALOG)
int inputPin = 2; // 5 volt source (DIGITAL)
int Vout;        // variable to store the output voltage
int deviceID = 1; // I2C address
int byteSending = 1;
int16_t toTransfer;
int16_t Shift = toTransfer;
int16_t mask = 0xFF;
char toSend = 0;

void setup()
{
  Wire.begin(deviceID);
  Wire.onRequest(requestEvent);
  //Serial.begin(115200);   
  pinMode (inputPin,OUTPUT);
  Serial.begin(115200);
}

void loop()
{
  requestEvent();
  //  Serial.println(volta);
  delay(2000);
}

void requestEvent()
{
  toTransfer = takeVoltage();
  if (byteSending == 1) //send packet 1
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 2;
  }
  else if (byteSending == 2) //send packet 2
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 3;
  }
  else if (byteSending == 3) //send packet 3
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 4;
  }
  else if (byteSending == 4) //send packet 4
  {
    toSend = Shift & mask;
    Shift = Shift >> 8;
    Wire.write(toSend);
    byteSending = 1;
    //initialization for next turn
    Shift = toTransfer;
    mask = 0xFF;
    toSend = 0;
  }
}
int16_t takeVoltage()
{
  digitalWrite(inputPin,HIGH); // supply voltage to the voltage divider
  int16_t voltageRead1 = analogRead(analogPin1);    
  int16_t voltageRead2 = analogRead(analogPin0);    
  //delay(1000);
  digitalWrite(inputPin,LOW);
  int16_t rawVoltage = voltageRead1-voltageRead2;
  //Serial.println(rawVoltage);
  //int Voltage = (5.0 / 1023.0) * rawVoltage;
  return rawVoltage;
}

When looking at union declaration though, I am a little confused, in c++ the syntax is as such is it not?:

union {
  uint16_t placeholder_name;
  uint8_t   placeholder_name2;
}variable_name;

Unfortunately when I try to initialize such a union I get this error:
no match for ‘operator =’ in ‘output = received’

#include <Wire.h>
// MAX INT -16382 to 16382
//int output = 0;

union {
  uint16_t intval;
  uint8_t bytes[2];
  //char f;
} output;

void setup()
{
  Wire.begin();        
  Serial.begin(115200);  
}

void loop()
{
  Wire.requestFrom(1, 1);   
  while(Wire.available())
  { 
    char received = Wire.read();
    output = received; // *error highlights this line*
  }
  
  for (int i = 0 ; i < 3 ; i++) 
  {
     Wire.requestFrom(1, 1);   
     while(Wire.available())
     { 
        char received = Wire.read();
        output |= (received << 8);
     }
  }
  Serial.println(output);
  //Serial.println("loop");
  //Serial.println(".");
  delay(500);
}

Perhaps I misunderstand how to declare a union, I appreciate any help you can give, thank you!

Perhaps I misunderstand how to declare a union, I appreciate any help you can give, thank you!

My example was an anonymous union. If you use your declaration you have to use output.intval or output.bytes[0] to access the union representations.

The trick of a union (Union type - Wikipedia) is to have multiple ways to access the same memory area. This way you can set the output by bytes and use the integer interpretation afterwards:

union {
  uint16_t intval;
  byte b[2];
}

b[0] = firstbyte;
b[1] = secondbyte;

uint16_t result = intval;

I have changed all of the relevant integers in the slave to be int16_t as suggested.

I did not only suggest to change the types but also to only submit the necessary two bytes. Your still sending 4 bytes, don't you?

Why are you calling requestEvent in loop()? You already declared it as the interrupt handler if an I2C request is received from the master.