Tutorial: Serial Connection Between Raspberry Pi and Arduino

Sebastian
Sebastian
Sep 27 · 5 min read

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

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.

apt-install i2c-tools
sudo pip3 install smbus

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 <Wire.h> 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:

#include <Arduino.h>
#include <Wire.h>
#define I2C_DEVICE_ADDRESS 0x44void setup() {
Wire.begin(I2C_DEVICE_ADDRESS);
Wire.onReceive(receiveMsg);
Serial.begin(9600);
Serial.println("Listening for Input");
}
void loop() {
Serial.print(".");
delay(500);
}

The program works as follows:

  • Line 2: Import the <Wire.h> library

The callback function is defined as follows:

void receiveMsg() {
if (Wire.available()) {
char c = Wire.read();
Serial.print(c);
}
}

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

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.

from smbus import SMBusclientAddr = 0x44
bus = SMBus(1)
def i2cWrite(msg):
for c in msg:
bus.write_byte(clientAddr, ord(c))
return -1
def main():
print("Send msg to Arduino")
while True:
msg = input("$> ")
print("...")
i2cWrite(msg)
if __name__ == "__main__":
main()

Lets explain the details:

  • Line 1: Import the SMBus library

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.

$> ls /dev/*i2c*
/dev/i2c-1

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:

$> i2cdetect -y 1     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

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.

$> sudo i2cdetect -F 1Functionalities implemented by /dev/i2c-1:
I2C yes
SMBus Quick Command yes
SMBus Send Byte yes
SMBus Receive Byte yes
SMBus Write Byte yes
SMBus Read Byte yes
SMBus Write Word yes
SMBus Read Word yes
SMBus Process Call yes
SMBus Block Write yes
SMBus Block Read no
SMBus Block Process Call no
SMBus PEC yes
I2C Block Write yes

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

Listening for Input
.......................Hello from Raspberry Pi!................

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.

Conclusion

This article showed the essential steps to establish an I2C connection from a Raspberry Pi, acting as the server, to and Arduino Uno, acting as the client. For the Arduino, we use the built-in library Wire.h, which handles the concrete I2C message details, and exposes methods to start, listen and handle I2C communications from the server. On the Raspberry Pi, we use the Python SMBus library, with which it is easy to start an IC2 server bus and actively send messages to its connected clients.

Geek Culture

Proud to geek out. Follow to join our +1.5M monthly readers.