Sunday, September 17, 2017

Connecting Arduino and Raspberry Pi with Bluetooth

Using the Serial Port Profile



Bluetooth has become a household word for connecting wireless peripheral devices to mobile phones, tablets, game consoles and computers. It's a standardized protocol operating in the 2.4GHz band with a power footprint that's ideal for short range/small data communication among electronic devices. It's a built-in feature in the Raspberry Pi 3 and affordable breadboard-ready modules are now widely available for DIY projects.

Bluetooth supports several sub-protocols, or profiles, that define how data is transmitted and implemented.

  • Headset Profile (HSP) connects wireless headsets and is widely used in mobile phones.
  • Human Interface Device (HID) for input devices such as mice, keyboards, and joysticks. 
  • Hands-free profile (HFP) is specific to car audio systems.
  • Advanced Audio Distribution Profile (A2DP) defines how high-fidelity audio is transmitted between devices.
  • The A/V Remote Control Profile (AVRCP) for device remote control and typically comes together with A2DP to allow integration of remote speakers.
  • Serial Port Profile (SPP) is a wireless replacement to the serial communication interface.

SPP was the intended profile when Bluetooth was originally designed.  It's the simplest to implement and effective in sending quick data streams between two devices. This post explores the basics of implementing SPP for Raspberry Pi and Arduino projects. The examples can be extended to other AVRs and computer platforms. The Bluetooth Serial Port Profile is practically a serial communication interface without the wire. So it's useful to first looked into wired serial communications.

Serial Basics

There are countless protocols for interconnecting electronic devices but they all fall into two general categories: parallel and serial. Parallel interfaces transfer multiple bits simultaneously over several wires. Serial interfaces transfer data as one bit at a time, typically over one or up to four wires only.


Between the these two general protocols, Serial interfaces are more often used due to its simplicity. Although parallel interfaces have several advantages, speed and being less error-prone among them, processing data one bit at a time is relatively easier, and less expensive, both from a hardware circuit and software programming perspective.

There has been several serial protocol implementations designed for specific needs, the ubiquitous USB (universal serial bus) and Ethernet are the most commonly used for networking devices. Serial protocols can be further broken down to two types:
  • Synchronous: includes a clock signal with the data line to synchronized the transmitting and receiving devices. This is typically faster and less prone to errors. However, it requires an extra wire and a hardware clock.
  • Asynchronous: data transmission is not synchronized by a hardware clock. It requires less wires but bit-level error-correction measures is necessary at the software layer to assure reliable data transfer.
For closer look into the Serial protocol , this article in Spark Fun breaks dow serial communication to the bit and signaling level. In the meantime let's get a taste of serial in action.

Serial Communications between Arduino and Raspberry Pi

Connect an Arduino Uno to a Raspberry Pi with a USB cable and upload the following Sketches code from the IDE (an Arduino IDE is available for Raspian):

Code:
serial_send.ino
 
 //send numbers 0 to 255 to the Serial port  
 //Context: Arduino  
 //
  
 void setup() {  
   
 //open serial connection at 9600  
 //  
 Serial.begin(9600);  
    }  
   
 //start program main loop  
 void loop() {  
   
  //count from 0 to 25  
  for (byte n = 0; n < 255; n++) {  
   
  //send current number to the serial port  
  Serial.print(n);  
 // pause  
  delay(50);  
      }  
    }  

Open the Serial Monitor on the IDE and we will see a stream of numbers from 0 - 255.

Sketches has a built in library for managing serial communications. We initialize the program by opening a serial connection with Serial.begin() and passing a baud rate of 9600. Baud rate is the speed of data transfer measured in bits-per-second. It can be set to any value as long as both the transmitting and receiving device operates at the same rate. Higher baud rates are, of course, faster but prone to errors, slower baud rates allows more room for error-checking and correction. The typical sweet spot is 9600. The program goes through a loop that counts from 0-255 and sends it to the serial port by calling Serial.print() and passing the value of n.

This program sends, via the Serial library, an asynchronous data transfer to the Pi using TTL(Transistor to Transistor Logic), the most basic serial protocol and which is often simply called 'serial'. This goes through a USB-Serial converter and sends it down the USB line, on the receiving end it also goes through another USB-Serial converter before it's passed on to the receiving application. Of course, this process is handled by the Serial library of each respective platform and is completely transparent. All we need to do is pass the data to the library serial function, in this case Serial.print().

On the Pi side we write a Python program to listen to the incoming data on the serial port. We will use the PySerial module to communicate with a serial port using python. Install as follows:

 python -m pip install pyserial  

