Use Sidecar I2C port to communicate with Arduino?

My team is working on a project that involves the digital sidecar sending information over I2C to an Arduino Uno (R2, if it matters). Is this possible, and how can it be done? I know nothing of LabView (which is what my team uses to program the robot) but I know quite a bit of arduino C.

Would this code work? All I need to do is have the Arduino receive one byte of information at a time. I want the Arduino configured as master and the sidecar configured as slave.


#include <Wire.h>//I2C library for Arduino

int d = 999;     //arbitrary int used for state change stuff

void setup() {
  Wire.begin();                  // join i2c bus (address optional for master)
  delay(1000);
}

void loop() {
  Wire.requestFrom(1, 1); //Request data from slave device (the sidecar)
    while(Wire.available()) {
      
    int dat = Wire.read();
    
    if (dat == 0) {
           if (d != 0) {
             //runs once every time state changes to 0
           d = 0;
           }
             //runs repeatedly while state is 0
     }
     
     if (dat == 1) {
           if (d != 1) {
           //runs once every time state changes to 1
           d = 1;
           }
     //runs repeatedly while state is 1
     }
     
     if (dat == 2) {
           if (d != 2) {
           //runs once every time state changes to 2
           d = 2;
           }
     //runs repeatedly while state is 2
     }
     
     if (dat == 3) {
           if (d != 3) {
           //runs once every time state changes to 2
           d = 3;
           }
     //runs repeatedly while state is 2
     }
   }
}

1 Like

evanperryg,

You can use the cRIO to communicate with the Arduino, several teams have done so, including mine. However, to the best of my knowledge, the cRIO has to be the master as an I2C slave is not currently supported. There are a couple of threads on this topic that might help if you search. I can help if you have specific questions from the Arduino side, (or cRIO from a Java perspective).

Mike

Our team has been having some trouble with this. We don’t have anybody experienced with the arduino, and we are seeing what we think to be incorrect I2C timing on the cRIO side. If you have an example for the arduino and for java, it would be appreciated.

I will try to post some sample code tonight, however if you are having what you believe are timing issues, take a look at this post.

Mike

I’ve done some research, and I have pretty much drawn a blank on how to get this to work. If someone has example code of an arduino being used as slave and recieving data, then that would be awesome.

Did some quick googling, found this that should work for i2c on the arduino side.

#include <Wire.h> 

void setup() 
{ 
  Wire.begin(2);                // join i2c bus with address #2 
  Wire.onRequest(requestEvent); // register event 
  Serial.begin(9600);           // start serial for output 
} 

void loop() 
{ 
  delay(100); 
} 

// function that executes whenever data is received from master 
// this function is registered as an event, see setup() 
void requestEvent() 
{
  static char c = '0';

  Wire.send(c++);
  if (c > 'z')
    c = '0';
}

Also, the i2c libraries for the cRIO only support master mode… With good reason. Conceptually, the cRIO is the master of all of the networks onboard the robot, because it collects data and uses it to move the robot.

We used an Arduino on our robot, this year, to give us very accurate gyro readings and to control LED’s on the robot. We program in java on the cRIO and at first had trouble. It turns out that the cRIO is not defaulted to transfer I2C data correctly because we would receive fault values followed by a correct value. To fix this issue we added the line:
private static I2C i2c = digitalSidecar.getI2C(4);
i2c.setCompatabilityMode(true);
to our startup code.
This solved our main problem and data was reliable recieved/sent after that,

Here is some sample code from http://blog.oscarliang.net/raspberry-pi-arduino-connected-i2c/ that communicates via I2C to a Raspberry Pi, although it will be pretty much the same to communicate with the cRio.

#include <Wire.h>

#define SLAVE_ADDRESS 0x04
int number = 0;
int state = 0;

void setup() {
    pinMode(13, OUTPUT);
    Serial.begin(9600);         // start serial for output
    // initialize i2c as slave
    Wire.begin(SLAVE_ADDRESS);

    // define callbacks for i2c communication
    Wire.onReceive(receiveData);
    Wire.onRequest(sendData);

    Serial.println("Ready!");
}

void loop() {
    delay(100);
}

