user3186918
user3186918

Reputation: 21

How to send 4 Pot values via i2c from Arduino to Arduino? How to differentiate these values while receiving them?

I have one Arduino with 4 Pots. The other Arduino receives these 4 values via i2c and prints them on a Display. The problem is that I don't know how to send these 4 values that the Slave is able to know which value belongs to which Pot.

Slave Code:

#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

void setup()
{
  Wire.begin(5);
  Wire.onReceive(receiveEvent);
  Serial.begin(9600);
  lcd.begin(16,2);

}

void loop()
{
}

   
void receiveEvent(int)
{

 while(Wire.available())
  {
           //How to create this part? How does the Slave know which value belongs to which pot?
  }

}

Master Code:

#include <Wire.h>
    
void setup()

{
  Serial.begin(9600);
  Wire.begin();
  delay(2000);
}

void loop()
{
  int sensor1 = analogRead(A1);
  Wire.beginTransmission(5);
  Wire.write(sensor1);
  Serial.print(sensor1);
  Wire.endTransmission();
  delay(100);
  
  int sensor2 = analogRead(A2);
  Wire.beginTransmission(5);
  Wire.write(sensor2);
  Serial.print(sensor2);
  Wire.endTransmission();
  
  delay(500);
  
}

Upvotes: 2

Views: 17602

Answers (2)

Mr. Girgitt
Mr. Girgitt

Reputation: 2903

Ahh what we have here is a basic question on how to design I2C communication. Unfortunately Examples for I2C master and slave included in Arduino IDE are IMO too limited to provide clear guidance on this matter.

First of all in your examples the master and slaves roles are exchanged and should be switched. Slave should read values from analog inputs and master should request them. Why? Because it's master which should decide when to request values and properly decode the request. Slave should provide proper answer to a given request eliminating the problem of data interpretation.

I2C communication is based on requestFunction-(wait)-requestResponse sequence controlled by the master. Plese refer to the range finder example on arduino page. In a nutshell:

First: master requests a function to measure distance:

  // step 3: instruct sensor to return a particular echo reading 
  Wire.beginTransmission(112); // transmit to device #112
  Wire.write(byte(0x02));      // sets register pointer to echo #1 register (0x02)
  Wire.endTransmission();      // stop transmitting

(sometimes slaves need some time e.g. 10 - 50 ms to process requests but in the example I'm refering to master doesn't delay read)

Second: master requests response:

  // step 4: request reading from sensor
  Wire.requestFrom(112, 2);    // request 2 bytes from slave device #112

Third: master tries to read and analyze response.

You should design reliable I2C communication in a similar way.

Here is how I do it; you can follow my pattern and get extensible slave implementation which will support one function: read analog inputs but can be easily extended by adding additional function codes and required processing implementation to the slave main loop

Initial remarks

  1. some kind of a simple protocol is needed to control slave - e.g. it should support requesting functions. Supporting functions requests is not absolutely needed in such simmple scenario as reading four analog inputs but what I'm describing is a more general pattern you may use in other projects.

  2. Slave should not perform any additional actions (like reading inputs) on request response as I2C communication may break (due to delays) and you will get partial responses etc. This is very important requirement which affect the slave design.

  3. response (and also request if needed) can contain CRC as if master waits not long enough it may get empty response. If nobody else is going to use your code such countermeasures are not needed and will not be described here. Other important thing is Wire library buffer limitation which is 32 bytes and implementing CRC checksum without modifying the buffer length limits the available data length by two bytes (if crc16 is used).

slave:

#include <WSWire.h>  // look on the web for an improved wire library which improves reliability by performing re-init on lockups 


// >> put this into a header file you include at the beginning for better clarity
enum { 
  I2C_CMD_GET_ANALOGS = 1
};

enum { 
  I2C_MSG_ARGS_MAX = 32,
  I2C_RESP_LEN_MAX = 32
};

#define I2C_ADDR                 0             
#define TWI_FREQ_SETTING         400000L       // 400KHz for I2C
#define CPU_FREQ                 16000000L     // 16MHz

extern const byte supportedI2Ccmd[] = { 
  1
};
// << put this into a header file you include at the beginning for better clarity



int argsCnt = 0;                        // how many arguments were passed with given command
int requestedCmd = 0;                   // which command was requested (if any)

byte i2cArgs[I2C_MSG_ARGS_MAX];         // array to store args received from master
int i2cArgsLen = 0;                     // how many args passed by master to given command

uint8_t i2cResponse[I2C_RESP_LEN_MAX];  // array to store response
int i2cResponseLen = 0;                 // response length

void setup()
{
  // >> starting i2c
  TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;

  Wire.begin(I2C_ADDR);                        // join i2c bus 
  Wire.onRequest(requestEvent);                // register event
  Wire.onReceive(receiveEvent);    
  // << starting i2c
}

