I tried BLE (Bluetooth Low Energy) communication between M5StickC and Raspberry Pi4
Contents
Advertising with BLE
I used the M5StickC and BME280 sensor module to measure temperature, humidity, and air pressure, and advertise (broadcast) the measurement results via BLE (Bluetooth Low Energy).
I made a program to scan BLE with a Raspy (RaspberryPi 4B) and receive the data asynchronously.
What is BLE?
BLE (Bluetooth Low Energy) is one of the extended specifications of Bluetooth that enables energy-saving wireless communication.
The first standard, Bluetooth 1.0, had a transmission range of several meters and was mainly used for communication with mice and PC peripherals.
Bluetooth 4.0 has a range of about 100 meters, and Bluetooth 5.0 is said to have a range of about 400 meters when the data rate is set to a low speed of 125 kbps.
The latest version, Bluetooth 5.1, has a new function to detect direction.
Bluetooth4.2 | Data rate: 1Mbps Reaching distance: several tens of meters to 100 meters Message capacity: 31Byte |
Bluetooth5.0 | Data rate: 2Mbps、1Mbps、500kbps、125kbps Reaching distance: 2-1Mbps 100m, 125kbps 400m Message capacity: 255Byte |
Bluetooth5.1 | This version has a function to detect the direction of the pairing partner. |
Supported Versions
The Bluetooth versions supported by each device are as follows.
Raspberry Pi Zero | Bluetooth 4.1 |
Raspberry Pi 3 Model B+ | Bluetooth4.2 |
Raspberry Pi 4 Model B | Bluetooth 5.0 |
M5StickC(ESP32-PICO-D4) | Bluetooth 4.2 |
This time, the communication between the M5StickC and the Raspberry Pi 4 B is supposed to be Bluetooth 4.2 standard.
The speed is slower than Wi-Fi, but it’s energy efficient, so I think it will be very useful for measuring temperature and humidity in places where power is not available.
The BLE is so energy efficient that it is said to be able to run for a year on a button battery.
Therefore, I think the lithium battery in the M5StickC can last for quite a long time depending on the programming.
Overall configuration diagram
The overall configuration diagram is as follows.

- Attach a temperature, humidity, and air pressure measurement sensor (BME280) to the HY2.0-4P terminal of the M5StickC
- Advertise (broadcast) the values acquired by the sensor via BLE for 10 seconds
- Scan the signal with BLE on Raspberry Pi 4 and display it on the console
- Data communication is asynchronous, not pairing.
Required equipment
The equipment we prepared is as follows.
M5StickC
M5StickC is used to connect temperature, humidity, and barometric pressure sensors and advertise them via BLE.
The chip (ESP32-PICO-D4) in the M5StickC can communicate via Wi-Fi as well as Bluetooth.
However, I tried to communicate via Bluetooth to save energy, assuming that we would be able to operate it in places where we could not secure a power supply.

Temperature, humidity, and air pressure sensors
Sensor module (BME280) for measuring temperature, humidity, and barometric pressure.
The power supply from the M5StickC is 5.0V, so I chose a type that has a voltage regulator and can use both 3.3V and 5.0V.
Connect it to the HY2.0-4P (Grove compatible terminal) of the M5StickC.
For more information on the BME280’s measurement range and error, please refer to the previous article.
The terminals are
- VIN (both 3.3 and 5.0V can be used)
- GND
- SCL
- SDA
It is connected to the M5StickC via I2C (Inter-Integrated Circuit) communication.

Grove general-purpose cable
This cable is used to connect the M5StickC’s HY2.0-4P (Grove compatible terminal) to the BME280.
This cable has Grove terminals on both ends, so the BME280 side will need to be modified.

Jumper wire set
The above Grove general-purpose cable has Grove terminals on both ends, so I changed the BME280 side to a 4-pin female jumper wire set.

Raspberry Pi 4B
I used a Raspberry Pi 4B that I had on hand, but I think a Raspberry Pi Zero would work just as well.

Tools used
The tools used were as follows
Soldering iron
A soldering iron was used to fix the connector pins to the temperature, humidity, and barometric pressure sensor module (BME280).

Solder
Any kind of leaded solder for electronic work is fine.

Stand loupe
The stand loupe makes it easier to work with.
The LED lights up your hand and the loupe magnifies it, so you can see better, and since the base is fixed, it is easier to solder.
I’m not good at soldering, so I need a stand loupe.

Electrician’s pliers
Electrician’s pliers
I used these to cut off one of the Grove compatible terminals and replace it with a 4-pin jumper wire.
Small electric pliers are easier to handle than large ones.

Assembly
Soldering the BME280
The BME280 module that I purchased had separate pins and modules as shown below.

Solder the pins to the module.

Making the connector
The Grove general-purpose cable I bought had Grove terminals on both ends, so I cut off one end and replaced it with a 4-pin jumper wire.

Wiring Diagram
The wiring diagram for the M5StickC and BME280 is shown below.

Building the Environment
Install the Arduino IDE, the development environment for the M5StickC, on your computer.
Also, install the necessary libraries and modules on the Raspberry Pi 4B in advance.
Arduino IDE
I installed the Arduino IDE (Integrated Development Environment) on my Windows 10 machine, which is my development environment.
For details on the installation procedure, please refer to the previous article.
The next step is to install the Adafruit BME280 library in the Arduino IDE, the development environment for the M5StickC.
Start the Arduino IDE and select Sketch, Include Library, Manage Libraries.

Search for ″BME280″ in the search field and install the “Adafruit BME280 Library” that appears.

I selected “install all”.

Raspberry Pi
Install bluepy, a module for controlling Bluetooth devices from Python, on the Raspberry Pi with the following command.
sudo pip3 install bluepy

Sketch (Program)
M5StickC
The M5StickC program for the side that measures and advertises (broadcasts) temperature, humidity, and barometric pressure is as follows.
It is coded in Arduino IDE (C++) with reference to Switch Science’s website.
source-code
ble_pub_en.ino
#include <M5StickC.h>
#include <BLEDevice.h> // Bluetooth Low Energy
#include <BLEServer.h> // Bluetooth Low Energy
#include <BLEUtils.h> // Bluetooth Low Energy
#include <esp_sleep.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#define T_PERIOD 10 // Number of seconds to send advertizing packets
#define S_PERIOD 20 // Number of seconds to Deep Sleep
RTC_DATA_ATTR static uint8_t seq; // Send SEQ
Adafruit_BME280 bme;
uint16_t temp; // Temperature
uint16_t humid; // Humidity
uint16_t press; // barometric pressure
uint16_t vbat; // Voltage
void setAdvData(BLEAdvertising *pAdvertising) { // Formatting Advertising Packets
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
oAdvertisementData.setFlags(0x06); // BR_EDR_NOT_SUPPORTED | General Discoverable Mode
// oAdvertisementData.setFlags(0x05); // BR_EDR_NOT_SUPPORTED | Limited Discoverable Mode
std::string strServiceData = "";
strServiceData += (char)0x0c; // Length(12Byte)
strServiceData += (char)0xff; // AD Type 0xFF: Manufacturer specific data
strServiceData += (char)0xff; // Test manufacture ID low byte
strServiceData += (char)0xff; // Test manufacture ID high byte
strServiceData += (char)seq; // sequence number
strServiceData += (char)(temp & 0xff); // Lower byte of temperature
strServiceData += (char)((temp >> 8) & 0xff); // Upper byte of temperature
strServiceData += (char)(humid & 0xff); // Lower byte of Humidity
strServiceData += (char)((humid >> 8) & 0xff); // Upper byte of Humidity
strServiceData += (char)(press & 0xff); // Lower byte of Pressure
strServiceData += (char)((press >> 8) & 0xff); // Upper byte of Pressure
strServiceData += (char)(vbat & 0xff); // Lower byte of Voltage
strServiceData += (char)((vbat >> 8) & 0xff); // Upper byte of Voltage
oAdvertisementData.addData(strServiceData);
pAdvertising->setAdvertisementData(oAdvertisementData);
}
void setup() {
M5.begin();
M5.Axp.ScreenBreath(10); // Reduce the brightness of the screen
M5.Lcd.setRotation(1); // Change the direction of the LCD
M5.Lcd.setTextSize(2); // Set font size to 2
M5.Lcd.setTextColor(WHITE, BLACK); // White for text, black for background
Wire.begin(); // I2C initialization
while (!bme.begin(0x76)) { // BMP280 initialization
M5.Lcd.println("BMP280 init failed");
}
temp = (uint16_t)(bme.readTemperature() * 100); // Obtain temperature (multiply by 100 and change decimal point to integer part)
humid = (uint16_t)(bme.readHumidity() * 100); // Acquire humidity (multiply by 100 and change decimal point to integer part)
press = (uint16_t)(bme.readPressure()/100); // Acquisition of air pressure (converted from pa to hPa)
vbat = (uint16_t)(M5.Axp.GetVbatData() * 1.1 / 1000 * 100); // Battery voltage's up 100 times.
M5.Lcd.setCursor(0, 0, 1); // cursor position
M5.Lcd.printf("temp: %4.1f'C\r\n", (float)temp / 100);
M5.Lcd.printf("humid:%4.1f%%\r\n", (float)humid / 100);
M5.Lcd.printf("press:%4.0fhPa\r\n", (float)press);
M5.Lcd.printf("vbat: %4.2fV\r\n", (float)vbat / 100);
BLEDevice::init("blepub-01"); // Initialize the device
BLEServer *pServer = BLEDevice::createServer(); // Create a server
BLEAdvertising *pAdvertising = pServer->getAdvertising(); // Get the advertised object.
setAdvData(pAdvertising); // Set the advertizing data.
pAdvertising->start();
delay(T_PERIOD * 1000); // Advertise T_PERIOD seconds
pAdvertising->stop();
seq++; // Count up the sequence number
delay(10);
esp_deep_sleep(1000000LL * S_PERIOD); // S_PERIOD seconds to Deep Sleep
}
void loop() {
}
Supplemental Information
Transmission and Deep Sleep
T_PERIOD specifies the number of seconds to advertise (broadcast) (10 seconds).
S_PERIOD specifies the number of seconds for DeepSleep (20 seconds).
In this program, it transmits for 10 seconds and sleeps for 20 seconds, but if you want to measure temperature, humidity, and air pressure at 5-minute intervals, you can use 4 minutes and 50 seconds (250 seconds).
ESP32 has the following 5 modes.
- Active mode:100mA~240mA
- Modem-sleep mode:20mA~25mA(Single-core)
- Light-sleep mode:0.8mA(800µA)
- Deep-sleep mode:10µA~150µA
- Hibernation mode:5µA
In Deep Sleep mode, the power consumption is quite low.
In this program, we use esp_deep_sleep() for Deep Sleep.
There are many examples on the Internet where you can specify the time to wakeup with esp_sleep_enable_timer_wakeup() and start Deep Sleep with esp_deep_sleep_start().
Since esp_deep_sleep() internally calls esp_sleep_enable_timer_wakeup() and esp_deep_sleep_start() in succession, the process is the same.
setAdvData
Assemble the data to be advertised.
Since the M5StickC is Bluetooth 4.2, the message size that can be sent is limited to 31Byte.
The layout of the data to be advertised is as follows (from Bluetooth.com)