// callback for received data
void receiveData(int byteCount){

    while(Wire.available()) {
        number = Wire.read();
        Serial.print("data received: ");
        Serial.println(number);

        if (number == 1){

            if (state == 0){
                digitalWrite(13, HIGH); // set the LED on
                state = 1;
            }
            else{
                digitalWrite(13, LOW); // set the LED off
                state = 0;
            }
         }
     }
}

// callback for sending data
void sendData(){
    Wire.write(number);
}

OK, here is some sample code that should allow you to communicate between an Arduino and the cRIO. Sorry for it being a little longer than I intended. First off, while the format is similar to what we use on the robot, I can not test this out to ensure there are no errors as I do not have the hardware available. There shouldn’t be any syntax errors, but by extracting and making a sample program, I may have introduced some other problem. Hopefully not so severe that you can’t make it work relatively easily.

This sample includes both a read and a write example with the Arduino code handling both. A couple of items to note… A write/read transaction is never performed in the same function call. This is because there is a bug in the Arduino wire library that sends a ā€œSTOPā€ after the first part of the transaction even though it is setup as a SLAVE. There is a fix for this, but I have not had the chance to test it out. This method of splitting the write and read into two parts should always work. Second, there is a bug in the JAVA version of the I2C library that could corrupt data sent to the Arduino if any of the bytes are greater than 127. This is due to how the library combines the ā€œbytesā€ into "int"s prior to sending and JAVA interprets all bytes as signed. The next version of WPILib will correct this. ā€˜C’ and Labview users shouldn’t see this problem. Finally, the Arduino code uses an I2C address of 2, while the cRIO uses an address of 4. This is intentional.

The cRIO code is not complete. It only has pieces that can be added into your own code and prints out error messages if the communication fails. The Arduino code, however, is a complete sketch and ā€œshouldā€ work out of the box. Finally, you need to have at least 3 wires between the cRIO and the Arduino for the Clock, Data, and Ground. Omitting a ground wire between the two will cause the communication to fail.

If you have (find) any errors, let me know and I will try to fix them. Hope this helps.

Mike

cRIO Part


import edu.wpi.first.wpilibj.I2C;


public static byte dataReceived] = {0, 0, 0, 0, 0, 0, 0};
private static byte dataToSend] = {0, 0, 0, 0, 0, 0, 0};
private static I2C i2c = digitalSidecar.getI2C(4);


// In the constructor, add the following line.  This puts the i2c device into 
//   CompatibilityMode, this ensures compliance with the entire i2c spec

i2c.setCompatabilityMode(true);



// This routine retrieves the incrementing counter value from the Arduino and reconstitutes
// it back into a 32 bit integer.  This is a rather convoluted way of doing it as JAVA does
// not support an unsigned single byte (bytes are signed)

public int arduino_counter()
{
  // Request Command #1 - Return Counter Value ("Register" 1)

  i2c.write(1, 0);

  // Read 5 bytes of data, with 0 bytes to send.  A false return value indicates success
    
  if (i2c.transaction(dataToSend, 0, dataReceived, 5) == false)
  {
    // If the data returned is indeed the counter, the first byte should be a 1 - identical
    // to the value we sent above

    if (dataReceived[0] != 1)
      System.out.println("Invalid data returned from Arduino - command 1");
    else
      return (((int) dataReceived[4] * 16777216) +
        (((int) dataReceived[3] & 0x000000ff) * 65536) +
        (((int) dataReceived[2] & 0x000000ff) * 256) +
        ((int) dataReceived[1] & 0x000000ff));
  }
  else
    System.out.println("Failure to read from Arduino - command 1");

  return 0;
}


// This routine sends up to 6 bytes to place in the Arduino's "read/write" array and
// then reads it back into the public byte array "dataReceived" for verification

public void arduino_write(byte newData], byte length)
{
  int i;

  // Maximum 6 bytes to send in addition to the "command" byte.  Place all the data into
  // the byte array.

  if (length > 6)
    length = 6;

  dataToSend[0] = 2;

  for (i=0; i<length; i++)
    dataToSend* = newData*;

  // Send the data to the Arduino.  Do not request any return bytes or this function
  // will fail

  if (i2c.transaction(dataToSend, length + 1, dataReceived, 0) == false)
  {
    // After successfully sending the data, perform a data read.  Since the last
    // transaction was a write with a "Command" value of 2, the Arduino will assume
    // this is the data to return.

    if (i2c.transaction(dataToSend, 0, dataReceived, 7) == false)
    {
      if (dataReceived[0] != 2)
        System.out.println("Invalid data returned from Arduino - command 2");
    }
    else
      System.out.println("Failure to read from Arduino - command 2");
  }
  else
    System.out.println("Failure to send data to Arduino");
}

