Connecting Nano 33 BLE Devices over Bluetooth®

Learn about the history of Bluetooth®, how BLE works and how to connect two Nano BLE devices over Bluetooth®.

Introduction

In this tutorial, we will learn how to exchange information between two Arduino boards, the Nano 33 BLE and the Nano 33 BLE Sense, through Bluetooth® Low Energy. For this, we will be using the ArduinoBLE library.

Goals

  • Learn Bluetooth® Low Energy fundamentals.
  • Use the ArduinoBLE library.
  • Exchange information between two Arduino boards through Bluetooth® Low Energy.

Hardware & Software Needed

History of Bluetooth®

The Bluetooth® standard was originally conceived by Dr. Jaarp Haartsen at Ericsson in 1994, more than 20 years ago. It was named after a renowned Viking and king who united Denmark and Norway in the 10th century, King Harald Gormsson. Dr. Haartsen was appointed to develop a short-range wireless connection standard that could replace the RS-232 standard, a wired telecommunications standard that was conceived in the 60s and that is still used nowadays.

Bluetooth® uses what is known as short-link radio technology. It operates at the unlicensed but regulated, 2.4 to 2.485GHz band and it uses radios to communicate and establish connections between two or more devices. Bluetooth® is based on the frequency-hopping spread spectrum method, this method was first described in the 1940s by the actress Hedy Lamarr and the composer George Antheil. Lamarr and Antheil wished to create a way to prevent torpedoes guided by radio to be jammed. Bluetooth® is, in essence, a short-range wireless network called a piconet.

In 1994, besides Ericsson, companies like Intel, Nokia, IBM, and Toshiba also had the idea of a short-range wireless link between electronic devices. What these companies understood at that time was that to create a short-range wireless link that could be used across different electronic devices, a protocol had to be standardized so that it could be universally applied. In 1996, those companies formed the Bluetooth® Special Interest Group (SIG) and it was finally established in 1998. SIG started with just 5 members, by the end of its first year it reached 4,000 members and nowadays it has more than 30,000.

Bluetooth®'s goal is to unite devices just like King Harald Gormsson united the tribes of Denmark into a single kingdom.