- Length: Data length (1 octet)
- AD Type: AD Type + Company ID (3 octets)
- AD Data: SEQ, temperature, humidity, barometric pressure, voltage (9 octets)
It consists of the above.
0xff (AD Type) represents the manufacturer-specific data, and the following two octets (2Byte) represent the company ID.
For the company ID, I used a dummy value of 0xff×2, but if you want to use it in a production environment, you need to join the Bluetooth SIG (Special Interest Group) and apply for and obtain a company ID.
Little Endian
Temperature, humidity, and other values (two octets) are stored in little-endian format, with the upper and lower levels reversed.
M5.Lcd.printf
Lines 62-65 show the temperature, humidity, barometric pressure, and voltage on the M5StickC display so that you can check the values.
If you want to make the lithium battery last longer, you don’t need this display, for example, you can change it to show only when a button is pressed.
Raspberry Pi 4
The program for the Raspberry Pi 4B on the receiving end of the data is as follows.
directory structure
Created a directory under “.local”
├─$HOME
│ │
│ ├─.local
│ │ │
│ │ ├──ble_sub
│ │ │ │
│ │ │ ├──ble_sub_en.py ... python script
│ │ │ │
source-code
ble_sub_en.py
# -*- coding: utf-8 -*-
#
# Scan the BLE and display the data.
#
from bluepy.btle import DefaultDelegate, Scanner, BTLEException
import sys
import struct
from datetime import datetime
class ScanDelegate(DefaultDelegate):
def __init__(self): # constructor
DefaultDelegate.__init__(self)
self.lastseq = None
self.lasttime = datetime.fromtimestamp(0)
def handleDiscovery(self, dev, isNewDev, isNewData):
if isNewDev or isNewData: # New device or new data
for (adtype, desc, value) in dev.getScanData(): # Repeat as many times as there are data
if desc == 'Manufacturer' and value[0:4] == 'ffff': # Test companyID
__delta = datetime.now() - self.lasttime
# Only use the first one retrieved.
if value[4:6] != self.lastseq and __delta.total_seconds() > 11:
self.lastseq = value[4:6] # Save Seq and time
self.lasttime = datetime.now()
(temp, humid, press, volt) = struct.unpack('<hhhh', bytes.fromhex(value[6:])) # h is a 2Byte integer
print('Temp= {0} °C, Humi= {1} %, Pres= {2} hPa, Volt= {3} V'.format( temp / 100, humid / 100, press, volt/100))
if __name__ == "__main__":
scanner = Scanner().withDelegate(ScanDelegate())
while True:
try:
scanner.scan(5.0) # Let ScanDelegate take care of the rest after finding the device.
except BTLEException:
ex, ms, tb = sys.exc_info()
print('BLE exception '+str(type(ms)) + ' at ' + sys._getframe().f_code.co_name)
supplementary explanation
Scan while looping with a while statement to extract the data advertised by M5StickC.
When the data is received, the handleDiscovery method of the ScanDelegate class is called to extract only the data whose company ID is 0xffff.
Extract the packed binary data with struct.unpack in line 25.
“<” represents little-endian, and “h” represents integer (2Byte).
Execution result
After writing the program and turning on the power of the M5StickC, the advertisement starts immediately.
At the same time, temperature, humidity, barometric pressure, and voltage information are shown on the display.