Arduino Sketch


#include <Wire.h>

byte dataToSend = 0;
unsigned long counter = 0;
byte data2[6] = {11, 12, 13, 14, 15, 16};

void setup()
{
  Wire.begin(2);                                            // Start I2C interface (address 2)
  Wire.onReceive(i2cReceive);                               // Receive ISR routine
  Wire.onRequest(i2cRequest);                               // Request (send) ISR routine
}

void loop()
{
  delay(10);                                                // 10 milliseond delay
  
  // Increment a counter every loop.  Since the ISR may interrupt this code at any time,
  // we need to disable interrupts during the increment operation so the ISR can't a
  // partially updated value.
  
  noInterrupts();
  counter++;
  interrupts();
}

// This routine is called by the ISR once a complete transmission is received from the master

void i2cReceive(int count)
{
  byte i2cCount;                                            // Loop/Array counter
  byte tmp;                                                 // temporary byte holder
  byte i2cDataR[7];                                         // 7 Bytes in length as that is the
                                                            // maximum that can be sent by the
                                                            // cRIO

  for (i2cCount = 0; i2cCount < count; i2cCount++)          // Read data into local buffer
  {                                                         // looping through the entire message
    tmp = Wire.read();

    if (i2cCount < 7)                                       // If more than 7 bytes in length,
      i2cDataR[i2cCount] = tmp;                             // discard remaining data as somthing
  }                                                         // is wrong.

  // the first byte read is typically what is called the register to be read/written.  This
  // example utilizes it as a "command" either to be executed or the type of data to be returned
  // when a "request" is received.

  switch (i2cDataR[0])                                      // Perform action based on command
  {
    // Command 1: Return the counter value when the "request" occurs
    case 1:
      dataToSend = 1;                                       // Set a "global" variable for use by
      break;                                                // the i2cRequest routine

    // Command 2: Update data 2 array with what ever data is sent (if any) and allow this data
    //            to be returned to the master if it is requested
    case 2:
      dataToSend = 2;                                       // Communication for i2cRequest

      if (i2cCount > 1)                                     // More than just the command byte
        memcpy(&data2[0], &i2cDataR[1], i2cCount < 7 ?      // Limit the data to copy to 6 max
          i2cCount - 1 : 6);                                // so as to not overflow the array
  }
}

// This routine is called when the master requests the slave send data.  A full transaction
// typically includes a Receive envent (register to send) followed by a Request event where the
// slave data is actually transmitted to the master.

void i2cRequest()
{ 
  byte i2cDataW[7];
  byte length;
  
  // I use the first byte of the data array to send a comfirmation of the information that follows.
  // This allows the cRIO a way to check what "message" is being sent from the Arduino and interpret
  // it correctly.  While this does limit the actual data part of the message to 6 bytes, the extra
  // verification, I feel, is worth it.
  
  switch (dataToSend)                                        // variable set in the i2cReceive event
  {    
    // Return the value of the counter
    
    case 1:
      i2cDataW[0] = 1;
      memcpy(&i2cDataW[1], &counter, 4);
      length = 5;
      break;
    
    // Return the contents of the cRIO read/write array
    
    case 2:
      i2cDataW[0] = 2;
      memcpy(&i2cDataW[1], &data2[0], 6);
      length = 7;
      break;
    
    // Unknown or invalid command.  Send an error byte back
    
    default:
      i2cDataW[0] = 0xFF;
      length = 1;
  }

  Wire.write((uint8_t *) &i2cDataW[0], length);
}


**

Thanks guys! I will post my changed code here as soon as I can. Just in case any of you are curious, the objective of this was to add more lights to my team’s robot before IRI. (and if any of you have seen our robot, you probably know we like lights. a lot.)

Thanks to you all, I got the code fixed up. If you guys see any issues in the code, please tell me so I can get them patched up as soon as possible.