void loop()
{

 if(requestedCmd == I2C_CMD_GET_ANALOGS){
    // read inputs and save to response array; example (not tested) below
    i2cResponseLen = 0;
    // analog readings should be averaged and not read one-by-one to reduce noise which is not done in this example
    i2cResponseLen++;
    i2cResponse[i2cResponseLen -1] = analogRead(A0);
    i2cResponseLen++;
    i2cResponse[i2cResponseLen -1] = analogRead(A1);
    i2cResponseLen++;
    i2cResponse[i2cResponseLen -1] = analogRead(A2);
    i2cResponseLen++;
    i2cResponse[i2cResponseLen -1] = analogRead(A3);
    // now slave is ready to send back four bytes each holding analog reading from a specific analog input; you can improve robustness of the protocol by including e.g. crc16 at the end or instead of returning just 4 bytes return 8 where odd bytes indicate analog input indexes and even bytes their values; change master implementation accordingly
    requestedCmd = 0;   // set requestd cmd to 0 disabling processing in next loop

  }
  else if (requestedCmd != 0){
    // log the requested function is unsupported (e.g. by writing to serial port or soft serial

    requestedCmd = 0;   // set requestd cmd to 0 disabling processing in next loop
  }

} 


// function that executes whenever data is requested by master
// this function is registered as an event, see setup()
void requestEvent(){

  Wire.write(i2cResponse, i2cResponseLen);

}


// function that executes when master sends data (begin-end transmission)
// this function is registered as an event, see setup()
void receiveEvent(int howMany)
{
  //digitalWrite(13,HIGH);
  int cmdRcvd = -1;
  int argIndex = -1; 
  argsCnt = 0;

  if (Wire.available()){
    cmdRcvd = Wire.read();                 // receive first byte - command assumed
    while(Wire.available()){               // receive rest of tramsmission from master assuming arguments to the command
      if (argIndex < I2C_MSG_ARGS_MAX){
        argIndex++;
        i2cArgs[argIndex] = Wire.read();
      }
      else{
        ; // implement logging error: "too many arguments"
      }
      argsCnt = argIndex+1;  
    }
  }
  else{
    // implement logging error: "empty request"
    return;
  }
  // validating command is supported by slave
  int fcnt = -1;
  for (int i = 0; i < sizeof(supportedI2Ccmd); i++) {
    if (supportedI2Ccmd[i] == cmdRcvd) {
      fcnt = i;
    }
  }

  if (fcnt<0){
    // implement logging error: "command not supported"
    return;
  }
  requestedCmd = cmdRcvd;
  // now main loop code should pick up a command to execute and prepare required response when master waits before requesting response
}

master:

#include <WSWire.h>

#define I2C_REQ_DELAY_MS         2  // used for IO reads - from node's memory (fast)
#define I2C_REQ_LONG_DELAY_MS    5  //used for configuration etc.

#define TWI_FREQ_SETTING         400000L
#define CPU_FREQ                 16000000L

enum { 
  I2C_CMD_GET_ANALOGS = 1
};

int i2cSlaveAddr = 0;


void setup(){
  // joining i2c as a master
  TWBR = ((CPU_FREQ / TWI_FREQ_SETTING) - 16) / 2;
  Wire.begin(); 
}

void loop(){
  //requesting analogs read: 
  Wire.beginTransmission(i2cSlaveAddr); 
  Wire.write((uint8_t)I2C_CMD_GET_ANALOGS);  
  Wire.endTransmission();  

  delay(I2C_REQ_DELAY_MS);

  // master knows slave should return 4 bytes to the I2C_CMD_GET_ANALOGS command
  int respVals[4];

  Wire.requestFrom(i2cSlaveAddr, 4);

  uint8_t respIoIndex = 0;

  if(Wire.available())
    for (byte r = 0; r < 4; r++)
      if(Wire.available()){ 
        respVals[respIoIndex] = (uint8_t)Wire.read();
        respIoIndex++;      
      }
      else{
        // log or handle error: "missing read"; if you are not going to do so use r index instead of respIoIndex and delete respoIoIndex from this for loop
        break;
      }
  // now the respVals array should contain analog values for each analog input in the same order as defined in slave (respVals[0] - A0, respVals[1] - A1 ...)
}

I hope my example will help. It's based on code working for weeks making 40 reads a second from multiple slaves however I have not compiled it to test the function you require. Please use WSWire library as the Wire (at least as for Arduino 1.0.3) may occasionally freeze your master if for some reason slave will not respond to request.

EDIT: The WSWire lib requires external pull-up resistors for I2C unless you modify the source and enable internal pull-ups like Wire does.

EDIT: instead of creating i2c slave implementation you may try the EasyTransfer library. I haven't tried it but it may be easier to use it if sending four bytes is everything you need.

EDIT[12.2017]: There is a new player on the block - PJON - a library suited for easy multi-master communication ideal to exchange pot values (and much more). It's been around for some time but gained a substantial development speed in recent months. I'm partially involved in its development and switched all field-level and local buses I've used so far (I2C, MODBUS RTU) to PJON over single wire, hardware serial or RF.

Upvotes: 13

Amir
Amir

Reputation: 101

Check out GitHub-I2CBus, I've done the exact same thing. Hope it can help

Upvotes: 0

Related Questions