- Tutorial: Serial Connection Between Raspberry Pi and Arduino
- Setup
- Wiring
- Software Libraries
- Arduino: I2C Client Configuration
- Raspberry Pi
- Exchange I2C messages
- I2C Between Arduino & Raspberry Pi
- Introduction
- I2C Voltage Levels
- I2C operation
- Mixing Logic Levels
- Connecting Directly
- Bidirectional Logic Level Converters
- Raspberry Pi I2C Setup
- Obtaining Raspbian
- Enable I2C
- I2C Test Code
- Arduino Slave I2C Sketch
Tutorial: Serial Connection Between Raspberry Pi and Arduino
When you want to build complex microcontroller projects in which data needs to be exchanged between different devices, you need a fast and reliable way to exchange data. In the last article, we investigated serial UART connection, a direct one-to-one interface. This article continues the series with the I2C protocol, a half-duplex, bidirectional communication system with many-to-many servers and clients. We will see how to wire a Raspberry Pi and an Arduino Uno to form an I2C connection and exchange data between the two systems.
This article originally appeared at my blog admantium.com.
Setup
In the following setup, the Raspberry Pi will be the controller, and the Arduino Uno will be the client.
Wiring
We first wire the two devices as follows:
- Connect Raspberry GPIO2 => Arduino D18 SDA
- Connect Raspberry GPIO3 => Arduino D19 SCL
- Connect Raspberry Ground PIN => Arduino Ground
If you are unsure about the pin numbering and configuration, see the Raspberry Pi Pin Layout and Arduino Pin Layout, or read my earlier articles.
Software Libraries
On the Raspberry Pi, we need to install a I2C Raspian package, and a library for Python. The library of choice is SMBus, an I2C-based protocol. To install all required software, execute the following commands to install the required libraries.
For the Arduino, no additional setup is required. The library of choice is Wire.h, and it comes bundled with the Arduino IDE or a third-party IDE like plattform.io.
Arduino: I2C Client Configuration
The Arduino program will import the library, a wrapper for basic I2C communications. With the simple call of Wire.begin() it will start an IC2 client that can react on messages.
The following program implements a basic I2C client:
The program works as follows:
- Line 2: Import the library
- Line 4: Define the I2C client address with which the Arduino can be reach, here its hex 0x44 (decimal 68) — be careful to choose a suitable 7bit address that is not used by any other device on the same bus
- Line 7: To create the I2C client, execute Wire.begin with the chosen address
- Line 8: When the server sends a message to this client, the callback function receiveMsg will be executed
The callback function is defined as follows:
This function work as explained here:
- Line 2: Check that there are is an active, not consumed message on the I2C bus for this particular client
- Line 3: Read the first byte of the message, and store it as a char
- Line 4: Print the char
Let’s continue with the Raspberry Pi setup.
Raspberry Pi
The Raspberry Pi will start I2C node in the server role. In this role, it can actively write messages to the bus, and read data from the clients.
The following program will open a small terminal, waiting for user input, and then send this data to the client.
Lets explain the details:
- Line 1: Import the SMBus library
- Line 3: Define the address of the I2C client that will receive the messages, we specify hex 0x44 , decimal 144
- Line 4: Create an instance of the SMBus class
- Line 6: The custom method ic2Write receives a msg string, and in Line 8 it will send each character of this string to the client
- Line 10: The main method will start an infinite loop (Line 12), which opens the prompt $> to the user and reads the answer (Line 14), and will then call ic2Write with the complete message.
Ok, we are read to go.
Exchange I2C messages
First of all, check the wiring of the two devices. Then, upload the Arduino program via the Arduino IDE or a third-party IDE such as Plattform IO.
On the Raspberry Pi, start the Python program. And on another terminal, check that a new I2C hardware device is registered.
If you do not see a device, then check the program source code. Then, if all is well, Finally, use an I2C helper program to check that the Arduino is properly connected:
This command prints a table of all 7Bit — that is max 144 — connected IC2 devices. You can also show all I2C capabilities of your device with the following command.
On the terminal in which you started the Python program, type any input. Then, in the Arduinos serial console, you should see the received messages
Excellent! We can exchange I2C messages between the Raspberry Pi and Arduino.
But what about sending data from the client to the server? In I2C, the server controls all the communication, it actively requests data from its clients, and only when requested, are the clients answering. It is not possible to actively send data from clients to the server. To quote StackExchange:
All communication is controlled by the server. The clients does nothing, the server doesn’t want it to do. The server controls the speed of the clock (clock stretching not withstanding) and how many bytes are read. At no time should the clients try forcing the data line when the server did not tell it to. The data structure should be known beforehand.
Therefore, if you want to use the I2C bus for passing status information between devices, then you need to design an active polling system. First, each client needs to buffer its status messages. Second, the server needs to call the clients periodically, collect the status information, and act on this information.
I2C Between Arduino & Raspberry Pi
Introduction
We have already examined the I2C bus in a fair amount of detail. We have seen how the I2C bus works, and how we can create our own I2C sensors and devices using an Arduino. Today we will take another look at I2C, and this time we’ll be using both an Arduino and Raspberry Pi.
Both the Arduino and the Raspberry Pi support I2C, however interfacing them can present a special challenge as they don’t work at the same logic voltage levels. The Raspberry Pi uses 3.3-volt logic, whereas most Arduino’s (including the Arduino Uno) make use of 5-volt logic.
However, despite these voltage differences it is possible to interface the two devices. In fact, there are two ways to do it.
Follow along and we’ll examine both ways of interfacing a Raspberry Pi and an Arduino using the I2C bus.
I2C Voltage Levels
Before we examine the issues with mixing multiple I2C devices of different logic-levels it would be a good idea to make sure that we are familiar with the i2c bus and how it operates.
If you need a detailed explanation about the I2C bus please see the first article in this series. If you just need a quick refresher then please read on.
I2C operation
As a quick recap I2C, or the ”Inter-Integrated Circuit Bus”, is a method of exchanging serial data between two or more devices. An I2C circuit consists of one bus “Master” and one or more bus “Slaves”.
The bus uses four connections:
- SDA – the serial data line,. This is a bidirectional data line, handling all communications between the master and slave(s).
- SCL – The clock line. This provides the clock signal to synchronize the data on the SDA line.
- VCC – This is the logic-level voltage reference, typically either 5-volts or 3.3.-volts. In many arrangements, this voltage is also used to power the slave device.
- GND – A ground reference.
The arrangement of master and slaves(s) is illustrated below.
Notice the use of pull-up resistors. These resistors pull the logic and clock levels up to the level of the VCC reference voltage. This concept is very important to understand when interfacing devices with mixed logic levels.
Another important concept is that it is the Master that determines the logic voltage level. Keep that in mind when we start hooking up our Raspberry Pi and Arduino.
Mixing Logic Levels
5-volt logic, also sometimes referred to as “TTL logic”, has been around for many decades. The original microprocessors of the 1970s used this type of logic, as did the discrete CPU designs before them.
Despite its age 5-volt logic is still very common, and it’s no coincidence that the standard USB voltage is 5-volts (although the newer USB-C can make use of multiple voltages).
3.3-volt logic devices have also been around for many years, they are popular as they consume less current and are therefore ideal for battery-powered devices.
The rule for connecting the two logic families together is pretty simple:
- You may connect a 3.3-volt output to a 5-volt input.
- You may NOT connect a 5-volt output to a 3.3-volt input.
Interestingly, a 3.3-volt logic signal is capable of working properly when connected to a5-volt input. This is because most 5-volt logic chips have a threshold of a bit less than 3-volts, in other words, a logic signal of 3-volts or more will be recognized as a valid signal.
An exception to this is a Schmitt Trigger, a logic gate that has very narrow thresholds for zero and one. This type of gate is often used to “clean up” a noisy logic line and will not be triggered by a 3.3-volt signal.
Connecting Directly
The most common scenario for interfacing a Raspberry Pi with an Arduino is to have the Raspberry Pi assume the role of Master.
In this configuration, it is possible to connect the Raspberry Pi and Arduino directly together, as the Master is determining the logic levels. However, you need to be careful when doing this.
The most important thing to pay attention to when interfacing 3.3-volt logic to 5-volt logic is the arrangement of the pull-up resistors. It is critical that in this arrangement the pull-ups are connected to the 3.3-volt reference. Any pull-up connected to 5-volts will raise the logic level, possibly destroying the 3.3-volt device(s).
The Raspberry Pi has internal pull-up resistors on the I2C lines, which pul the bus up to 3.3-volts. As long as you don’t connect any devices that pull the levels up to 5-volts you will be OK.
You should also note that Arduino has open-collector outputs. Because of this, the Arduino logic levels on its I2C bus will be set to the levels of the pull-ups, which in this arrangement are in the Raspberry Pi.
If you attempt to interface to other 5-volt I2C devices you need to ensure that none of them have pull-ups that would bring the logic levels up to 5-volts.
Of course, if you decide to use the Arduino as a Master then you won’t be able to connect it directly to a Raspberry Pi (unless you use a 3.3-volt Arduino). For that job, you’ll need to add one more component.
Bidirectional Logic Level Converters
A Logic Level Converter is exactly that, a device that accepts logic signals of one voltage level and converts them into signals of another logic voltage level.
A logic level converter can be used to resolve issues with using I2C devices that operate on different voltage levels. Since the SDA (data) line is used to both send and receive data you will need to use a bidirectional logic level converter.
These devices are very inexpensive and are available in many configurations, common configurations are 2, 4 or 8-channel devices. Fo use with I2C a 2-channel device will suffice.
The bidirectional logic level converter is very easy to use. On one side you have the 3.3-volt logic signals, plus power and ground. The other side has equivalent connections using 5-volts.
Even is a scenario using a Raspberry Pi as Master it is still advantageous to use a bidirectional logic level converter. It provides a more reliable signal on both sides, and it prevents disaster caused by accidentally connecting a 5-volt logic device that has internal pull-up resistors.
All you need to do is ensure that all 3.3-volt devices are kept on the 3.3-volt side and that 5-volt devices are connected to the 5-volt side.
Today we will look at both methods of connecting the Raspberry Pi to the Arduino via I2C.
Raspberry Pi I2C Setup
Using I2C with an Arduino is pretty simple. You just look at the specifications to see which two pins on your Arduino are used for SDA and SCL, hook them up accordingly and then use the Wire Library (which is built into your Arduino IDE) in your sketch.
The Raspberry Pi requires a couple of extra steps, however. In fact, by default I2C is not even enabled.
Obtaining Raspbian
Unlike the Arduino, you need to install an operating system in order to start using your Raspberry Pi, which makes sense as the Raspberry Pi is essentially a small computer. The operating system, as well as all of your programs, are stored on a small MicroSD card.
While there are a number of operating systems that will work with the Raspberry Pi, the most common choice is Raspbian. This is a Linux-based OS especially made for the Raspberry Pi.
You’ll need to download the latest version of Raspbian from the Raspberry Pi website. As of this writing that is Raspbian Buster, released in July 2019.
There are a few variations of Raspbian to choose from, I recommend that you grab one of the versions that include the desktop.
Once you have the ZIP file downloaded you’ll need to burn the image onto a blank MicroSD card. Make sure you choose a card that is at least 8GB, I recommend 16GB or more, and pick a brand-name card with decent transfer rate specifications. This is especially important if you are using one of the newer Raspberry Pi 4 models.
A great utility for burning MicroSD cards is Etcher, it’s a free program that is available for Windows, Linux and Mac operating systems.
Once you have the MicroSD card burned, insert it into your Raspberry Pi, with the contacts facing towards the printed circuit board. You’ll also need to hook up a USB keyboard, USB mouse and HDMI video monitor. Then power up the Raspberry Pi with a suitable power supply and watch the screen.
Enable I2C
When you first boot up Raspbian it will partition the MicroSD card, which is it equivalent of a hard drive, and then restart. It will then take you to a Raspbian desktop, where you’ll be prompted to answer a number of questions about your locale, keyboard, and monitor. You also can set up WiFi here, assuming you’re using a Raspberry Pi that supports WiFi.
The system will then reboot and you’ll arrive back on the Raspbian desktop.
Now that the operating system is installed you’ll need to configure Raspbian to enable the I2C functions. You can do it as follows:
- Open a Terminal window, you’ll see an icon for this on the top menu bar.
- At the command prompt type sudo raspi-config . This will bring up a configuration menu
- Use your arrow keys to navigate to Interfacing Options.
- Select I2C. You wil be asked if you wish to enable the I2C interface.
- Use your keyboard to move to the Yes key and press Enter .
- Use the Tab key to navigate to Exit and press Enter .
I2C should now be operational on your Raspberry Pi. To confirm this, type the following at the Terminal command prompt:
This should bring back this response:
This indicates that the I2C-1 port is active. This is the port we will be using for our experiments.
Incidentally, there is also an I2C-0 port on the Raspberry Pi GPIO, however, it is only used to communicate with Raspberry Pi HATs (Hardware Attached on Top) and cannot be used to communicate with external devices.
Now that your Raspberry Pi is configured correctly we can begin our experiments.
I2C Test Code
Our experiment is going to be very basic, but it will serve to illustrate how communications between the Raspberry Pi and Arduino can be achieved.
We are simply going to control an LED attached to the Arduino using the keyboard on the Raspberry Pi. We’ll configure the Arduino as an I2C slave, accepting commands from the Raspberry Pi master.
Let’s get some code ready to accomplish this.
Arduino Slave I2C Sketch
We will begin with the code for the Arduino Uno. This device will be the I2C slave in our experiment.
Here is the sketch that we will be using with the Arduino: