Преглед на файлове

wip, i2c arduino passthrough 'how-to'

abetusk преди 5 години
родител
ревизия
10b97a30f7
променени са 1 файла, в които са добавени 429 реда и са изтрити 0 реда
  1. 429 0
      how-to/RPi-Arduino-I2C.md

+ 429 - 0
how-to/RPi-Arduino-I2C.md

@@ -0,0 +1,429 @@
+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
+---
+
+* [I2C Arduino Raspberry Pi](https://dronebotworkshop.com/i2c-arduino-raspberry-pi/).
+