RPi-Arduino-I2C.md 9.3 KB

Raspberry Pi (3B) to Arduino I2C Setup

I had a hard time finding example code to do simple I2C communication with an Arduino, so I decided to write this minimal tutorial on how to get non-trivial communication functioning between an Arduino (Nano) and a Raspberry Pi (3B).

This tutorial shows a simple "passthrough" to communicate to an Arduino (Nano) via I2C from a Raspberry Pi (3B). Text can be sent via the Serial line from the Arduino and show up from a program running on the Raspberry Pi. Messages from the Raspberry Pi can be sent via the I2C line and show up on the Serial line on the Arduino.

Overview

The Arduino will have a direct connection to the Raspberry Pi's I2C line and the Arduino will act as the 'slave/worker' whereas the Raspberry Pi will act as the 'master/manager'.

The Arduino will have a connection to the PC via a USB line which will serve double duty to program the Arduino and allow us to interact with the Serial interface.

Normally, the operating voltage of 5V for the Arduino would cause problems when interfacing with the 3.3V of the Raspberry Pi but since the Arduino is a 'worker' I2C device and the Raspberry Pi is the 'manager' I2C device, the Arduino will not drive the I2C lines higher than the 3.3V the Raspberry Pi sets them at. This means we can interface the Arduino directly to the I2C lines of the Raspberry Pi with out a line level converter.

Once the physical wiring is setup, the Arduino will be programmed with a simple 'passthrough' program that sets up a receive and send I2C interrupt handlers for the I2C communication to the Raspberry Pi.

The Raspberry Pi will be setup to enable the I2C lines and a program will be created that uses the I2C SMBUS interface to communicate over the I2C lines.

Wiring

Arduino Programming

The Arduino will be setup with an I2C worker address of 8.

The following program should be put into a sketch and the Arduino should be programmed:

//
// This code is in the public domain.
//

#include <Wire.h>


#define MSG_BUF_MAX 128
#define ARDWORKER_I2C_ADDRESS 0x08


char _tbuf[MSG_BUF_MAX];

int   i2c_msg_buf_n = 0,
      i2c_msg_buf_rdy = 0;
char  i2c_msg_buf[MSG_BUF_MAX];

int serial_buf_s=0,
    serial_buf_n=0;
char serial_buf[MSG_BUF_MAX];

void setup() {
  i2c_msg_buf_rdy = 0;
  i2c_msg_buf[0] = '\0';
  i2c_msg_buf_n = 0;

  serial_buf_s=0;
  serial_buf_n=0;
  serial_buf[0] = '\0';
  
  Wire.begin(ARDWORKER_I2C_ADDRESS);
  Wire.onReceive(receive_i2c_data);
  Wire.onRequest(send_i2c_data);
  Serial.begin(115200);
}



void loop() {
  int i, _b, _cur;
  int _ser_print_rdy = 0;
  

  noInterrupts();
  if (Serial.available()) {
    _b = Serial.read();
    if (_b >= 0) {
  
      // Add to our circular serial_buf for later tranport
      // through to the i2c line.
      //
      if (serial_buf_n < MSG_BUF_MAX) {
        _cur = (serial_buf_s + serial_buf_n) % MSG_BUF_MAX;
        serial_buf[_cur] = (char)_b;
        serial_buf_n++;
      }

    }

  }
  interrupts();

  noInterrupts();
  if (i2c_msg_buf_rdy) {
    for (i=0; i<MSG_BUF_MAX; i++) {
      _tbuf[i] = i2c_msg_buf[i];
    }
    
    i2c_msg_buf[0] = '\0';
    i2c_msg_buf_n=0;
    i2c_msg_buf_rdy=0;
    _ser_print_rdy = 1;
  }
  interrupts();

  if (_ser_print_rdy) {
    Serial.print(_tbuf);
  }

}


void send_i2c_data() {
  int res;

  if (serial_buf_n > 0) {
    res = Wire.write((char)serial_buf[serial_buf_s]);
    serial_buf_n--;
    serial_buf_s++;
    serial_buf_s %= MSG_BUF_MAX;
  }

}