Bluetooth® 1.0 was released around 1999, version 2.0 in 2004, version 2.1 in 2007, version 3.0 in 2009, version 4.0 in 2010, version 4.1 in 2013, version 4.2 in 2014, version 5.0 in 2016 and version 5.1 on 2019 (that's a lot of work!).

If you look up the Bluetooth® 3.0 specification, you will find that this specification includes three working modes: BR, EDR, and HS (AMP). These three working modes are what people usually, for convenience, call classic Bluetooth®. In 2010, SIG merged with Wibree, a wireless technology developed by Nokia, Nordic Semiconductor, and other companies whose objective was to find a low-power wireless communication technology for electronics devices. SIG renamed Wibree as Bluetooth® Low Energy (also referred to as Bluetooth® LE or BLE). BLE was designed to reduce, significantly, the power consumption by reducing the amount of time that the Bluetooth® radio is on. Classic Bluetooth® and BLE are both included since the Bluetooth® 4.0 specification, but here's the thing: Classic Bluetooth® and BLE work differently, and they are not compatible.

Each mode, classic Bluetooth®, and BLE have different physical layer modulation and demodulation methods. This means that classic Bluetooth® and BLE cannot work with each other. Generally speaking, classic Bluetooth® is mainly used for audio applications (wireless headphones, for example) while BLE is more often seen in power-constrained applications (such as wearables and IoT devices, for example).

How Does BLE Work?

To understand how does BLE works, we need to talk about the roles and responsibilities of two devices that are connected through Bluetooth®. In any Bluetooth® connection, two roles that are being played: the central and peripheral roles. Devices with a central role are also call servers while devices with a peripheral role are also called clients.

Central and peripheral roles in Bluetooth® applications.
Central and peripheral roles in Bluetooth® applications.

When a Bluetooth® connection is established, one device, the peripheral, will advertise or broadcast information about itself to any near devices. At the same time, another device, the central, will be performing a scan and will be listening for any device or devices that are broadcasting information. As soon as the central device picks up the advertising information from the peripheral device, an attempt to connect the peripheral device will be made. Once a connection is established, the central device will interact with the available information that the peripheral device has. This information exchange is made using, what is known as, services.

Services and Characteristics

A service is a group of capabilities. For example, a smartwatch can measure your heart rate, track your physical activity through the day and track your sleep patterns. These three capabilities, for example, would exist in a service called health service. By grouping capabilities in services, central devices allow peripheral devices to quickly find, select and interact with the desired services they want. Any service has a unique identification code called UUID. This code can be 16-bit or 32-bit long for official Bluetooth® specification services while non-official Bluetooth® services (the ones we can develop) are 128-bit long, UUIDs can be created randomly. A profile is a group of services.

Within each service will exist a list of characteristics. Each one of these characteristics represents a unique capability of the central device. In the previous example, the health service would have three characteristics (heart rate, physical activity, and sleep pattern). Once the peripheral device discovers these characteristics, it can write information to, request information from, and subscribe to updates from these characteristics. Any characteristic, like the services, have a 16 bit long or 128 bit long UUID.

Health service example.
Health service example.

Information Exchange in BLE

There are three ways data can be exchanged between two connected devices: reading, writing, or notifying. Reading occurs when a peripheral device asks the central device for specific information, think about a smartphone asking a smartwatch for the physical activity information, this is an example of reading. Writing occurs when a peripheral device writes specific information in the central device, think about a smartphone changing the password of a smartwatch, this is an example of writing. Notifying occurs when a central device offers information to the peripheral device using a notification, think about a smartwatch notifying a smartphone its battery is low and needs to be recharged.

Well, that's what we need to know about BLE for now. Bluetooth® specifications are quite extensive but interesting to read and learn about. If you want to know more about BLE, check out Getting Started with Bluetooth® Low Energy by Kevin Townsend, Carles Cufí, Akiba, and Robert Davidson.

Using BLE and Arduino

Now, let's use BLE with Arduino. In this example, we are going to use two Arduino boards, the Nano 33 BLE and the Nano 33 BLE Sense to exchange information between them. One of the boards, the Nano 33 BLE Sense, is going to be set up as a central device while the other board, the Nano 33 BLE, is going to be set up as a peripheral device. The information that we are going to share between the boards will come from the embedded gesture sensor of the Nano 33 BLE Sense board. For this, we are going to create a service called gestureService that will have one characteristic called gesture_type.

Gesture example architecture.
Gesture example architecture.

The central device, the Nano 33 BLE Sense, is going to connect to the peripheral device, the Nano 33 BLE, and will look for the service called gestureService. Once a connection is established between the central and the peripheral device, if the central device detects a gesture with its gesture sensor, it will write the type of the gesture detected in the gesture_type characteristic of the gestureService. Then, based on the value stored in the gesture_type characteristic, the built-in RGB LED of the peripheral device, the Nano 33 BLE, will turn on a specific color depending on the stored value in the gesture_type characteristic.

Programming the Boards

1. First, let's make sure we have the drivers for the Nano 33 BLE boards installed. If we are using the online IDE, there is no need to install anything, if you are using the offline IDE, we need to install it manually. This can be done by navigating to Tools > Board > Board Manager..., search for Arduino Mbed OS Nano Boards, and install it.

2. Also, let's make sure we have all the libraries we need installed. If we are using the online IDE, there is no need to install anything. If we are using the offline IDE, this can be done by navigating to Tools > Manage libraries..., search for ArduinoBLE and Arduino_APDS9960, and install them both.

Programming the Central Device

The complete central device code can be found below:

1/*
2 BLE_Central_Device.ino
3
4 This program uses the ArduinoBLE library to set-up an Arduino Nano 33 BLE Sense
5 as a central device and looks for a specified service and characteristic in a
6 peripheral device. If the specified service and characteristic is found in a
7 peripheral device, the last detected value of the on-board gesture sensor of
8 the Nano 33 BLE Sense, the APDS9960, is written in the specified characteristic.
9
10 The circuit:
11 - Arduino Nano 33 BLE Sense.
12
13 This example code is in the public domain.
14*/
15
16#include <ArduinoBLE.h>
17#include <Arduino_APDS9960.h>
18
19const char* deviceServiceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214";
20const char* deviceServiceCharacteristicUuid = "19b10001-e8f2-537e-4f6c-d104768a1214";
21
22int gesture = -1;
23int oldGestureValue = -1;
24
25void setup() {
26 Serial.begin(9600);
27 while (!Serial);
28
29 if (!APDS.begin()) {
30 Serial.println("* Error initializing APDS9960 sensor!");
31 }
32
33 APDS.setGestureSensitivity(80);
34
35 if (!BLE.begin()) {
36 Serial.println("* Starting BLE module failed!");
37 while (1);
38 }
39
40 BLE.setLocalName("Nano 33 BLE (Central)");
41 BLE.advertise();
42
43 Serial.println("Arduino Nano 33 BLE Sense (Central Device)");
44 Serial.println(" ");
45}
46
47void loop() {
48 connectToPeripheral();
49}
50
51void connectToPeripheral(){
52 BLEDevice peripheral;
53
54 Serial.println("- Discovering peripheral device...");
55
56 do
57 {
58 BLE.scanForUuid(deviceServiceUuid);
59 peripheral = BLE.available();
60 } while (!peripheral);
61
62 if (peripheral) {
63 Serial.println("* Peripheral device found!");
64 Serial.print("* Device MAC address: ");
65 Serial.println(peripheral.address());
66 Serial.print("* Device name: ");
67 Serial.println(peripheral.localName());
68 Serial.print("* Advertised service UUID: ");
69 Serial.println(peripheral.advertisedServiceUuid());
70 Serial.println(" ");
71 BLE.stopScan();
72 controlPeripheral(peripheral);
73 }
74}
75
76void controlPeripheral(BLEDevice peripheral) {
77 Serial.println("- Connecting to peripheral device...");
78
79 if (peripheral.connect()) {
80 Serial.println("* Connected to peripheral device!");
81 Serial.println(" ");
82 } else {
83 Serial.println("* Connection to peripheral device failed!");
84 Serial.println(" ");
85 return;
86 }
87
88 Serial.println("- Discovering peripheral device attributes...");
89 if (peripheral.discoverAttributes()) {
90 Serial.println("* Peripheral device attributes discovered!");
91 Serial.println(" ");
92 } else {
93 Serial.println("* Peripheral device attributes discovery failed!");
94 Serial.println(" ");
95 peripheral.disconnect();
96 return;
97 }
98
99 BLECharacteristic gestureCharacteristic = peripheral.characteristic(deviceServiceCharacteristicUuid);
100
101 if (!gestureCharacteristic) {
102 Serial.println("* Peripheral device does not have gesture_type characteristic!");
103 peripheral.disconnect();
104 return;
105 } else if (!gestureCharacteristic.canWrite()) {
106 Serial.println("* Peripheral does not have a writable gesture_type characteristic!");
107 peripheral.disconnect();
108 return;
109 }
110
111 while (peripheral.connected()) {
112 gesture = gestureDetectection();
113
114 if (oldGestureValue != gesture) {
115 oldGestureValue = gesture;
116 Serial.print("* Writing value to gesture_type characteristic: ");
117 Serial.println(gesture);
118 gestureCharacteristic.writeValue((byte)gesture);
119 Serial.println("* Writing value to gesture_type characteristic done!");
120 Serial.println(" ");
121 }
122
123 }
124 Serial.println("- Peripheral device disconnected!");
125}
126
127int gestureDetectection() {
128 if (APDS.gestureAvailable()) {
129 gesture = APDS.readGesture();
130
131 switch (gesture) {
132 case GESTURE_UP:
133 Serial.println("- UP gesture detected");
134 break;
135 case GESTURE_DOWN:
136 Serial.println("- DOWN gesture detected");
137 break;
138 case GESTURE_LEFT:
139 Serial.println("- LEFT gesture detected");
140 break;
141 case GESTURE_RIGHT:
142 Serial.println("- RIGHT gesture detected");
143 break;
144 default:
145 Serial.println("- No gesture detected");
146 break;
147 }
148 }
149 return gesture;
150}

Programming the Peripheral Device

The complete peripheral device code can be found below:

1/*
2 BLE_Peripheral.ino
3
4 This program uses the ArduinoBLE library to set-up an Arduino Nano 33 BLE
5 as a peripheral device and specifies a service and a characteristic. Depending
6 of the value of the specified characteristic, an on-board LED gets on.
7
8 The circuit:
9 - Arduino Nano 33 BLE.
10
11 This example code is in the public domain.
12*/
13
14#include <ArduinoBLE.h>
15
16enum {
17 GESTURE_NONE = -1,
18 GESTURE_UP = 0,
19 GESTURE_DOWN = 1,
20 GESTURE_LEFT = 2,
21 GESTURE_RIGHT = 3
22};
23
24const char* deviceServiceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214";
25const char* deviceServiceCharacteristicUuid = "19b10001-e8f2-537e-4f6c-d104768a1214";
26
27int gesture = -1;
28
29BLEService gestureService(deviceServiceUuid);
30BLEByteCharacteristic gestureCharacteristic(deviceServiceCharacteristicUuid, BLERead | BLEWrite);
31
32
33void setup() {
34 Serial.begin(9600);
35 while (!Serial);
36
37 pinMode(LEDR, OUTPUT);
38 pinMode(LEDG, OUTPUT);
39 pinMode(LEDB, OUTPUT);
40 pinMode(LED_BUILTIN, OUTPUT);
41
42 digitalWrite(LEDR, HIGH);
43 digitalWrite(LEDG, HIGH);
44 digitalWrite(LEDB, HIGH);
45 digitalWrite(LED_BUILTIN, LOW);
46
47
48 if (!BLE.begin()) {
49 Serial.println("- Starting BLE module failed!");
50 while (1);
51 }
52
53 BLE.setLocalName("Arduino Nano 33 BLE (Peripheral)");
54 BLE.setAdvertisedService(gestureService);
55 gestureService.addCharacteristic(gestureCharacteristic);
56 BLE.addService(gestureService);
57 gestureCharacteristic.writeValue(-1);
58 BLE.advertise();
59
60 Serial.println("Nano 33 BLE (Peripheral Device)");
61 Serial.println(" ");
62}
63
64void loop() {
65 BLEDevice central = BLE.central();
66 Serial.println("- Discovering central device...");
67 delay(500);
68
69 if (central) {
70 Serial.println("* Connected to central device!");
71 Serial.print("* Device MAC address: ");
72 Serial.println(central.address());
73 Serial.println(" ");
74
75 while (central.connected()) {
76 if (gestureCharacteristic.written()) {
77 gesture = gestureCharacteristic.value();
78 writeGesture(gesture);
79 }
80 }
81
82 Serial.println("* Disconnected to central device!");
83 }
84}
85
86void writeGesture(int gesture) {
87 Serial.println("- Characteristic <gesture_type> has changed!");
88
89 switch (gesture) {
90 case GESTURE_UP:
91 Serial.println("* Actual value: UP (red LED on)");
92 Serial.println(" ");
93 digitalWrite(LEDR, LOW);
94 digitalWrite(LEDG, HIGH);
95 digitalWrite(LEDB, HIGH);
96 digitalWrite(LED_BUILTIN, LOW);
97 break;
98 case GESTURE_DOWN:
99 Serial.println("* Actual value: DOWN (green LED on)");
100 Serial.println(" ");
101 digitalWrite(LEDR, HIGH);
102 digitalWrite(LEDG, LOW);
103 digitalWrite(LEDB, HIGH);
104 digitalWrite(LED_BUILTIN, LOW);
105 break;
106 case GESTURE_LEFT:
107 Serial.println("* Actual value: LEFT (blue LED on)");
108 Serial.println(" ");
109 digitalWrite(LEDR, HIGH);
110 digitalWrite(LEDG, HIGH);
111 digitalWrite(LEDB, LOW);
112 digitalWrite(LED_BUILTIN, LOW);
113 break;
114 case GESTURE_RIGHT:
115 Serial.println("* Actual value: RIGHT (built-in LED on)");
116 Serial.println(" ");
117 digitalWrite(LEDR, HIGH);
118 digitalWrite(LEDG, HIGH);
119 digitalWrite(LEDB, HIGH);
120 digitalWrite(LED_BUILTIN, HIGH);
121 break;
122 default:
123 digitalWrite(LEDR, HIGH);
124 digitalWrite(LEDG, HIGH);
125 digitalWrite(LEDB, HIGH);
126 digitalWrite(LED_BUILTIN, LOW);
127 break;
128 }
129}

Testing the Code

Once we finished with the coding, we can upload both sketches to our boards. We can verify first that the central device (Arduino Nano 33 BLE Sense) is working as expected by selecting the right port of the central device and opening the Serial Monitor. In the Serial Monitor, we should see the following:

Serial monitor output of central device.
Serial monitor output of central device.

After turning on, the central device starts looking for a peripheral device with a specified service and characteristic. Once the peripheral device with the specified service and characteristic is found, in the Serial Monitor we should see the following:

Serial monitor output of peripheral device.
Serial monitor output of peripheral device.

The central device gives you feedback about:

  • The peripheral device MAC address.
  • The peripheral device local name.
  • The peripheral device advertised service UUID.

Then, the central device tries to establish a connection with the peripheral device and also tries to discover the service and the characteristic we specified before. If both things are made with success, then we can start triggering the onboard gesture sensor of the Nano 33 BLE Sense board. We begin by stabilizing our Nano 33 BLE Sense board on a standing position in our front with the USB port of the board facing down. Then, we carry on by making directional UP-DOWN-RIGHT-LEFT hand gestures. When a gesture is detected by the gesture sensor, in the Serial Monitor we should see the following:

Serial monitor output
Serial monitor output

The central device gives us feedback about:

  • The type of gesture detected (UP, DOWN, RIGHT, or LEFT).
  • The value written to the gesture_type characteristic of the gesture service in the peripheral device.

We should see also that an on-board LED in the peripheral device gets on depending on the detected gesture:

  • UP gesture detected: red LED on.
  • DOWN gesture detected: green LED on.
  • RIGHT gesture detected: built-in LED on.
  • LEFT gesture detected: blue LED on.

The peripheral device can also give us feedback through the serial port. We can verify with that feedback that the peripheral device (Arduino Nano 33 BLE) is working as expected by selecting the right port of the peripheral device and opening the Serial Monitor. In the Serial Monitor, we should see the following:

Serial monitor output
Serial monitor output

The peripheral device gives us feedback about:

  • The central device's MAC address.
  • The central device's local name.
  • The gesture_type characteristic actual value and the onboard LED that is on.

Troubleshoot

Sometimes errors occur, if one of the codes is not working there are some common issues we can troubleshoot:

  • Missing a bracket or a semicolon.
  • Arduino board connected to the wrong port.
  • Accidental interruption of cable connection.
  • We haven't opened the Serial Monitor to initialize the program.

Conclusion

In this tutorial, we have learned how to exchange information between two Arduino boards, the Nano 33 BLE and the Nano 33 BLE Sense, through Bluetooth® Low Energy. We also learned the basics of BLE, how does it works, what are services and characteristics, and how information is exchanged in BLE. Lastly, we turn on different colors of the on-board RGB LED of the Nano 33 BLE board based on the values sent from the Nano 33 BLE Sense, those values were defined using its onboard gesture sensor.

Contribute to Arduino

Join the community and suggest improvements to this article via GitHub. Make sure to read out contribution policy before making your pull request.

Missing something?

Check out our store and get what you need to follow this tutorial.