Sunday, August 7, 2016

I2C communciation between Pi and Arduino/AVR - Part 1


Transfer at 100Kb/sec with two wires?  Supervise 100 Arduinos or AVRs with one Pi?  Those are the sugar plums the started dancing in my head when I began my first experimenting with Arduinos and robot stuff.

There are things the Pi does well (massive processing power, 'Jessie' Debian, very cheap) and things that the Arduino does well (very easy to program, analog sampling, motor/servo/stepper control)...so all we need is a division of labor and a good reliable way for the Pi to wave the baton, yes?

Thank you, Peter Mount..

So my first idea was to use the Gordon Henderson's wiringPi library and the I2C or SPI libraries there...but I was leaning toward I2C in any case and my various attempts at using it didn't work well.  A little Googling, though, found me this:

https://blog.retep.org/2014/02/15/connecting-an-arduino-to-a-raspberry-pi-using-i2c/

This gave me a good reason to dive into C heavy once again; it uses the i2c-tools and i2c-dev packages available with Raspbian (in 'Jessie' you have to apt-get them, though).  It's a more low-end approach, but it was a lot easier to debug and after a few weeks off-and-on, I got some good basic Pi-2-Arduino transceiver code working on both ends.

Attached below is my Docs writeup of my progress, so far....

----------------------------------------------------------------------

Introduction: I started this project because I need a good practice program to teach myself the ins-and-outs of Pi GPIO and Arduino stuff; I ultimately want to create a tracked robot that can be controlled and monitored from a web-interface. For that to work, I need a CGI program that will send commands to a navigation program, which will run a command line utility to control the motors and servos and query sensors. Since the Arduino is best for motor control, and the Pi has the horsepower to do the more complicated robot navigation and sensor processing, I decided to learn how to slave an Arduino via I2C to the bus on a Pi. Obviously I wanted a classic command-line tool that would send ONE command/query to the Arduino and receive the status/results, and print them out to STDOUT, for another program (a Ruby robot navigation script or a TCP/IP client) to use.

This system is intended as a general purpose command line tool (the Pi side) to query data pins and send arbitrary strings (that can be interpreted as commands on the Arduino side). Multiple Arduinos can be ‘slaved’ to a single I2C bus and queried separately, so that it is theoretically possible for Arduino-to-Arduino transfers to be facilitated (initiated by the Arduinos) if some additional interrupt lines were connected up and polled by the Pi to detect the requests.


The ‘test’ hardware setup includes (8/3/16)

Arduino Uno (rev. 3)
Raspberry Pi B+ with Adafruit T-Cobbler and cable
Breadboards and jumpers and things
Sparkfun BOB-1200 4x1 bit bi-directional level shifter on breakout board
Motorola MMA2200W accelerometer and support components (pin A0)
Radio Shack ‘standard servo’ (driven from ArdUno pin 9)
Tamiya planetary gear motor
IRF540 MOSFET PWM driver for above (from pin 6)
‘6V’ NiMH 4xAA battery pack for servo
‘6V’ NiMH 4xAA battery pack for motor
8 ohm speaker w/ 330 ohm resistor (pin 11)

The level shifter is the most important part of the hardware here; RaspPi uses 3.3V TTL levels, which must be converted to 5V for the Arduino. Don’t kill your Pi by connecting 5V signals to the Pi GPIO connector!

Both the Pi and the Arduino Uno have internal pullups on their I2C pins, so no external pullup/debouncing components are needed. SDA and SDL pins (A4 and A5 on the Ard Uno) are connected together on the opposite of the (correctly powered) level shifter, and the rest comes naturally.

The Motorola accelerometer produces 0-5V corresponding linearly to -50G to +50G, with 0G at 2.5V in theory. My particular device reads slightly low, or….my table may be slightly unlevel. The output for the MMA2200W is connected to analog pin 0 on the Ard Uno.

The ‘standard servo’ is connected to digital pin 9, which also has PWM output capability; a separate battery pack is used to power the servo. BE SURE to connect the negative side of the battery pack to the Arduino/Pi common ground plane!

The software (8/3/16)


Raspbian ‘jessie’
      w/ i2c-dev, i2c-tools, and wiringPi packages installed
Arduino IDE 1.6.9
‘Ardxcv<ver>’ - Pi-side C i2c ‘master’ program
‘i2c-2-pi-<ver>’ - Arduino-side ‘slave’ program

Needless to say, the i2c bus must be enabled on the Pi. You can test it with the wiringPi ‘gpio’ tool by entering ‘gpio i2cd’, which should give you a bus map with all the devices on it if everything is setup right. But the ‘ardxcv’ (see below) program is not otherwise wiringPi dependent, and there are other ways to do testing, ferinstance...

You ALSO need to install the i2c-tools package for the Pi, so that you will have the ‘i2c-dev’ library available. You can query the i2c bus on a newer pin with ‘ic2detect -y 1’ if you don’t have wiringPi installed.
------------------------------------------------------

8/2/16 - Currently there are two pieces of software: ‘ardxcv21’ on the RaspberryPi side, and ‘i2c_2_pi_5’ on the Arduino side.

Click here to see ver.0.21 of the code