void receive_i2c_data(int howMany) {
  char c;

  while ( Wire.available()) {
    c = Wire.read();

    i2c_msg_buf[i2c_msg_buf_n++] = c;
    if (i2c_msg_buf_n >= MSG_BUF_MAX) {
      i2c_msg_buf_n = MSG_BUF_MAX-1;
    }

    if ((c == '\n') || (c == '\r')) {
      i2c_msg_buf[i2c_msg_buf_n] = '\0';

      // Our messages are terminated by '\n' and '\r'.
      // Once a message is received, shuttle it to the
      // serial line.
      //
      i2c_msg_buf_rdy=1;
    }
  }

}

Once done, a serial window should be opened with the baud rate set to 115200.

Raspberry Pi Configuration

On the RPi, issue raspi-config (as root) and setup I2C

(5) Interfacing Options -> (P5) I2C

Check to see the device is available:

# ls /dev/*i2c*
/dev/i2c-1

If desired, the speed for the I2C device can be confirmed with:

#!/bin/bash
var="$(xxd /sys/class/i2c-adapter/i2c-1/of_node/clock-frequency | awk -F': ' '{print $2}')"
var=${var//[[:blank:].\}]/}
printf "%d\n" 0x$var
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>

#include <linux/i2c-dev.h>
#include <i2c/smbus.h>

#include <errno.h>

#include <stdint.h>

#define ICHTHYIC_MAX_BUF 128

typedef struct ichthyic_type  {
  uint8_t i2c_fn[ICHTHYIC_MAX_BUF];
  int     i2c_adapter;
  int     i2c_worker_addr;
  int     i2c_fd;

  uint32_t i2c_from_buf_n;
  uint8_t i2c_from_buf[ICHTHYIC_MAX_BUF];

  uint32_t i2c_to_buf_n;
  uint8_t i2c_to_buf[ICHTHYIC_MAX_BUF];

} ichthyic_t;

ichthyic_t *ichthyic_alloc() { return malloc(sizeof(ichthyic_t)); }
void ichthyic_free(ichthyic_t *ich) { free(ich); }

void ichthyic_init(ichthyic_t *ich) {
  ich->i2c_from_buf[0] = '\0';
  ich->i2c_fn[0] = '\0';
  ich->i2c_adapter  = -1;
  ich->i2c_worker_addr = -1;
  ich->i2c_fd = -1;

  ich->i2c_to_buf_n = 0;
  ich->i2c_to_buf[0] = '\0';

  ich->i2c_from_buf_n = 0;
  ich->i2c_from_buf[0] = '\0';
}

int ichthyic_i2c_queuebuf(ichthyic_t *ich, uint8_t *buf, uint32_t nbuf) {
  int i;
  for (i=0; i<nbuf; i++) {
    ich->i2c_to_buf[i] = buf[i];
  }
  ich->i2c_to_buf[nbuf] = '\0';
  ich->i2c_to_buf_n = nbuf;
}

int ichthyic_i2c_read(ichthyic_t *ich) {
  int32_t res, dn=0;

  res = i2c_smbus_read_byte(ich->i2c_fd);
  if (res<0) { return -1; }
  if (res==0) { return 0; }

  dn=1;
  while (ich->i2c_from_buf_n < (ICHTHYIC_MAX_BUF-1)) {

    ich->i2c_from_buf[ich->i2c_from_buf_n] = res;
    ich->i2c_from_buf_n++;
    ich->i2c_from_buf[ich->i2c_from_buf_n] = '\0';

    res = i2c_smbus_read_byte(ich->i2c_fd);
    if (res<0) { return -1; }
    if (res==0) { break; }

    dn++;
  }

  return dn;
}

int ichthyic_i2c_write(ichthyic_t *ich) {
  int i;
  int32_t res;

  for (i=0; ich->i2c_to_buf[i]; i++) {
    res = i2c_smbus_write_byte(ich->i2c_fd, ich->i2c_to_buf[i]);
  }
  ich->i2c_to_buf_n = 0;
  ich->i2c_to_buf[0] = '\0';
}

int ichthyic_i2c_writebuf(ichthyic_t *ich, uint8_t *buf, uint32_t nbuf) {
  uint32_t i;
  int32_t res;
  for (i=0; i<nbuf; i++) {
    res = i2c_smbus_write_byte(ich->i2c_fd, buf[i]);
  }
}

int main(int argc, char **argv) {
  int i, ret, fd, adapter_nr, ch;
  char fn[32];
  int worker_addr = 0x08;

  FILE *msg_fp;
  char msg_fn[] = "msg/msg.txt";
  char *msg_buf=NULL;
  int msg_buf_n=0;

  struct timespec _sleepy, _sleepy_rem;

  ichthyic_t ich;

  // 1000000000 ns in 1 sec
  //
  _sleepy.tv_sec = 0;
  _sleepy.tv_nsec = 1000000;


  ichthyic_init(&ich);
  ich.i2c_worker_addr = 0x08;
  ich.i2c_adapter = 1;
  snprintf(ich.i2c_fn, 31, "/dev/i2c-%i", ich.i2c_adapter);
  ich.i2c_fd = open(ich.i2c_fn, O_RDWR);
  if (ich.i2c_fd < 0) {
    perror(ich.i2c_fn);
    exit(-1);
  }

  if (ioctl(ich.i2c_fd, I2C_SLAVE, ich.i2c_worker_addr) < 0) {
    perror(ich.i2c_fn);
    exit(-2);
  }

  msg_buf = malloc(sizeof(char)*1024);

  while (1) {

    // for debugging, process message file
    //
    msg_fp = fopen(msg_fn, "r");
    if (msg_fp) {
      msg_buf_n = 0;
      while (!feof(msg_fp)) {
        ch = fgetc(msg_fp);
        if (ch == EOF) { continue; }
        msg_buf[msg_buf_n++] = ch;
      }
      msg_buf[msg_buf_n] = '\0';
      //msg_buf_n++;
      fclose(msg_fp);
      unlink(msg_fn);

      printf("processing[%i]: '%s'\n", msg_buf_n, msg_buf);

      ichthyic_i2c_queuebuf(&ich, msg_buf, msg_buf_n);
    }

    ichthyic_i2c_write(&ich);
    ret = ichthyic_i2c_read(&ich);
    if (ret<0) {
      perror("i2c read");
    }
    if (ret > 0) {
      printf("# read +%i (%i,%i) i2c bytes\n", ret, ret, ich.i2c_from_buf_n);
    }

    if (ich.i2c_from_buf_n > 0) {
      if ((ich.i2c_from_buf[ ich.i2c_from_buf_n-1 ] == '\r') ||
          (ich.i2c_from_buf[ ich.i2c_from_buf_n-1 ] == '\n')) {
        printf("[%i]>:", ich.i2c_from_buf_n);
        for (i=0; i<ich.i2c_from_buf_n; i++) {
          if (ich.i2c_from_buf[i] == '\n') {
            printf("`");
          }
          else if (ich.i2c_from_buf[i] == '\r') {
            printf("~");
          }
          else if ((ich.i2c_from_buf[i] >= ' ') &&
                   (ich.i2c_from_buf[i] <= '~')) {
            printf("%c", ich.i2c_from_buf[i]);
          }
          else {
            printf("<%x>", ich.i2c_from_buf[i]);
          }
        }
        printf("\n");
        ich.i2c_from_buf[0] = '\0';
        ich.i2c_from_buf_n = 0;
      }
    }

    nanosleep(&_sleepy, &_sleepy_rem);

  }

  close(ich.i2c_fd);
}

Creating Virtual TTY

A virtual TTY pair can be created with socat:

socat -d -d pty,raw,echo=0,link=/tmp/ttyPTich pty,raw,echo=0,link=/tmp/ttyPT

ichthyic-passthrough can be used to connect the two, logging to /tmp/ich.log:

./ichthyic-passthrough -i /tmp/ttyPTich -I /dev/i2c-1 -v -v -L /tmp/ich.log

Running

Once the Arduino has been programmed and the Raspberry Pi has been setup, the programs can be run to test out.

References