Here's Python code for the listener on the Pi:

Code:
serial_listener.py
 #import PySerial module  
 #wait for data from serial port and print on console
 #Context: Raspberry Pi
 
 import serial  
   
 #assign serial port address  
 port = "/dev/ttyACM0"  
   
 #open serial port at 9600 baud rate and create serial communication object  
 serialData = serial.Serial(port,9600)  
   
 #main function  
 def run():  
   
    while True:  
   
      #wait for incoming data from the serial port  
      if (serialData.inWaiting() > 0):  
   
         #read incoming data  
         input = serialData.read()  
   
         #print incoming data  
         print input  
   
 run()  

The first part of the code imports the PySerial module and assigns the serial port address where the Arduino is connected to the variable port. Ports are virtual references to physical device connections. In this case, it's the same connection where we upload the Sketches from the Arduino IDE. The address can be found by going to Tools->Port on the IDE. Next we open a connection to the port by calling the serial.Serial() function, passing the port address and the baud rate, assigning it to a connection object SerialData.  In the main program loop it waits for incoming data from the port. When data is available it reads the data and prints it to the console. Run the code and the numbers 0 to 255 with start streaming on the console just like in the IDE Serial Monitor.

The following code demonstrates sending and receiving data, to and from the Arduino via serial:

Code:
serial_echo.ino
 //Read data from the serial port and send the same data back to the serial port.  
 //Context: Arduino  
   
 // variable for data received from serial connection  
 int inByte;  
   
   
 void setup() {  
   
  // open serial connection  
  Serial.begin(9600);  
    
  }  
   
 void loop() {  
   
  // wait for incoming data from the serial port  
  if (Serial.available() > 0) {  
   
    // read incoming data  
    int inByte = Serial.read();  
   
    //send data back to serial port  
    Serial.print(inByte);  
    delay(10);  
   
    }  
    
  }  

The value of Serial.avialable() is greater than 0 if there is data available in the serial port. If this is true, the program reads the data with Serial.read(), and sends the value back to the serial port. We paused the program briefly to give time for the connection to clear.

Code:
serial_echo.py
#Context: Raspberry Pi  
   
import time #import time module  
import serial #import serial module  
   
#assign serial port address  
port = "/dev/ttyACM0"  
   
#open serial port at 9600 baud rate and create serial communication object  
serialData = serial.Serial(port,9600)  
   
def run():
 while True:
  #wait for console input  
  send = raw_input("Enter a character: ")  

  # send input to serial connection  
  serialData.write(send)
  #print input to screen  
  print "Sent from Raspberry Pi " + send
  time.sleep(1)


  #wait for incoming data from serial
  if (serialData.inWaiting() > 0):

   #read the data on the serial port
   input = serialData.read(1)

   #print the data to the console screen
   print "Sent from Arduino: " + input  
   
run()  
   
   
   

On the Pi side, the program waits for a character input, prints it on the console before sending it to the the serial using serialData.write(). It then waits for a response from the serial port, reads it and and again prints it to the console.

Run the the program and enter a character when prompted. The same character will be printed back to the console from the Arduino.

Going Wireless

A Bluetooth network consists of two or more devices connected through a master/slave model commonly known as a piconet. A master (M) device can interconnect up to seven active slave (S) devices simultaneously. Up to 255 slave devices can stay on the network inactive, or parked. The master device can bring slave devices into active status at any time as long as the device is parked first.


Bluetooth Piconet
(Credit: Wikimedia Commons)
For the examples that follow, we will set-up the Raspberry Pi as the master device, and use an HC-05 Bluetooth module to connect the Arduino as a slave. Other Bluetooth modules, such as the popular Bluetooth Mate, may also be used. The HC-05 can be configured as a master or a slave device. For now, we will use it as a slave, which is the default mode.

HC-05 Bluetooth Module
Credit: Art of Circuits

This circuit shows how to connect the HC-05 to an Arduino Uno. We will only be concerned with the following four pins in the middle:

- VCC: 5.0 v power
- GND: Ground
- RXD: Serial Data Receive
- TXD: Serial Data Transmit

Parts List

1 Arduino Uno
1 Bluetooth HC-05
1 1kΩ Resistor
1 2kΩ Resistor

