This post demonstrates simple sending and receiving of data from a Raspberry Pi using Python 3.7 to a microcontroller using the Arduino libraries. There is a follow-up post here that demonstrates multi-byte transfer and control of GPIO, ADC, PWM, and stored data on the microcontroller.
Introduction
I have been thinking about building a robot again and would like to be able to use Artificial Intelligence / Image Recognition. The two leading candidates for the main processor are the Raspberry Pi 4 and the Beaglebone AI. I am electing to use the Raspberry Pi since I have more experience with it. A potential problem with that choice however is the lack of peripherals compared to the Beaglebone as well as the real time control possible with the PRUs on the Beaglebone.
To address this I am experimenting with I2C using a Raspberry Pi as the master and a microcontroller as the slave. I have used I2C in the past and have written drivers for ICs but not from the Raspberry Pi. In this post a Python 3.7 script running on a Raspberry Pi 4 will be used to send a single byte to an Adafruit Feather M0+ using the Arduino libraries to control the built-in LED. It will then request the Feather to return the byte and display it on the terminal.
There is nothing original about this and there are multiple posts on the internet doing something similar. However, most seem to be written for Python 2.7, have errors, don't demonstrate return of data, etc. If future experimentation works out another post will be made demonstrating a more sophisticated use of the microcontroller such as motor control, adding additional GPIO, PWM, DAC, ADC, etc.
Overview of I2C
Wikipedia gives a good discussion of I2C which is greatly glossed over in this section. I2C (Inter-integrated Circuit) is pronunciation variously as I-squared-C, I-two-C and IIC. It was invented in 1982 by Phillips Semiconductor and is a bidirectional bus consisting of two open drain lines, the Serial Data Line (SDA) and Serial Clock Line (SCL) pulled up with resistors. System Management Bus (SMBus) is a stricter subset developed by IBM.
The bus can act in master mode where a node generates the clock and initiates communication with slaves or in slave mode where the node receives the clock and responds to the master. Multiple nodes can be on the bus with 7-bit addressing. With the Python and Arduino modules / libraries and the hardware on the Raspberry Pi and Adafruit board it is not necessary to get too deep into the details of how it works so that is left to the reader. In the examples below the Raspberry Pi always acts as the master and the Feather as the slave.
Setup
The components required are a Raspberry Pi and a microcontroller that can use the Arduino Wire library. In this demonstration a Raspberry Pi 4 and an Adafruit Feather M0+ are used but many others would work as well. In the photo below the Raspberry Pi 4 is bottom right and the script can be seen running in the Geany IDE on the display. The Feather is between the display and Raspberry Pi and when will blink the onboard red LED when directed to do so..
Raspberry Pi and Python
The Python 3 script below alternately sends a 0 and 1 to the Feather once per second over I2C and then requests the Feather to ping it back. Be careful when connecting SDC, SCL, and GND to the Raspberry Pi as they are not marked on the board and it would not be difficult to inadvertently connect 5V to a 3V board like the Feather.
''' i2c_v1.py Simple Python 3 script to test I2C by alternately sending a 1 and 0 over I2C every second and reading back the response from the Arduino Tested on Raspberry Pi 4 B+ and Adafruit Feather M0+ ''' import smbus import time import sys bus = smbus.SMBus(1) address = 0x04 # Arduino I2C Address def main(): i2cData = False while 1: # send data i2cData = not i2cData bus.write_byte(address,i2cData) # request data print ("Arduino answer to RPi:", bus.read_byte(address)) time.sleep(1) if __name__ == '__main__': try: main() except KeyboardInterrupt: gpio.cleanup() sys.exit(0)
- Line 8 imports the smbus module - remember from above that SMBus is a stricter subset of I2C
- Line 11 instantiates a new smbus object on the Raspberry Pi I2C port 1
- Line 12 gives the I2C address for the Feather - it is arbitrarily set to 0x04
- Line 17 flips the data sent over I2C from True to False and back (0 to 1)
- Line 18 sends the data as a byte to the Feather. In future posts I hope to describe transfer of more complex data.
- Line 21 requests the Feather to send a byte back, which in this case is the data just sent by the Raspberry Pi.
- Lines 24 on allows a graceful exit when ctrl-C is entered
Adafruit Feather M0+ and Arduino Libraries
The code below receives I2C data from the Raspberry Pi and turns the built-in LED on if the data received is a 1. If the Raspberry Pi makes a request for data, the most recent received data is returned.
/* * Arduino code to send and receive I2C data * Tested on Adafruit Feather M0+ Express and Raspberry Pi Model 4 * * SDA <--> SDA * SCL <--> SCL * GND <--> GND * * Sets built-in LED (1 = on, 0 = off) on Feather when requested * and responds with data received */ #include <Wire.h> #define SLAVE_ADDRESS 0x04 // I2C address for Arduino #define LED 13 // Built-in LED int i2cData = 0; // the I2C data received void setup(){ Wire.begin(SLAVE_ADDRESS); Wire.onReceive(receiveData); Wire.onRequest(sendData); pinMode(LED, OUTPUT); digitalWrite(LED, HIGH); } void loop() { // Everything happens in the interrupts } // Handle reception of incoming I2C data void receiveData(int byteCount) { while (Wire.available()) { i2cData = Wire.read(); if (i2cData == 1) { digitalWrite(LED, 1); } else { digitalWrite(LED, 0); } } } // Handle request to send I2C data void sendData() { Wire.write(i2cData); }
- Line 12 is the Wire (I2C) library for Arduino.
- Line 13 defines the address, which must be the same as that in the Raspberry Pi Python script
- Line 17 initiates the bus with the I2C address and is called once
- Line 18 registers a function that is called when in slave mode and data is received from a master
- Line 19 registers a function that is called when in slave mode and the master requests data.
- Line 23 the loop is empty as everything is handled by the interrupts that occur when I2C data is received or a request for data is made
- Line 27 the receiveData function is called when I2C data is incoming. The integer parameter byteCount contains the number of bytes sent.
- Lines 28 - 35 read the incoming data, stores it in the variable i2cData, and turn the LED on or off depending on what is received.
- Line 39 the sendData function is called when the master requests data
- Line 40 writes the last incoming data, previously stored in the variable i2cData back to the bus
Summary and Video
While not too exciting, the video below demonstrates the output.
In this post there is a more useful template for implementing I2C. As always, comments and corrections are welcome.
Useful Links
Wikipedia I2C Entry
Follow-up Post with Multi-byte transfer and control of GPIO, ADC, PWM, and stored data
Top Comments