Tuesday, June 9, 2020

Bidirectional Serial Communication between Arduino and Raspberry Pi



For most of my movie replica props, the Arduino is well-suited to control the lights, sounds, push buttons, etc. Sometimes, however, a prop requires more, like a fast-responding high resolution LCD screen. In those cases am still keeping the Arduino to handle the other electronics, but I am adding a Raspberry Pi to operate the screen and, possibly, access time, date, weather, news information from the internet.

This setup requires the Arduino and the Raspberry Pi to communicate with each other. Conceptually, there are different options to achieve this. Some of these require additional hardware (voltage level shifters) to convert the different operating voltages (most Arduinos: 5V / RPi: 3.3V).



The easiest solution is a simple "serial" connection of the two via a standard USB cable. Just take the cable that you usually use to connect the Arduino to your PC for programming, and plug this into the Raspberry Pi. As a nice side effect, this also provides power to the Arduino, so no additional power connection is needed. So, obviously, this project requires an Arduino with a USB interface, like an Arduino Uno, Mega, or Nano.

For the Arduino, the regular software framework is used  - the Raspberry Pi code is written in Python (Python3, to be precise).

Planning the Data Exchange

The Arduino and Raspberry Pi will be exchanging bytes - in my example a single byte per transfer. One byte can have values in the range 0-255. So, when planning my projects, I'm creating a protocol that includes all possible pieces of information that the two may send each other. Such a protocol for the Arduino-Raspberry Pi serial connection may look like this:

Arduino sends
1        short press of push button #1 
2        long press of push button #1 
3         the PIR infrared motion sensor fired
11-17     request the RPi to display images 1-7 on the screen
21-25     request the RPi to show video clips 1-5
31-87     request the RPi to play audio clips 1-57
91        request the RPi to download new weather data and display it
92        request the RPi to download new news data and display it
93        request the RPi to check Google calendar for upcoming events
RPi sends
1  request the Arduino to turn on LED #1 for 3 sec 
2  request the Arduino to turn on LED #2 for 3 sec
3  request the Arduino to turn on LED #3 
4  request the Arduino to turn off LED #3 
11  request the Arduino to start a short beep on a connected piezo
12  request the Arduino to start a long beep on a connected piezo
21  tell the Arduino that a new hour has started
31-35    tell the Arduino that alarm 1-5 was fired

So, if it is not required to transmit more complex information like dates or times, but only simple requests, a single byte is often sufficient. If a project requires more than a single byte to be transferred, the example below can easily be extended to multiple bytes.

In the following I introduce all the single code pieces required for the Serial communication: The transmit and received codes for the Arduino and the Raspberry Pi. Below, everything is put together in a simple example that demonstrates the full functionality.

The Arduino Code (send & receive)

For any serial communication, the Arduino and Raspberry Pi need to agree on the speed of the communication. Here, I choose a baud rate of 9600. In the Arduino code, this is set in the "setup()" function.
// =======================================================
void setup(){
  Serial.begin(9600);
}
The Arduino code to transfer one byte "n" via serial is:
 Serial.write(n);
Reading a byte received by the Arduino, is done in the following code:
 byte n = 0;
 if (Serial.available() > 0) {
    int var = Serial.read();    // data type: int
    n = var;        
 }
 The "Serial.read" command returns an "integer" result, which is then stored in the byte-type variable "n". While the code to transmit/send a byte is only called when needed, the code for reading a byte must, of course, be called continuously.

The Raspberry Pi Code (send & receive)

For the Raspberry Pi, one first needs to find out the logical port at which the Arduino is connected. After connecting the Arduino via the USB cable, the port can be found by typing (before and after connecting the Arduino):
ls /dev/tty* 
From the difference before/after one can figure out the name of the Arduino port. Typical locations for the port are: 
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyACM0
In my case, it is the first one: "/dev/ttyUSB0". In the Python code this information is used to start the serial connection in the following statement: 
# --- start serial
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
It has to be noted that the Raspberry Pi Python serial code does not send byte- or integer-type variables, but character variables. This requires to convert the bytes before sending, and converting the received characters to bytes.
Sending a byte "x" is done by
x = 11
ser.write(x.to_bytes(1, 'little'))
where the inner function "x.to_bytes" converts the integer variable "x" to the corresponding character, which is then transmitted via serial by the function "ser.write" (note: the function ".to bytes" only works in Python3 - not in Python version 2).