From the HC-05, connect the VCC pin with the 5V power pin, and GND pin to one of the GND pins on the Arduino respectively. The Arduino serial transmit pin (TX)  should connect to the serial receive (RXD) on the HC-05, while the Arduino serial receive (RX) connects to the serial transmit (TXD) pin on the HC-05. . However, the HC-05 serial pins operate at 3.3V while the Arduino pins operate at 5V, so we need to put a voltage divider, as shown, between the connection. In this case, we step down the voltage by getting power between a series 1k and 2k resistors. Other modules such as the Bluetooth Mate operate with 5V serial pins so this step may not be necessary. Check the docs or Google to be sure. The HC-05 TXD pin can connect directly with the Arduino RX pin.  The EN pin is for activating AT command mode to configure the module which we won't be doing here. The STATE pin indicates if the module is connected to a network (HIGH when connected and LOW when not) should that be needed.

Once the circuit is ready Power up the Ardunio by connecting the USB cable to the Pi.  The light on the HC-05 should start flashing rapidly indicating that it is on but not connected.

Setting up the Pi

Getting the built-in Bluetooth up and running on Raspian is not as straightforward. Unlike Wifi, Bluetooth is not automatically configured and my attempts via the the desktop has been so far unsuccessful. Hopefully, this should be fixed on the subsequent release of Raspian. For now, we will configure it the old fashioned way, on the command line. First we need to install Bluez, the official Linux Bluetooth protocol stack, as follows:

sudo apt-get install bluetooth bluez blueman  
sudo reboot     

There's a lot to be explored in Bluez and the documentation is not really that extensive, so we will just focus on getting a serial port connected to the Bluetooth interface so we can communicate with Arduino.

Addressing

Each Bluetooth device has a unique 48-bit address  known as a BD_ADDR, represented as 12-digit hex value. The first (24 bits) is the Organization Unique Identifier (OUI) of the manufacturer, and the last 24-bits is the device unique address. A user-friendly name of up to 24 bytes is also assigned each device for easy identification. The name is not unique and can be shared among devices. Usually it can be changed.

Connecting and Pairing

There are three things that need happen for Bluetooth devices to connect with each other:

  1. Inquiry – Before any connection can be made, a device needs to see if other devices are within range by sending a discovery request. A device that is set as discoverable responds with it's address, and sometimes the user-friendly name. Devices with discoverable flag turned off will not respond and be invisible.
  2. Paging  – After device discovers other devices withing range during inquiry, it needs to initiate a connection request to a specific device and proceed with an authentication process.
  3. Connection – The two devices establishes an active connection, or the slave device is set to either sniff mode where it waits for a connection at specified intervals, hold mode where it sleeps for a specified period of time, or in park mode where it stays inactive until the master activates it.
Once a connection is made the two devices needs to bind so they automatically connect each time they are both within range. Devices are bonded by a pairing process where they respectively share and store profiles, addresses, and a common secret key that is used each time they connect. Pairing usually requires a one-time authentication that is validated on either side of the connection. The authentication process varies for each device pairing. It can involves entering matching PIN codes or simply acknowledging that the connection is valid.

Now we will go through the process using bluetoothctl the command line interface to Bluez. Run the following on the command line:

 sudo bluetoothctl  

It would first list the address of the controlling device and all discovered, paired devices with the corresponding friendly names, and the command interface. In this case there are none of those  devices:



Entering 'help' brings out the help menu. Again, there's not a lot of documentation available on most commands and a detailed exploration deserves a separate post. We will focus on what we need to connect to the Arduino.


'list' shows a list of available controller devices on the host. It should show at least one, unless there's a dongle or other Bluetooth devices attached the Pi. If there are other controllers the current active device will be labeled with 'default'.

'devices' shows all available devices. 'paired-devices' will show a list of remote devices that have been previously paired. The controller can also be set to scan mode by entering 'scan on'.  This will keep on checking for devices within range. It will show the list of discovered devices with the corresponding address. Some will show friendly names or Relative Signal Strentgh Indicator (RRSI). If the HC-05 is on it should show up with the 'devices' command since it's set to discoverable and pairable by default. In the example below, it has a device address of 98:D3:33:81:06:FB with the HC-05 friendly name.



To pair the HC-05 with the Pi enter agent on and default-agent. This sets a pairing manager that will handle the pairing process. Enter pair xx:xx:xx:xx:xx:xx where xx:xx:xx:xx:xx:xx is the address of the HC-05 device as shown by the either a 'scan' or 'devices' command. The agent will go through the authentication process and request for the PIN code. The default PIN of the HC-05 is 1234. Before we connect we need to enter trust xx:xx:xx:xx:xx:xx. Check for pairing by entering paired-devices