bluetoothctl
Before running the program on the Raspberry Pi 4, check if the data is being received using the interactive bluetoothctl command in LXTerminal.
bluetoothctl
show
scan on
- show: Display Bluetooth information
- scan on: Display information received via BLE
- quit: Quit bluetoothctl
The data with the key of 0xffff (company ID) is the data from this program, so it is received normally.

Program the Raspberry Pi 4
Run the program on the Raspberry Pi 4B.
Note that root privileges are required to access the device via Bluetooth.
Therefore, it is necessary to run the program with “sudo”.
sudo python ble_sub_en.py
Information such as temperature and humidity received about every 30 seconds is displayed on the console.

About the reach distance
Next, I tried to see if Raspberry Pi 4 could receive data by placing the M5StickC at a distance.
Raspberry Pi 4 can receive data from a distance of several dozen meters (less than 100 meters) without any problem as long as the visibility is good.
However, the situation is different depending on the arrangement of doors, walls, and cabinets in the office.
Even if there are some walls, the Raspberry Pi 4 was able to receive the signal without any problem if it was about 15 meters away, but if it was through a reinforced concrete wall, there were times when it could not be received.
However, I was able to feel a much greater sense of “far reaching” than with Bluetooth in the early days.
If it can reach this far, I think it will be very powerful for communication between IoT devices.
Finally.
This time, I tested a one-to-one combination of M5StickC and Raspberry Pi 4.
However, I thought it would be interesting to place multiple M5StickC (sensors) and only one Raspberry Pi 4 in a place where power supply can be secured, and collect information from multiple sensors and upload it to the cloud.
For example, it is difficult to secure a power supply in all outdoor locations such as fields and greenhouses, so I felt that it would be possible to use the M5StickC placed in multiple locations with a lithium battery plus an auxiliary battery to run the M5StickC and aggregate the data once on the Raspberry Pi 4.
This concludes this article.
I hope this article will be useful to someone somewhere.
Thank you very much for this post. I’ve referenced it many times over several months for my project. It’s been very helpful!
Glad I could be of service.
I wish you the best of luck with your project.
Thank you for taking the time to publish this. My son has Type 1 diabetes and I’m exploring using the m5stick to connect via BLE to an iPhone app in order to display the current blood glucose values from his continuous glucose monitor, so that he doesn’t need to open the phone to see it. I’m encouraged by your article that the m5stick might work for my use case, which will be sending data from the iPhone app to the m5stick. Since it would be a watch, it needs to use BLE since wifi only covers one location.
Thank you for your comment. I am glad my article was helpful. Indeed, it would be nice to be able to see it on the m5stick without having to open the iPhone. I wish you and your son good health.