ardxcv21 : compile like so: ‘gcc ardxcv21.c -o ardxcv21’, and run as root (e.g. ‘sudo ./ardxcv21’). Currently there are three basic commands and three command line options:

‘ardxcv21 1’ - check device status of ‘standard device’ (I2C address 0x08); should return status 1|result NIL’ if it talks to the Arduino ok.

‘ardxcv21 4 <string>’ - send an arbitrary string (limited to 20 characters at the moment) to the Arduino. Two <string> combinations are currently set up to ‘do’ something:

‘Fx-yy’ - sets (x=1) or clears (x=0) digital pin yy.

‘Sy-xxx’ - slew servo y to xxx degrees.

‘Sy-xxxxM’ - slew servo y with xxxx microsecond pulses

Any other string is ignored. Example? ‘ardxcv21 4 F1-13’ to light the onboard LED on the Uno.

‘ardxcv21 7 <pincode>’ - query an analog or digital pin. ‘pincode’ can be ‘a-f’ for analog pins (corresponding to A0-A5) or ‘A-Z’ (for digital pins 0-25). The result string will be something like ‘status 7|result AV:xxxx’ where xxxx is the analog result, or ‘DV:x’ where x is the digital (0 or 1) state of the pin. Example: ‘ardxcv 7 a’ to get the accelerometer reading from pin A0. NOTE: DO NOT query A4 or A5 (e or f) since those are the SDA/SCL pins for I2c! I will add an ‘exclusion table’ to the Arduino-side code so that the Uno will ignore pins you shouldn’t be messing with for certain projects.

option -D: Debug mode. Extra information is printed to STDIO to show the actual signals and data going back and forth during a transfer.

option -Ay: Device ID, default is 8. Talk to I2C device on address y. Currently limited to 3-13, cuz I said so.

option -By: PI I2C bus device. Defaults to 1, can be ‘-B0’ for older revisions of the Pi I2C bus hardware.

Return status codes (non-error, non-debug):

‘I2C: <command> sent to 0x<device id>: status <status code>|result <data>’

-9: bus and device are ok, but no valid (one of 1, 4, 7) command arguments given.
-3: unrecognized command.
-2: i2c device not available at given address
-1: i2c bus not available
1: command ‘1’ (status) returned success
4: string write to Arduino successful
7: pin query successful (result follows)

------------------------------------------------------

i2c_2_pi_5: the Arduino-side code. Currently this has a debugging mode (set the DEBUG #define to 0 to turn it off) that writes data to the serial port, so that you can monitor the Arduino during transfers...but this hogs a considerable amount of code and static RAM space, and it will be cut out eventually.

As described in the intro material, the hardware setup currently has the servo connected to pin 9 for PWM fun, and the accelerometer to A0. Pin 13, the onboard ‘fun’ LED, flashes on and off when data is received with the ‘4’ command, and also flashes very fast when an analog or digital pin is queried with the ‘7’ command.

Click here to see ver 0.6 of the code

------------------------------------------------------

8/3/16 -
Updated Arduino code, new version:

One of the worries, when I got this far, was that I did NOT know if the Arduino would actually be able to handle multiple PWM/analogOut devices at the same time. The Uno/Mega328P has three timers, though, so….and it turns out that this works, with some caveats. Look at the following table

Timer output   Arduino Uno pins:
OC0A              6
OC0B              5
OC1A              9
OC1B             10
OC2A             11
OC2B              3



Thanks to Ken Shiriff for his Arduino PWM writeup here:

http://www.righto.com/2009/07/secrets-of-arduino-pwm.html

What this all means is….pins 5&6 are controlled by timer 0 (which also supplies the timing for the delay() and millis() functions), 9&10 by timer 1, and 3&11 by timer 2. While there ARE six total PWM output pins, you can only generate three truly independent base frequencies. You can play with prescaler settings and things down on the bare-metal AVR level, so you CAN get different duty cycles to two pins off one timer, but...there are other gotchas.  If you want to use 5 or 6 at all, you can’t use delay()/millis() for accurate timing. Luckily, the ADC and the various serial communications interfaces (SPI, I2C, etc.) are not affected by PWM output on any of these pins.

The tone() function additionally MUST use the OC2 timer, so you might as well hook up any sound device to 3 or 11 since those pins won’t work as analog outputs while a tone is being generated anyway.

In any case, and without further ado:

i2c_2_pi_6: added ‘pin lists’ and some error detection and bounds checking so that pins and servos will not be accidently driven that should NOT be. Added the PWM motor control and Tone commands, which can be sent from ‘ardxcv21’. Ferinstance:

‘ardxcv21 4 Mx-yyy’: set motor ‘x’ (NOT pin number) to PWM level ‘yyy’ (0-255)

‘ardxcv21 4 Txx-yyyyy’: send tone on pin ‘xx’ to frequency ‘yyyyy’. (yyyyy = 0 turns off).

I verified that all three analog output techniques work simultaneously: I set a tone of 1000 Hz on pin 11 to the 8 ohm speaker, then started the motor at maximum on pin 6, then slewed the servo back and forth a few times on pin 9...then went back and changed tones and motor speed...no glitches.

Click here to see ver 0.6 of the code

---------------------------------

No comments:

Post a Comment