Now that we have paired the HC-O5 with the Pi, the last step is to bind it to a serial port. Type 'exit' on the bluetoothctl command prompt and enter the following in the command line:

sudo rfcomm bind 0 xx:xx:xx:xx:xx:xx

xx:xx:xx:xx:xx:xx being the address of the HC-05. To automatically bind the device with a serial port on startup, add the edit /etc/rc.local and add the above command before exit 0 in the script.

RFCOMM is the is the Bluetooth protocol that emulates a serial connection over radio/wireless.  The above command will bind the paired device with an address of xx:xx:xx:xx:xx to /dev/rfcomm0 serial port. Several Bluetooth devices can be bound to different serial ports, which we will see later.

Now we use the Bluetooth serial port by changing the following line in serial_echo.py:

#assign serial port address
port = "/dev/ttyACM0"

To the following:

#assign serial port address
port = "/dev/rfcomm0"

Disconnect the Arduino from the Pi and power it with either a 9V battery or power supply. Run serial.py again on the Pi console, and wallah... it should run the same way but this time without the cable connection.

Getting Physical

Let's make something a bit more interesting than passing around numbers. Let's build two basic sensor/actuator circuits that we are all familiar with and extend it to a wireless Bluetooth network.

First assemble the sensor circuit below, with a push button and a potentiometer, to demonstrate digital and analog inputs respectively:



Parts
1 Arduino Uno
1 Bluetooth HC-05
1 10K Rotary Potentiometer
2 220Ω Resistor
1 1kΩ Resistor
1 2kΩ Resistor
1 Pushbutton Switch

The state of the button switch is read on Digital Pin 13 and the value of the potentiometer is read on Analog Pin 0 on the Arduino. The HC-05 is attached to TX/RX Serial pins of the the Arduino as demonstrated earlier.

Load the following Sketches code on the Arduino