Reading serial data is done by: 
if (ser.in_waiting > 0):
    b = ser.read(1)
    num = int.from_bytes(b, byteorder='big')
Initially the data is read as the character (string) variable "b" which is then converted into the corresponding integer value using the function "int.from_bytes()" (again, the latter only works in Python3 - not in Python version 2).
As already noted for the Arduino, the code for reading a byte must be called continuously.

Example of Arduino-RPi Bidirectional Communication

Here is a complete example for the bidirectional communication between the Arduino and the Raspberry Pi. 
This is what the example code does
  • Twice per second, the Arduino sends a random number between 0 and 49
  • The Raspberry Pi reads the number
  • When the received number is less than 10 the RPi sends a 10 to the Arduino
  • When the received number is larger than 40, the RPi sends a 40 to the Arduino
  • If the Arduino receives a 10, it turns the on-board LED on
  • If the Arduino receives a 40, it turns the on-board LED off

Arduino Example Code

The Arduino code consists of the standard "setup()", "loop()", plus two functions "sendRandom()" and "getSerial()". Continuously the main "loop()" loops over the "sendRandom()" and "getSerial()" functions. The "sendRandom()"routine keeps track of the time and sends every 500 milliseconds a random number to the RPi. The "getSerial()" checks continuously if new data has arrived from the RPi. The additional code in the "loop()" checks if the received data was either 10 or 40 and, correspondingly, turns the on-board LED on or off. Further documentation/explanations are in the code.
// ===========================================
void setup(){
  Serial.begin(9600); delay(2000); // wait 2 seconds
}

// ===========================================
void loop(){
  byte n = 0; sendRandom(); // call function to send a random number
  n = getSerial();
  if (n == 10) {
    digitalWrite(LED_BUILTIN, HIGH);
  } else if (n == 40) {
    digitalWrite(LED_BUILTIN, LOW);
  }

} 
// ------------------------------------------------
// --- twice per second, send a byte in range 0-49
// ---           via serial to RPi
// ------------------------------------------------
void sendRandom() { 
  static unsigned long previousTime = 0;
  byte n = 0;  
  if ( (millis() - previousTime) > 500) {       // repeat every 500 ms
    previousTime = millis();
    n = random(50);    // random number between 0 and 49
    Serial.write(n);
  } 
}
// -----------------------------------------------------------
// --- read a byte from RPi serial 
//     returns zero if nothing was received
// -----------------------------------------------------------
byte getSerial() {
  byte n = 0;
  if (Serial.available() > 0) {
    int var = Serial.read();    // data type: int
    n = var;        
  }  
  return (n);
}
// ===========================================================
// ===          end of Arduino code
// ===========================================================

Raspberry Pi Example Code

And here is the Raspberry Pi Python code (as stated above, it requires Python 3). It imports the module "serial". The code starts the serial connection. Then it starts an infinite loop during which it continues to check if serial data is available. If yes, it reads the data, and if the data is <10 or >40, returns a value of 10 or 40 to the Arduino. 
Note: when the Python code initializes the serial connection (in the command: "serial.Serial('/dev/ttyUSB0', 9600, timeout=1)"), this is resetting the Arduino. So, when you turn on the Raspberry Pi, it powers the Arduino, which is starting to run. When the RPi has finished booting and you start the Python program, this is restarting the Arduino again. Depending on the application, this needs to be taken into account in the Arduino code.

Save the code in a file e.g. as "serial-test.py" and run it from the command line as
python3 serial-test.py 

And here starts the code:

import serial
print("  ============================================")
print("       ---> Serial Python code")
# --- start serial      -->> check port number !!!
ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
# ----------------------------------------------
# ---        infinite loop 
# ----------------------------------------------
while True:
  # --- read Serial - display faces & play audio
  if (ser.in_waiting > 0):
    a = ser.read(1)
    num = int.from_bytes(a, byteorder='big')
    print(" received from serial:")
    print(num)
    if num < 10:
      x = 10
      ser.write(x.to_bytes(1, 'little'))
      print("                     turn LED on")
    elif num > 40:
      x = 40
      ser.write(x.to_bytes(1, 'little'))
      print("                     turn LED     off")
# --------------------------------------------- # ---       end of Python code # ---------------------------------------------

When I was looking for help with the serial communication between the Arduino and Raspberry Pi, I had to combine many bits and pieces that I found at different places. I hope that this example includes all aspects that get you up and running with your projects!
Please let me know if it worked for you - or if not.  

No comments: