I tried BLE (Bluetooth Low Energy) communication between M5StickC and Raspberry Pi4 | そう備忘録

I tried BLE (Bluetooth Low Energy) communication between M5StickC and Raspberry Pi4

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.

Advertizing with BLE
  • 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.

M5StickC set

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.

BME280 module

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.

Grove cable

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.

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.

Raspberry Pi 4B

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).

Soldering iron

Solder

Any kind of leaded solder for electronic work is fine.

Solder

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.

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.

Electrician's pliers

Assembly

Soldering the BME280

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

BME280

Solder the pins to the module.

Soldering the BME280

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.

Creating a connector and connecting it to the M5StickC

Wiring Diagram

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

Wiring between M5StickC and BME280

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.

Manage Libraries

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

install the Adafruit BME280 Library

I selected “install all”.

Select Install all
R

Raspberry Pi

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

sudo pip3 install bluepy
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.

  1. Active mode:100mA~240mA
  2. Modem-sleep mode:20mA~25mA(Single-core)
  3. Light-sleep mode:0.8mA(800µA)
  4. Deep-sleep mode:10µA~150µA
  5. 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)

Advertisingデータレイアウト
  • 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.

M5StickC

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.

bluetoothctl command

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.

Run the program on the Raspberry Pi 4B.

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.

Finally.

I hope this article will be useful to someone somewhere.

souichirou kikuchi

I'm Japanese. A reminder to remember what I've done. I'm blogging in the hope that it will be helpful to others who want to do similar things. I mainly write blogs about LEGO, AWS (Amazon Web Services), WordPress, Deep Learning and Raspberry Pi. At work, I'm working on installing collaborative robots and IoT in factories. I passed the JDLA (Japan Deep Learning Association) Deep Learning for GENERAL in July 2019. If you have any questions, please leave them in the comments at the bottom of the article.

You may also like...

4 Responses

  1. Anonymous says:

    Thank you very much for this post. I’ve referenced it many times over several months for my project. It’s been very helpful!

  2. A says:

    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.

    • souichirou says:

      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.

Leave a Reply to souichirou Cancel reply

Name, Email, and Website are optional.
and, your Email address will not be published.