Code:
sensor.ino
 // wait for a request from the serial port and send switch state or potentiometer value depending   
 // on th request parameter   
 // context: Arduino  
 //  
   
 //  
   
 //pin constants  
 const int DIGI_IN = 12;  
 const int DIGI_OUT = 13;  
   
 int inByte; //incoming byte from serial  
 int currentPin; //the pin requested from serial input  
 int state = 0; //state of digital sensor pin  
 int analogVal; //raw value of analog sensor pin  
 int analogMap; //analogVal map to 1 - 9 range  
   
 void setup(){  
   
  Serial.begin(9600);  
  pinMode(DIGI_IN,INPUT);  
  pinMode(ANA_IN,INPUT);  
   
  }  
   
 void loop() {  
  // check to see whether there is a byte available  
  // to read in the serial buffer:  
  if (Serial.available() > 0) {  
   
  // read incoming byte  
  inByte = Serial.read();  
   
  //respond to pin requested by serial input  
  if (inByte == 'a'){  
    state = digitalRead(DIGI_IN); //read digital  
      if (state == HIGH){  
         Serial.write('1'); //if button is on send out 1  
         }  
      else if (state == LOW) {  
         Serial.write('0'); //if button is low send out 0  
         }  
      }  
   
  if (inByte == 'b'){  
    analogVal = analogRead(ANA_IN); //read analog sensor  
    analogMap = map(analogVal,0,850,0,9);  
    Serial.print (analogMap);  
    }  
   }  
   

We implement a simple communication protocol that accepts a request from the serial port. If it receives an 'a' it returns the state of the button switch, or the value of the potentiometer of it receives a 'b', back to the serial port. We can test this by opening the Serial monitor on the Arduino IDE and sending an 'a' or 'b', and receive either the button state or the potentiometer value.

Now assemble the actuator circuit using another Arduino Uno below which is just two LEDs.


Parts
1 Arduino Uno
1 Bluetooth HC-05
1 Red LED
1 Yellow
2 220Ω Resistor
1 1kΩ Resistor
1 2kΩ Resistor

The red LED connects to Pin 12, and will turn on or off depending on the pin state, and the yellow LED brightness will adjust based on value of the PWM  Pin 13. Again we attach the HC-05 to TX/RX Serial pins of the the Arduino.

Load the following Sketches code on the Arduino:

Code:
actuator.no

 /* wait for a request from the serial port and send switch state or potentiometer value depending   
  on the request parameter   
  waits for incoming message from the serial port and handles the request for an actuator action,  
  request protocol: actuator code|value (no delimiter)  
  context: Arduino  
   
  actuator codes:  
  c = digital led  
  d = analog led  
  value:  
  0 or 1 for digital  
  0 to 9 for analog  
   
 */  
   
 //set pin Constants  
 const int DIGI_OUT = 13; //digital LED  
 const int ANA_OUT = 11; // analog LED  
   
 int inByte; //incoming byte from serial  
 int currentPin; //the pin requested from serial input  
 int state = 0; //state of digital sensor pin  
 int output  
 
   
 void setup(){  
   
  Serial.begin(9600);  
  pinMode(DIGI_OUT,OUTPUT);  
  pinMode(ANA_OUT,OUTPUT);  
   
  }  
   
 void loop() {  
   
  // check to see whether there is a byte available  
  // to read in the serial buffer:  
  if (Serial.available() > 0) {  
   
  // read incoming byte  
  inByte = Serial.read();  
   
  //respond to pin requsted by serial input  
  if (inByte == 'c'){  
    currentPin = DIGI_OUT;  
    }  
   
  if (inByte == 'd'){  
    currentPin = ANA_OUT;  
    }  
   
  if ((inByte >= '0' && inByte <= '1') && currentPin == DIGI_OUT) {  
   
    if (inByte == '1') {  
      digitalWrite(currentPin,HIGH);  
      }  
    else if (inByte == '0') {  
      digitalWrite(currentPin,LOW);  
      }  
    }  
   
  if ((inByte >= '0' && inByte <= '9') && currentPin == ANA_OUT){  
    output = map(inByte,'0','9',0,255);  
    analogWrite(currentPin, output);  
    }  
  }  
 }    

Here we implement a slightly extended  communication protocol that accepts two character request from the Serial port. The first character represents an LED and the second represents a state or a value. If it receives a c, it will set the LED to the state represented by the second character. If it receives a d it will set the brightness of the Yellow LED based on the value represented by the second character.

Now we will light up the LEDs with the button switch or the potentiometer of the the sensor circuit remotely using the Raspberry Pi to manage communication between the two devices. Pair the two Bluetooth devices with the Raspberry Pi and assign the sensor device to /dev/rfcomm0 and the actuator device to /dev/rfcomm1:

First we create a configuration file to store both ports:

Code:
settings.py
 class Settings():  
 """Class to store all program related settings"""  
   
    def __init__(self):  
      self.port_0 = "/dev/rfcomm0" #arduino 1 serial port  
      self.port_1 = "/dev/rfcomm1" #arduino 2 serial port  
   

The main program code are as follows:

Code:
loop.py
 
 #read incoming data from serial port 1 and send it to serial port 2
 #context: raspberry pi
 from settings import Settings  
 import time  
 import serial  
 com_settings = Settings()  
 serial_0 = serial.Serial(com_settings.port_0)  
 serial_1 = serial.Serial(com_settings.port_1)  
 def run():  
      digital_in = '0'  
      analog_in = '0'  
      analog_out = ''  
      #program loop  
      while True:  
           #send request for digital pin value  
           serial_1.write('a')  
           #wait for respponse  
           if (serial_1.inWaiting() > 0):  
                digital_in = serial_1.read() #read response  
                print "digital: " + digital_in #test print  
                #handle response  
                #  
                #send digital value  
                serial_0.reset_output_buffer()  
                if (digital_in == '1'):  
                     serial_0.write('c1');  
                elif (digital_in == '0'):  
                     serial_0.write('c0');  
                     #send request for analog value  
           serial_1.write('b')  
           #wait for respponse  
           if (serial_1.inWaiting() > 0):  
                analog_in = serial_1.read() #read response  
                print "analog: " + analog_in #test print  
                #handle response  
                #  
                #clear serial buffer  
                serial_0.reset_output_buffer()  
                #send analog value  
                analog_out = 'd' + str(analog_in)  
                serial_0.write(analog_out)            
 run()  

First, we import the Settings class.  We create serial objects serial_0 and serial_1, using these attributes. We first read the button state from the sensor device through serial_0, and pass the state to the actuator device via serial_1 to turn the red LED on or off. Then we read the value of the potentiometer from the sensor device, and pass the value to the actuator device via serial_1 to adjust the brightness of the LED.

Turn on the two Bluetooth devices and run the script. The red LED should turn and off when you press and release the button, and the Yellow LED should adjust it's brightness as you turn the potentiometer.

Serial over Bluetooth is a quick way of adding remote control devices to the Raspberry Pi using an Arduino. Although it does not pack enough range and transfer speed for sensor networks, it's a good choice for short range/low power/small data transfer required for most wireless peripherals. Hopefully the examples above can get you started. If you have anything to share, I would be stoke to read it at the comments section below.