This is an archive of my website, as it appeared when I was in grad school

Want something new? Click here

There are many fantastic online tutorials describing how to interface a Lego Mindstorms NXT robot with the Arduino microcontroller, and there are several reasons for this project’s popularity. First, the NXT actually uses an AVR microcontroller internally for IO, so interfacing with these devices is easy! Second, the Arduino makes it fantastically inexpensive to add sensors to NXT. For example, you can buy an Arduino compatible 3-axis accelerometer from Adafruit Industries for $20.00, whereas a Lego accelerometer costs $70.00.

So why am I publishing yet another tutorial on this topic? Well, I am not an electrical engineer. I make tonnes of wiring mistakes, and I should have killed my Arduino many times over by now. This is acceptable for a $27 microcontroller platform, but not for the Lego Mindstorms (which is an order of order of magnitude more expensive). So I set out to find a safe way to isolate Arduino wiring errors from the Lego controller brick.

Watchword: Galvanic Isolation

So we want the Arduino to talk to the Lego Mindstorms, but we don’t want shorts and other errors to propagate to the Lego device. This is the perfect situation for something called Galvanic Isolation. Quoting Wikipedia:

Galvanic isolation is a principle of isolating functional sections of electrical systems, thus preventing the movement of charge-carrying particles from one section to another, i.e. no direct current flows between the sections. Information can still be exchanged between the sections by other means, e.g. capacitance, induction or electromagnetic waves, or by optical, acoustic or mechanical means.

So, galvanic isolation allows information to be exchanged without the exchange of electrons. This is exactly what we need.

I2C Serial Communication

Ok, so we know we want galvanic isolation to protect the NXT from the Arduino — but what kind of communication needs to take place? Well, it turns out that the Lego NXT brick uses the I2C serial bus for low-speed (9600 bps) communication with some advanced sensors (e.g., the ultrasonic rangefinder). Fortunately for us, the Arduino can also speak I2C with the help of the built-in wire.h library. There are many tutorials describing how this can be accomplished.

So how do we galvanically isolate the I2C serial bus? Well there are actually several approaches for this (e.g., see the article Opto-electrical isolation of the I2C-Bus). Many of these approaches are complex, and require a number of components. Fortunately, there are several one-chip solutions. In this article, I use the Silicon labs Si8400, but I have heard reports that Analog Device’s ADUM1250 is pin compatible (Thanks Anton for pointing this out!).

Here’s how we can use the Si8400AA-B-IS to connect the Arduino to the NXT:

Note 1: The NXT’s I2C interface has some very peculiar requirements — namely, the use of very high 82K Ohm pull-up resistors.

Note 2: In this schematic there are no pull-up resistors on the Arduino side. This is because Arduino’s wire.h library enables the AVR atmega’s internal pull-up resistors.

Here is a video of the device in action:

And here is a close-up of the prototype board:

Note 3: The Si8400 is only available in the SOIC-8 surface mount package form. You will need a SOIC-to-DIP breakout board (e.g., see ShmartBoard‘s offerings) to use this chip in standard 0.1″ perfboard.

Arduino Code

  * Adapted from Gatsu's post on Arduino's forums:
#include <Wire.h>

int ADDRESS = 0x43;   // The Arduino's I2C Address

uint8_t SensorVersion[9]  =    "V0.1    ";
uint8_t SensorName[9]     =    "Arduino ";
uint8_t SensorType[9]     =    "Exprmntl";

byte x = 0;
byte val = 0;
byte sendbuffer[] = {0};

void setup() {

void loop() {
  // This could be any code used to read a sensor.
  // In this simple example, we just count up once a second

void receiveEvent(int howMany) {
    if (Wire.available() >0 ) {
        x =; // receive register address (1 byte)

void requestEvent() { 
    if (x == 0x00) {   
        Wire.write(SensorVersion, 8); 
    else if (x == 0x08) {   
        Wire.write(SensorName, 8); 
    else if (x == 0x10) {
        Wire.write(SensorType, 8); 
    else if (x == 0x42) {
        sendbuffer[0] = val;
        Wire.write(sendbuffer, 1); 

Mindstorms Code (LeJOS 0.9)

Note 4: This code reports a “Bus Error” whenever the NXT cannot find the Arduino on the serial bus. There are a variety of potential causes for this error. In my experience, the first problem to rule out is that of mismatching I2C addresses. Due to some addressing incompatibilities, the LeJOS code must address the Arduino at an address which is twice the value declared in the Arduino code (i.e., left shifted one bit). See the comments in the code below for more details.

Note 5: The latest LeJOS build (v0.9.1) changed the LeJOS API again. The necessary changes are documented below.

import lejos.nxt.*;

public class TalkToArduino {

     /* The Lego/NXT uses 8-bit I2C addresses in the range 0x02-0xFE,
      * where the low bit is always zero. Many devices (like the Arduino) 
      * use 7-bit addresses in the range 0x01-0x7F. To compensate, 7-bit
      * addresses must be shifted left one place (multiplied by 2).
      * This issue cost me hours of frustration.
    private static int ARDUINO_ADDRESS = 0x43 << 1;

    public static void main (String[] args) { 
        I2CSensor arduino = new I2CSensor(SensorPort.S1); 

        byte [] buf = new byte[1];
        buf[0] = 0;


            int result = arduino.getData(0x42, buf, 1);  

            if (result == -5) {
                LCD.drawString("Not connected", 0, 0);
            else if (result == -3) {
                LCD.drawString("Bus error", 0, 0);
            else {		           
                LCD.drawString("Name:    " + arduino.getProductID(), 0, 0);

		// If LeJOS version 0.9, then use this line                     
                LCD.drawString("Type:    " + arduino.getSensorType(), 0, 1);
                // Else if LeJOS version 0.9.1 and above, then use this line instead of the previous                     
                // LCD.drawString("Vendor: " + arduino.getVendorID(), 0, 1);
                LCD.drawString("Version: " + arduino.getVersion(), 0, 2);
                LCD.drawString("===============", 0, 3);
                LCD.drawString("Value: " +  buf[0], 0, 4);

            try{Thread.sleep(100);}catch(Exception e) {}

Other resources

In a previous post I mentioned hearing a very interesting interview with Paul Davis, the head developer of Ardour (a FOSS digital audio workbench) on the FLOSS Weekly podcast. In this interview conducted by Leo Laporte and Jono Bacon, Paul Davis explains how interfaces can become overly complicated when trying to appease many expert users, […]

I’m excited about today’s CS849 class — it’s the first class all semester that specifically deals with usability issues that emerge in FOSS software and culture. I’m leading the discussion of several high profile blog postings on this topic. These blog postings, and their connections to one another are as follows (notably not in chronological […]

Admittedly, I’ve been slightly behind in posting to this blog the for CS849 course. However, papers from the last two lectures (June 9th and 14th) have been researching the motivations and social development of developers within FOSS communities. As such, I will discuss the contents of both lectures in one blog post. The papers at […]

I must admit, I’ve recently become transfixed watching the live footage of BP’s underwater robots frantically trying to mitigate the deepwater horizon oil leak. The video feed gave me a whole new perspective of the technical challenges of deep-sea offshore oil drilling. I’ve attached a few of the screenshots of the feed that I’ve captured […]