/*REALLY COOL LIGHTS PROGRAM I2C rev 1.3
------------------------
Evan Grove, Team 2338
released 6/13/2013
rev 0.0: 6/5/2013
This code is used for visual effects on the curved part of the shooter.
During autonomous, LEDs light up in a random pattern. During Teleop, the
lights do a chaser pattern when discs are being fired.

DATA OUTPUTS FROM SIDECAR:
Byte(Bin)       Byte(Dec)        Definition
00000000        0                Autonomous, not shooting
00000001        1                Autonomous, shooting
00000010        2                Teleop, not shooting
00000011        3                Teleop, shooting

ARDUINO UNO I2C CONNECTIONS:
Analog 4 on the arduino is SDA
Analog 5 on the arduino is SCL

DIGITAL SIDECAR I2C CONNECTIONS:
Google it.
Connect analog 0 to a long piece of bare wire. This helps make
the random sequences more random.
*/
#include <Wire.h>//I2C library for Arduino

const int leds] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; //arrays make everything better
int i = 0;       //arbitrary integer used in the pattern generation. Probably shouldn't change this.
int d = 999;     //arbitrary integer used in debug sequence.
int w = 0;       //arbitrary integer used in debug sequence.
#define ini 30   //speed of the initialization pattern in ms. Default=30
#define ave 90   //speed of autonomous visual pattern in ms. Default=90
#define aws 125  //length of time visual effect for autonomous shooting is displayed in ms. Default=125
#define spd 15   //speed of teleop light effect in ms. Default=15
#define bfr 12   //length of teleop shooter effect buffer. Default=12
#define scm 9600 //rate of serial communication, leave this alone

void setup() {
  Serial.begin(scm);
  Serial.println("Serial: initialized");
  Serial.println("Serial: checking");
  if (Serial.available() > 0) {
    Serial.println("Serial: successfully initialized");
  }
  for (i=0;i<13;i++) {
    pinMode(leds*, OUTPUT);     //declare pins
    }
  Serial.println("Digital: outputs set");
  randomSeed(analogRead(0));     //make random() randomer using elecrical noise from analog 0.
  Serial.println("Analog: inputs set");
  Wire.begin(2);                  // join i2c bus
  Serial.println("I2C: initialized");
  delay(1000);
  Wire.onReceive(incoming);
  Serial.println("I2C: ready");
  for (i=0;i<13;i++) {            //these 2 for loops are used as a debug tool
    digitalWrite(leds*, HIGH);  //to make sure all the LEDs work and are
    delay(ini);                   //conected in the proper order.
    } 
  i = 0;
  delay(100);
  for (i=0;i<13;i++) {
    digitalWrite(leds*, LOW);
    delay(ini);
    }
  Serial.println("Test pattern completed");
  Serial.println("Ready");
}

void loop() {
  delay(100);
}

void incoming(int bct) {
    while(Wire.available()) {
    /* AUTONOMOUS */
//Autonomous while not shooting
    if (dat == 0) {
           if (d != 0) {
           Serial.println("Auto: not shooting");
           d = 0;
           }
           int ran = random(0,13);
           int ranb = random(0,40);
           int ran2 = map(ranb,0,40,0,13);
           digitalWrite(leds[ran], HIGH);
           digitalWrite(leds[ran2], HIGH);
           delay(ave);                             //ooooh pretty light patterns
           digitalWrite(leds[ran], LOW);
           digitalWrite(leds[ran2], LOW);
     }
   
//Autonomous while shooting
     if (dat == 1) {
           if (d != 1) {
           Serial.println("Auto: shooting");
           d = 1;
           }
        for (i=0;i<13;i++) {
          digitalWrite(leds*, HIGH);
        }
        delay(aws);
        for (i=0;i<13;i++) {
          digitalWrite(leds*, LOW);
        }
     }
  
    /* TELEOP */
//Teleop while not shooting
    if (dat == 2) {
           if (d != 2) {
           Serial.println("Tele: not shooting");
           d = 2;
           }
          for (i=0;i<13;i++) {
            digitalWrite(leds*, LOW);          //set all lights off if we are in teleop and trigger is not pressed
          }
        }
  
//Teleop while shooting
    if (dat == 3) {
           if (d != 3) {
           Serial.println("Tele: shooting");
           d = 3;
            for (i=0;i<13;i++) {
              digitalWrite(leds*, HIGH);         //simple for() loop that makes a 'chaser' effect
              delay(spd);                          //speed of the pattern is set by integer spd
              digitalWrite(leds*, LOW);
              delay(spd);
            }
           }
      }
   } 
}