In this post, we will explore the process of establishing wireless communication between an Arduino-based IMU sensor and a Python application using Bluetooth Low Energy (BLE). The use of BLE allows us to transfer data wirelessly between the sensor and the computer, making it easy to collect, store, and analyze IMU sensor data.
We will start by setting up the IMU sensor with Arduino, followed by a step-by-step guide to connecting it to a Python application using the Bleak library. The Arduino code is the foundation of the wireless communication, and it sets up the IMU sensor, declares the BLE service and characteristics, and continuously sends accelerometer and gyroscope data to the connected BLE central device.
To make the process as straightforward as possible, we will be using the Arduino Nano BLE 33 Sense for this demonstration. This board combines the functionality of the Arduino Nano with a BLE module, making it an ideal choice for projects that require both a microcontroller and wireless connectivity.
By the end of this post, you will have a better understanding of how to interface IMU sensors with Python and use BLE for wireless data transfer. Additionally, you will have a working example that you can use as a starting point for your own projects. So, let\’s dive into the code and get started!
Arduino code
In this section, I will demonstrate how to set up a system where the acceleration and gyroscope data read by the Arduino is rapidly transmitted to a script in Python.
In the Arduino code, the first step is to include the required libraries, which are the ArduinoBLE.h and Arduino_LSM9DS1.h libraries. The ArduinoBLE library is used to handle the BLE communication, while the Arduino_LSM9DS1 library is used to interface with the IMU sensor.
Next, the code declares a BLE service with a unique UUID of “1001”. This service acts as a container for the two BLE characteristics, which are the accelerometer and gyroscope data. The characteristics are declared using the BLECharacteristic class and are assigned UUIDs of “2001” and “2011” respectively. These UUIDs are used to identify the characteristics on the BLE peripheral.
In the setup() function, the code initializes the BLE module and the IMU sensor. If either the BLE module or the IMU sensor fails to initialize, an infinite loop is entered. The code then sets the device name and local name to “IMU”, adds the service to the BLE module, and sets the connection interval and connectable status of the BLE module. Finally, the BLE module is started advertising the BLE connection.
In the loop() function, the code continuously sends accelerometer and gyroscope data to the connected BLE central device. The code first gets the connected BLE central device, and if there is one, enters an infinite loop. Inside the loop, the code reads the accelerometer and gyroscope data from the IMU sensor, creates strings with the data, and writes the strings to the corresponding BLE characteristics. The loop then waits 7 milliseconds before sending the next data.
Here is the code for the Arduino Nano BLE 33 Sense:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
#include <ArduinoBLE.h> #include <Arduino_LSM9DS1.h> // Declare a BLE service with UUID "1001" BLEService IMUservice("1001"); // Declare two BLE characteristics for the accelerometer and gyroscope data, both with UUIDs "2001" and "2011" respectively. // The characteristics are readable and notify, and have a maximum data length of 20 bytes. BLECharacteristic accelData("2001", BLERead | BLENotify, 20); BLECharacteristic gyroData("2011", BLERead | BLENotify, 20); void setup() { // Initialize the BLE module if (!BLE.begin()) { // If the BLE module fails to initialize, enter an infinite loop while (1) { } } // Initialize the IMU if (!IMU.begin()) { // If the IMU fails to initialize, enter an infinite loop while (1) { } } // Set the device name and local name to "IMU" BLE.setDeviceName("IMU"); BLE.setLocalName("IMU"); // Add the service to the BLE module BLE.setAdvertisedService(IMUservice); // Add the two characteristics to the service IMUservice.addCharacteristic(accelData); IMUservice.addCharacteristic(gyroData); // Add the service to the BLE module BLE.addService(IMUservice); // Set the connection interval for the BLE connection BLE.setConnectionInterval(8, 8); // Enable the BLE module to be connectable BLE.setConnectable(true); // Start advertising the BLE connection BLE.advertise(); } void loop() { float acX, acY, acZ, gX, gY, gZ; // Get the connected BLE central device BLEDevice central = BLE.central(); if (central) { // If there is a connected BLE central device, enter an infinite loop while (central.connected()) { // Read the accelerometer data from the IMU device IMU.readAcceleration(acX, acY, acZ); // Create a string with the accelerometer data String accelString = String(acX) + "," + String(acY) + "," + String(acZ); // Write the accelerometer data to the BLE characteristic accelData.writeValue(accelString.c_str()); // Read the gyroscope data from the IMU device IMU.readGyroscope(gX, gY, gZ); // Create a string with the gyroscope data String gyroString = String(gX) + "," + String(gY) + "," + String(gZ); // Write the gyroscope data to the BLE characteristic gyroData.writeValue(gyroString.c_str()); // Wait 7 milliseconds before sending the next data delay(7); } } } |
The max sampling rate of the accelerometer and gyroscope in the Arduino Nano BLE 33 Sense is 104 Hz. This means that the device can send accelerometer and gyroscope data up to 104 times per second to the connected BLE central device.
By sending the data as fast as possible, the Python application can receive real-time sensor data, making it easier to analyze and process the information in real-time. This high sampling rate makes the Arduino Nano BLE 33 Sense an ideal device for a wide range of applications that require fast and reliable IMU data.
BLE scanner in Python
In this section, we will dive into the Python code and see how it connects to the IMU sensor using the BLE protocol. We will be using the Bleak library to handle the BLE communication, and the code will be written in Python 3.
To start, we will scan for available BLE devices and ensure that the IMU sensor is available. Once the IMU sensor is detected, we will use its address to establish a connection. Here is a code for that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import asyncio from bleak import discover # Asynchronous function to scan for BLE devices async def scan(): # Use the discover function from the bleak library to scan for devices devices = await discover() # Loop through the list of discovered devices for d in devices: # Print the information for each device print(d) # Get the event loop loop = asyncio.get_event_loop() # Run the asynchronous function to scan for devices loop.run_until_complete(scan()) |
This code uses the discover function from the bleak library to scan for BLE devices. The discover function returns a list of Device objects, which contain information about each device, including the device\’s name, address, and RSSI (received signal strength indicator).
In the scan function, the code loops through the list of discovered devices and prints the information for each device using the print function. Finally, the code gets the event loop and runs the scan function using loop.run_until_complete.
By using this code, you can easily check if your Arduino device is available and get its address, which is essential for establishing a BLE connection between the Python application and the Arduino. When I run this script, I get an output like this:
IMU is the name of the device that we specified in the Arduino code (BLE.setDeviceName(“IMU”)). We will need the address of our device to create a wireless channel between the Arduino and Python.
BLE is a widely-used technology, and as a result, many BLE devices can be detected in any given location. Depending on the environment, it is not uncommon to detect a few or even dozens of nearby BLE devices.
Real time wireless communication between Arduino and Python with BLE
Now that we have the BLE address of the Arduino Nano BLE 33 Sense, we can start the process of communication between the IMU sensor and the Python application.
In this section, we will go over the Python code that uses the Bleak library to connect to the BLE peripheral and receive real-time accelerometer and gyroscope data from the IMU sensor.
The code uses two functions, handle_accel_notification and handle_gyro_notification, to handle the accelerometer and gyroscope data received as notifications. The data is then processed and stored in a JSON file for further analysis.
We will also use an asynchronous function, run, to connect to the BLE peripheral and start receiving the notifications for the accelerometer and gyroscope data. The run function will continuously run the loop to receive real-time data from the IMU sensor.
Here is the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
import asyncio, time, json from bleak import BleakClient # Function to handle accelerometer data received as a notification def handle_accel_notification(sender, data): # Split the data into separate x, y, z values x, y, z = data.decode().split(',') acX = float(x) acY = float(y) acZ = float(z) # Get the current time timestamp = time.time() # Create a dictionary with the accelerometer data accel_data = {"acc_x": acX, "acc_y": acY, "acc_z": acZ, "Timestamp": timestamp} # Write the accelerometer data to a JSON file with open("data.json", "a") as json_file: json.dump(accel_data, json_file) json_file.write(',\n') # Function to handle gyroscope data received as a notification def handle_gyro_notification(sender, data): # Split the data into separate x, y, z values gx, gy, gz = data.decode().split(',') gX = float(gx) gY = float(gy) gZ = float(gz) # Get the current time timestamp = time.time() # Create a dictionary with the gyroscope data gyro_data = {"gy_x": gX, "gy_y": gY, "gy_z": gZ, "Timestamp": timestamp} # Write the gyroscope data to a JSON file with open("data.json", "a") as json_file: json.dump(gyro_data, json_file) json_file.write(',\n') # Asynchronous function to connect to the BLE peripheral async def run(address, loop): async with BleakClient(address, loop=loop) as client: # Start receiving notifications for the accelerometer data await client.start_notify("00002001-0000-1000-8000-00805f9b34fb", handle_accel_notification) # Start receiving notifications for the gyroscope data await client.start_notify("00002011-0000-1000-8000-00805f9b34fb", handle_gyro_notification) # Continuously run the loop while True: await asyncio.sleep(0.001) # Address of the BLE peripheral to connect to address = "00:00:00:00:00:00" # Get the event loop loop = asyncio.get_event_loop() # Run the asynchronous function loop.run_until_complete(run(address, loop)) |
The Python code uses the Bleak library to connect to the BLE peripheral and receive real-time data from the IMU sensor. The code starts by defining two functions to handle the accelerometer and gyroscope data received as notifications.
The handle_accel_notification function takes the sender and data as arguments and splits the data into separate x, y, z values. It then gets the current time, creates a dictionary with the accelerometer data, and writes the data to a JSON file.
The handle_gyro_notification function is similar to the handle_accel_notification function, but it handles the gyroscope data instead of the accelerometer data.
The run function is an asynchronous function that connects to the BLE peripheral and starts receiving notifications for the accelerometer and gyroscope data. The function uses the start_notify method of the BleakClient class to start receiving notifications for the BLE characteristics with UUIDs “2001” and “2011”.
The code then enters an infinite loop to continuously receive the data from the BLE peripheral. The loop is run using the run_until_complete method of the event loop. Finally, the code sets the address of the BLE peripheral to connect to and gets the event loop. The asynchronous function is then run using the run_until_complete method of the event loop.
In this way, the Python code connects to the BLE peripheral and receives real-time accelerometer and gyroscope data from the IMU sensor, making it easy to collect, store, and analyze IMU sensor data.
Results
The code above will save the data from the Arduin and store it in a JSON file. That file looks like this:
You can also print the variables accel_data and gyro_data if you want to see the data flow in real-time. In my experience with this algorithm, you can get 180 to 200 samples per second, with an equal distribution between acceleration and gyroscope data.
Conclusion
In conclusion, we have successfully demonstrated how to establish wireless communication between an Arduino-based IMU sensor and a Python application using Bluetooth Low Energy (BLE). By using the Arduino Nano BLE 33 Sense, we were able to send accelerometer and gyroscope data from the IMU sensor to the Python application in real-time, with a maximum sampling rate of 104 Hz.
We covered the process of setting up the IMU sensor with Arduino and connecting it to the Python application using the Bleak library. The Python code demonstrated how to scan for BLE devices, connect to the BLE peripheral, and receive real-time data from the IMU sensor as notifications.
This post serves as a starting point for a wide range of projects that require fast and reliable IMU data. By using BLE, it is possible to transfer data wirelessly between the sensor and the computer, making it easy to collect, store, and analyze IMU sensor data.
Thank you for taking the time to read this post. If you have any questions, comments, or feedback, please feel free to reach out.