Last year I wrote about controlling an Arduino using a Raspberry Pi and I2C. I2C being, of course, a 30+ year old protocol that everybody and their brother still supports. I tried this again, for reasons I’ll state in the very last sentence of this blog, and it failed. Significantly.
Why? Well, turns out I had forgotten the cardinal rule of I2C.
Link your devices, master and slave, sender and receiver, using a shared ground wire. Otherwise, you’re going to have a bad time. Really.
Mmm’k, with that out of the way:
1) The Adafruit tutorial is still the best one I’ve found for enabling I2C on your Raspberry Pi. However, raspi-config now helps you do this under advanced options. I used the advanced options and then doubled checked everything Adafruit told me to do. (Rule #1: do what Adafruit says.)
2) Add your user to the i2c group. The command is “sudo adduser pi i2c“. This will make your life easier. Thank you ABC Electronics.
3) Configure your Arduinos as slave senders. The example code that comes with the Wire library in the Arduino IDE is perfect for this. Wire ground to ground, SDA to SDA and SCL to SCL. Once you add power to the Arduino, install the slave sender example code and boot your pi, you should be able to see your I2C device using the i2cdetect -y 1 command. Mine with two Arduinos looks like this:
My code looks a little different than the slave sender sample, since I have a photocell and a temperature probe on each one. There’s a fair amount of boiler plate code to read those values, but the fundamental structure is the same as the sample code: configure an I2C address, wait for a request, send a fixed length string in response.
#include <Wire.h> #include <OneWire> // Define analog pin int sensorPin = 0; int lightLevel = 0; int DS18S20_Pin = 2; OneWire ds(DS18S20_Pin); // on digital pin 2 String deviceId = "D001"; int wireId = 4; String lightLevelString = ""; String output; int m; char c[5]; char d[10]; // Setup void setup() { Wire.begin(wireId); Wire.onRequest(requestEvent); // Init serial Serial.begin(9600); } // Main loop void loop() { // Get temperature int sensorValue = analogRead(sensorPin); lightLevel = map(sensorValue, 10, 1000, 1, 100); m = sprintf(c, "%03d", lightLevel); float temperature = getTemp(); dtostrf(temperature, 6, 2, d); output = deviceId; output.concat("|"); output.concat(String(c)); output.concat("|"); output.concat(String(d)); Serial.println(output); delay(1000); } void requestEvent() { char newOut[16]; output.toCharArray(newOut, 16); Wire.write(newOut); } float getTemp(){ //returns the temperature from one DS18S20 in DEG Celsius byte data[12]; byte addr[8]; if ( !ds.search(addr)) { //no more sensors on chain, reset search ds.reset_search(); return -1000; } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.println("CRC is not valid!"); return -1000; } if ( addr[0] != 0x10 && addr[0] != 0x28) { Serial.print("Device is not recognized"); return -1000; } ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion, with parasite power on at the end byte present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad for (int i = 0; i < 9; i++) { // we need 9 bytes data[i] = ds.read(); } ds.reset_search(); byte MSB = data[1]; byte LSB = data[0]; float tempRead = ((MSB << 8) | LSB); //using two's compliment float TemperatureSum = tempRead / 16; return TemperatureSum; }
4) Install node.js on your rpi, and then use npm to install the i2c library. You should now be able to read your Arduino via Node.js. My code declares two devices, both with addresses matching the settings in my Arduino code, and it looks like this:
var i2c = require('i2c'); var address = 0x18; var device1 = new i2c(address, {device: '/dev/i2c-1'}); device1.setAddress(0x4); var device2 = new i2c(address, {device: '/dev/i2c-1'}); device2.setAddress(0x6); var devices = [ device1, device2 ]; function handleTimeout() { setTimeout(function() { handleRead(); }, 1000 ); } function handleRead() { for (i = 0; i < devices.length; i++) { devices[i].readBytes(null,15, function(err,res) { console.log(res.toString('ascii')); }); } handleTimeout(); } handleTimeout();
The output is pretty simple — it just prints the data it receives, like so:
My final setup consists of two Arduinos (D001 and D002 in the data) connected to one Raspberry Pi B+. You’ll notice that the RPi is powering both Arduinos. (Red is power. Back is ground. Blue is SCL. Yellow is SDA.) Everything is integrated via the breadboard.
Lessons learned (and remembered):
1) Always connect the grounds. If you’re not seeing your minimally configured, powered and connected slave-sender device, check the ground first.
2) You can easily power a couple of Arduino’s from the RPi. Nice. I wonder how many you can power. I’m sure it depends on what you’re doing.
3) The SDA and SCL wires seem to be pretty flexible. You don’t have to chain them in a particular order as far as I can tell. Just make sure there’s some kind of connection and you’re good to go.
4) Arduinos, with built in analog to digital converters, are a lot less painful (and more reliable) to work with when it comes to reading basic sensors and sharing that data with an app running on the RPi. My original reason for revisiting this use case was dissatisfaction with reading a photocell directly on the RPi using Python (tutorial) and Arduinos are cheap — $10 at MicroCenter.