Johnny-five: How to use I2C?

Created on 24 Jul 2016  Â·  25Comments  Â·  Source: rwaldron/johnny-five

I've been trying to figure out how to do basic i2cWrite, but somehow I get really weird results, non logical ones.

So, I have two boards, board1 with StandardFirmata, board2 with custom i2c sketch:

#include <Wire.h>

void setup() {
  Wire.begin(8);                // join i2c bus with address #8
  Wire.onReceive(receiveEvent); // 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 receiveEvent(int howMany) {
  while(Wire.available() > 0) {
  char c = Wire.read();  
  Serial.print(c);   
  }
}

as you can see, this is slight modification of standard example "slave_receiver" sketch.
Now, on firmata side, I'm trying do this:

var five = require('johnny-five');
var board = new five.Board({
    port: 'COM6',
    repl: false
});

board.on('ready', function() {
    console.log('sending i2c...');

    this.io.i2cConfig();
    this.io.i2cWrite(8, 'hello world'.split('').map(function (c) { return c.charCodeAt(0); }));

    console.log('done.');
});

But, nothing is received at all. Any thoughts?
I'm using Arduino Mega for j5 + StandardFirmata and Arduino Uno for i2c slave device.

EDIT: also have to note, that master_writer sketch (instead of j5/firmata) works as it should.

EDIT: I also did more testing, seems it needs to wait for a while after calling this.io.i2cConfig(); (approx. 2 seconds). While this.io.isReady is still true, I'm unable to locate the problem.

Here's the test I used:

var five = require('johnny-five');
var board = new five.Board({
    port: 'COM6',
    repl: false
});

board.on('ready', function () {
    var self = this;
    var i = 0;

    this.io.i2cConfig();
    setInterval(function() {
        self.io.i2cWrite(8, 13, ('try #' + (++i).toString() + '\r\n').split('').map(function (c) { return c.charCodeAt(0); }));
    }, 1000);
});

and output:

try #2
try #3
try #4
...

but after calling this.io.i2cConfig() and wait for two seconds in debugger, it works fine. What's the problem? Why it needs to wait? Can someone explain?

EDIT: seems it totally ignores first bytes sent, now I'm doing like this, and I get the result, what the heck?!

this.io.i2cConfig();
this.io.i2cWrite(8); // without this, transmission below is just ignored.
this.io.i2cWrite(8, 'hello world\r\n'.split('').map(function (v) { return v.charCodeAt(0); }));

Most helpful comment

There is no need to wait any amount of time after i2cConfig is called before i2cWrite. In the receiveEvent, the program should do as little as possible. "Get the bytes and get out" is the mantra ;) Also, a 100ms delay in the loop will cause process blocking on every tick.

Upload this to the arduino:

#include <Wire.h>

const int maxlength = 64;

char buffer[maxlength];
char printable[maxlength];

int received = 0;

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

void loop() {
  if (received > 0) {
    memcpy(printable, buffer, maxlength);

    for (int i = 0; i < received; i++) {
      Serial.print(printable[i]);
    }
    Serial.println(""); 
    received = 0;
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  received = howMany;
  memset(buffer, 0, maxlength);

  for (int i = 0; i < howMany; i++) {
    buffer[i] = Wire.read();
  }
}

And then run this program:

var five = require("johnny-five");
var board = new five.Board({
  port: "COM6",
});

board.on("ready", function () {
  var write = (message) => {
    this.i2cWrite(0x08, Array.from(message, c => c.charCodeAt(0)));
  };
  this.i2cConfig();
  this.repl.inject({ write });

  write("Hello World");
});

Here's a video: https://youtu.be/5PQplMqyHBI

All 25 comments

There is no need to wait any amount of time after i2cConfig is called before i2cWrite. In the receiveEvent, the program should do as little as possible. "Get the bytes and get out" is the mantra ;) Also, a 100ms delay in the loop will cause process blocking on every tick.

Upload this to the arduino:

#include <Wire.h>

const int maxlength = 64;

char buffer[maxlength];
char printable[maxlength];

int received = 0;

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

void loop() {
  if (received > 0) {
    memcpy(printable, buffer, maxlength);

    for (int i = 0; i < received; i++) {
      Serial.print(printable[i]);
    }
    Serial.println(""); 
    received = 0;
  }
}

