|
|
@@ -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/).
|
|
|
+
|