// function that executes whenever data is received from master
// this function is registered as an event, see setup()
void receiveEvent(int howMany) {
  received = howMany;
  memset(buffer, 0, maxlength);

  for (int i = 0; i < howMany; i++) {
    buffer[i] = Wire.read();
  }
}

And then run this program:

var five = require("johnny-five");
var board = new five.Board({
  port: "COM6",
});

board.on("ready", function () {
  var write = (message) => {
    this.i2cWrite(0x08, Array.from(message, c => c.charCodeAt(0)));
  };
  this.i2cConfig();
  this.repl.inject({ write });

  write("Hello World");
});

Here's a video: https://youtu.be/5PQplMqyHBI

You're right, thanks for the tips!

It's still weird. Without dummy write call (this.io.i2cWrite(8);) it does not receive anything. Tried on the same code, I suppose it's my hardware problem then.

All I had was an Uno connected to an Uno, and both connected to USB on my laptop ¯_(ツ)_/¯

Can you take a picture of your hardware and post it here?

Sadly no, cause I switched the hardware. Now it's Due -> Mega and works fine. All I had was just simple SDA/SCL -> SDA/SCL.

Everything works? Can you close this?

Well yes. In case people can't get to know what's wrong with Mega/Uno (or my hardware might be damaged, dunno) you can try to send dummy message (0 bytes) and then actual data and after that it works fine.

That's still very strange :|

I'm away from home this week, but when I get back I'd like to experiment with Mega + Uno and see what's going on

Would be happy to know the reason. Also, fact is that, it sometimes worked fine, but REALLY rarely. I was simply getting non-logical results and I was not able to diagnose what to do when. If you find anything weird, will be glad to know, else I think it's my hardware problem (don't have extra yet to test :disappointed: )

Will definitely post back any findings here

I just reproduced your issue perfectly, but nothing further to report. I'll post back when I know more

I just confirmed with a logic analyzer that the first message isn't being sent by the mega

Seems weird, if you can analyse why it happens, could you please provide explanation? thanks :)

As soon as I learn more, I will report back

Hi.

I have connected via I2C a YUN and ONE arduinos and the example showed above works fine, but when I try to send messages longer than 30 bytes they aren't showed in the monitor serie of the ONE.

write("Hello World") --> Works fine.
write("Esta es una frase mucho mas larga y no es transmitida via I2C"); --> Doesn't work.

Is the number of bytes in the parameter 'inBytes' limited to 30 in the function i2cWrite(address, inBytes)?

Thanks

What version of StandardFirmata is running on the Yun? It might have a 32 byte limit—which can/should be changed to 64

I have installed the StandardFirmataYun.

I have been looking at the code in StandardFirmataYun.ino and in the line 86 the code is:

byte i2cRxData[32];

Is it the code that have to be changed?
Can I change it for byte i2cRxData[64]; or byte i2cRxData[128]; ?
What are the implications?

Thanks

We can just open PR for that :)

Hi

I have seen that you have modified i2cRxData[32] to i2cRxData[64] in the StandardFirmataYun.ino. I have uploaded it to my YUN and I get the same result. I can't send messages longer than 30 bytes.

Is there something more that I have to change or I am missing something?

Thanks

It may be because it takes two bytes to send a char with Firmata so 64 bytes yields 32 chars, but it's actually a few less because of START_SYXEX, I2C_REQUEST, 2 address bytes and END_SYSEX, so that's only 32 - 5 = 27 chars max.

Actually max chars = (64 - 5) / 2.

We probably need a way to frame an I2C_REQUEST message so you can send it in chunks. It's something I'll keep in mind for a future update to the Firmata I2C implementation.

Ok thanks.

I'll try to check messages greater than 27 bytes and divide then into chunks of 27 before sending them.

Hi

In a configuration of Master Reader / Slave Writer, with StandardFirmataYun.ino in the Master and a sketch in the Slave, changing the values bellow we can request 128 bytes via I2C

IN THE Arduino IDE:
Wire.h --> #define BUFFER_LENGTH 128
Twi.h --> #define TWI_BUFFER_LENGTH 128
StandardFirmataYun.ino --> byte i2cRxData[128];

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Zhairgling picture Zhairgling  Â·  3Comments

danielhep picture danielhep  Â·  4Comments

beriberikix picture beriberikix  Â·  11Comments

yuanhaoliang picture yuanhaoliang  Â·  7Comments

teuteuguy picture teuteuguy  Â·  4Comments