Raspberry Pi Pico W Bluetooth(BLE) using MicroPython | Point-to-Point Communication

In this tutorial, you will learn to use the onboard Bluetooth on Raspberry Pi Pico W using MicroPython to communicate with an Android app. The steps in this guide describe how to read the status of a pushbutton and send commands to control an LED using an Android app.

Raspberry Pi Pico W has the Infineon CYW4343 chip onboard, which features a single-band 2.4 GHz Wi-Fi 4 (802.11n) and Bluetooth 5.1. MicroPython modules will help us to easily interface with this chip and communicate with other devices.

The Infineon chip on Raspberry Pi Pico W also has Wi-Fi capability which is discussed in our article Connect Raspberry Pi Pico W to the Internet Using Wi-Fi.

raspberry pi pico w with infineon chip

Components Required

  • A Raspberry Pi Pico W.
  • A pushbutton switch.
  • Breadboard and jumper wires.
  • USB cable to connect Pico.

Overview of Bluetooth Low Energy(BLE) On Pico W

Bluetooth Low Energy, abbreviated as BLE, is a variant of Bluetooth wireless technology developed with power-saving as its notable feature. Unlike Bluetooth Classic which is always active, BLE can stay in a sleep state when it is not transceiving data. This makes it ideal for use in battery-operated devices such as smartwatches, fitness trackers, and security monitoring devices.

If you would like to use Bluetooth Classic with Raspberry Pi Pico, we have a tutorial where we interface the HC-05 Bluetooth module with RPi Pico.

BLE Communication Topologies

BLE has the following communication topologies:

  • Point-to-Point: Communication between two nodes(devices) in a network; server-client communication.
  • Mesh Network: Multiple nodes can communicate to each other in this topology.
  • Broadcast Mode: A server will broadcast data on the network that can be read by many devices.

BLE States

There are many layers in the BLE architecture. The Link Layer in BLE defines certain states when a device communicates.

  • Standby: The device neither transmits nor receives data.
  • Initiating: Responds to advertising packets from a particular device to request a
    connection.
  • Advertising: Transmits advertising packets and processes packets sent in
    response to advertising packets by other devices.
  • Connection: In a connection with another device.
  • Scanning: Listening for advertising packets from other devices.
  • Isochronous Broadcasting: Broadcasts isochronous data packets.
  • Synchronization: Listens for periodic advertising belonging to a specific advertising train transmitted by a device.

When in the connection state, a device can be in one of the two roles – the Central role or the Peripheral role. The device that will initiate a connection and transition from the Initiating state to the Connection state assumes the Central role. The device in the Peripheral role will be initially in the Advertising state. When it accepts a connection request from the Central device, it transitions to the Connection state.

In this guide, we will set up Raspberry Pi Pico in the peripheral role and establish point-to-point communication with an Android device via Bluetooth Low Energy.

Control LED using Android App & Raspberry Pi Pico Bluetooth

The following steps will demonstrate how we can control the onboard LED of Raspberry Pi Pico W using data received over Bluetooth from an Android application. Serial communication is emulated using BLE modules in this example.

Step 1: Install the MicroPython UF2 file

First, ensure that you have the correct version of the MicroPython UF2 file installed on Raspberry Pi Pico. You can find the UF2 file on the Raspberry Pi MicroPython documentation page. Download the correct UF2 file for Raspberry Pi Pico W with Wi-Fi and BLE support.

Download page screenshot for MicroPython UF2 (with BLE support) for Raspberry Pi Pico W

You can also find the latest builds on the MicroPython download page for Raspberry Pi Pico W.

After you have downloaded the file, connect your Raspberry Pi Pico to your computer using a USB cable while pressing the BOOTSEL button. A new drive named RPI-RP2 will be visible in your file explorer. Drag and drop the UF2 file to this drive. When done, the drive will automatically disappear from File Explorer. For more information, visit our getting started guide for Raspberry Pi Pico W.

Step 2: Save Bluetooth Modules to Pico W

We will need to save two MicroPython modules in our RPi Pico to communicate via Bluetooth Low Energy. I will describe the procedure using the Thonny IDE, but you can use other IDEs such as uPyCraft.

Connect your Raspberry Pi Pico W to the computer using a USB cable.

Open a new project in Thonny IDE by going to File>New. Copy the paste the code below.

# Helpers for generating BLE advertising payloads.

from micropython import const
import struct
import bluetooth

# Advertising payloads are repeated packets of the following form:
#   1 byte data length (N + 1)
#   1 byte type (see constants below)
#   N bytes type-specific data

_ADV_TYPE_FLAGS = const(0x01)
_ADV_TYPE_NAME = const(0x09)
_ADV_TYPE_UUID16_COMPLETE = const(0x3)
_ADV_TYPE_UUID32_COMPLETE = const(0x5)
_ADV_TYPE_UUID128_COMPLETE = const(0x7)
_ADV_TYPE_UUID16_MORE = const(0x2)
_ADV_TYPE_UUID32_MORE = const(0x4)
_ADV_TYPE_UUID128_MORE = const(0x6)
_ADV_TYPE_APPEARANCE = const(0x19)


# Generate a payload to be passed to gap_advertise(adv_data=...).
def advertising_payload(limited_disc=False, br_edr=False, name=None, services=None, appearance=0):
    payload = bytearray()

    def _append(adv_type, value):
        nonlocal payload
        payload += struct.pack("BB", len(value) + 1, adv_type) + value

    _append(
        _ADV_TYPE_FLAGS,
        struct.pack("B", (0x01 if limited_disc else 0x02) + (0x18 if br_edr else 0x04)),
    )

    if name:
        _append(_ADV_TYPE_NAME, name)

    if services:
        for uuid in services:
            b = bytes(uuid)
            if len(b) == 2:
                _append(_ADV_TYPE_UUID16_COMPLETE, b)
            elif len(b) == 4:
                _append(_ADV_TYPE_UUID32_COMPLETE, b)
            elif len(b) == 16:
                _append(_ADV_TYPE_UUID128_COMPLETE, b)

    # See org.bluetooth.characteristic.gap.appearance.xml
    if appearance:
        _append(_ADV_TYPE_APPEARANCE, struct.pack("<h", appearance))

    return payload


def decode_field(payload, adv_type):
    i = 0
    result = []
    while i + 1 < len(payload):
        if payload[i + 1] == adv_type:
            result.append(payload[i + 2 : i + payload[i] + 1])
        i += 1 + payload[i]
    return result


def decode_name(payload):
    n = decode_field(payload, _ADV_TYPE_NAME)
    return str(n[0], "utf-8") if n else ""


def decode_services(payload):
    services = []
    for u in decode_field(payload, _ADV_TYPE_UUID16_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<h", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID32_COMPLETE):
        services.append(bluetooth.UUID(struct.unpack("<d", u)[0]))
    for u in decode_field(payload, _ADV_TYPE_UUID128_COMPLETE):
        services.append(bluetooth.UUID(u))
    return services


def demo():
    payload = advertising_payload(
        name="micropython",
        services=[bluetooth.UUID(0x181A), bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")],
    )
    print(payload)
    print(decode_name(payload))
    print(decode_services(payload))


if __name__ == "__main__":
    demo()
Code language: Python (python)

Click on File> Save as and then select Raspberry Pi Pico.

Thonny Save to

Give the file name ble_advertising.py and press OK.

Repeat the procedure and save the code below as ble_simple_peripheral.py to your Pico W.

# This example demonstrates a UART periperhal.

import bluetooth
import random
import struct
import time
from ble_advertising import advertising_payload

from micropython import const

_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)

_FLAG_READ = const(0x0002)
_FLAG_WRITE_NO_RESPONSE = const(0x0004)
_FLAG_WRITE = const(0x0008)
_FLAG_NOTIFY = const(0x0010)

_UART_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX = (
    bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_READ | _FLAG_NOTIFY,
)
_UART_RX = (
    bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"),
    _FLAG_WRITE | _FLAG_WRITE_NO_RESPONSE,
)
_UART_SERVICE = (
    _UART_UUID,
    (_UART_TX, _UART_RX),
)


class BLESimplePeripheral:
    def __init__(self, ble, name="mpy-uart"):
        self._ble = ble
        self._ble.active(True)
        self._ble.irq(self._irq)
        ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
        self._connections = set()
        self._write_callback = None
        self._payload = advertising_payload(name=name, services=[_UART_UUID])
        self._advertise()

    def _irq(self, event, data):
        # Track connections so we can send notifications.
        if event == _IRQ_CENTRAL_CONNECT:
            conn_handle, _, _ = data
            print("New connection", conn_handle)
            self._connections.add(conn_handle)
        elif event == _IRQ_CENTRAL_DISCONNECT:
            conn_handle, _, _ = data
            print("Disconnected", conn_handle)
            self._connections.remove(conn_handle)
            # Start advertising again to allow a new connection.
            self._advertise()
        elif event == _IRQ_GATTS_WRITE:
            conn_handle, value_handle = data
            value = self._ble.gatts_read(value_handle)
            if value_handle == self._handle_rx and self._write_callback:
                self._write_callback(value)

    def send(self, data):
        for conn_handle in self._connections:
            self._ble.gatts_notify(conn_handle, self._handle_tx, data)

    def is_connected(self):
        return len(self._connections) > 0

    def _advertise(self, interval_us=500000):
        print("Starting advertising")
        self._ble.gap_advertise(interval_us, adv_data=self._payload)

    def on_write(self, callback):
        self._write_callback = callback


def demo():
    ble = bluetooth.BLE()
    p = BLESimplePeripheral(ble)

    def on_rx(v):
        print("RX", v)

    p.on_write(on_rx)

    i = 0
    while True:
        if p.is_connected():
            # Short burst of queued notifications.
            for _ in range(3):
                data = str(i) + "_"
                print("TX", data)
                p.send(data)
                i += 1
        time.sleep_ms(100)


if __name__ == "__main__":
    demo()
Code language: Python (python)

You will also find the code for the two modules shown above on this GitHub page of MicroPython Bluetooth examples.

Step:3 MicroPython Bluetooth Example Code To Control LED

Now paste the following code into a new project on Thonny IDE.

# Import necessary modules
from machine import Pin 
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral

# Create a Bluetooth Low Energy (BLE) object
ble = bluetooth.BLE()

# Create an instance of the BLESimplePeripheral class with the BLE object
sp = BLESimplePeripheral(ble)

# Create a Pin object for the onboard LED, configure it as an output
led = Pin("LED", Pin.OUT)

# Initialize the LED state to 0 (off)
led_state = 0

# Define a callback function to handle received data
def on_rx(data):
    print("Data received: ", data)  # Print the received data
    global led_state  # Access the global variable led_state
    if data == b'toggle\r\n':  # Check if the received data is "toggle"
        led.value(not led_state)  # Toggle the LED state (on/off)
        led_state = 1 - led_state  # Update the LED state

# Start an infinite loop
while True:
    if sp.is_connected():  # Check if a BLE connection is established
        sp.on_write(on_rx)  # Set the callback function for data receptionCode language: Python (python)

Run your code by pressing F5 or by clicking on the Run icon. Save it to your Raspberry Pi Pico. Name the script main.py or any other name with a “.py” extension. Code saved as main.py will run automatically when Pico W reboots.

When the script runs, you will see the text “Starting advertising” displayed on the console.

Note: If the IDE shows you an error that the bluetooth module cannot be imported or does not exist, you may have installed the wrong UF2 file to your Pico W.

Step 4: Setting Up Android App

Install the app Serial Bluetooth Terminal from the Play Store which is available for free. Open the app and from the navigation drawer, click on Devices.

Select the Bluetooth LE tab and tap on SCAN. You must now see your device listed as mpy-uart. Select the device name to connect to it.

When the connection is established, you can see the message “Connected” on the Android app. The console in Thonny IDE will also display the text “New connection 64”

Step 5: Toggle the Onboard LED Via Bluetooth App

On the Android app, type “toggle” without the quotes and tap the send icon.

The onboard LED on Raspberry Pi Pico W must change its state from OFF to ON. Sending the command again will toggle its state from ON to OFF.

Also read: Raspberry Pi Pico vs Arduino – Which Board To Choose?

Advertisement

Send Data from Raspberry Pi Pico to Android App Using BLE

The section above demonstrated how we can send data from an Android app to Pico. Now, this part will deal with receiving data on an Android via Bluetooth from Raspberry Pi Pico W. As an example, we will monitor if a pushbutton is pressed and display its status on the console of an Android app.

Step 1: Connect Pushbutton with Raspberry Pi Pico W

Connect a pushbutton between GPIO 0 and any of the GND pins of Raspberry Pi Pico W as shown in the schematic below.

Step 2: MicroPython Example Code To Read Data Over Bluetooth LE

The MicroPython modules, ble_advertising.py and ble_simple_peripheral.py, that we discussed above also need to be preloaded to your RPi Pico W.

Connect Pico W to your computer and upload the following code:

# Import necessary modules
from machine import Pin
import bluetooth
from ble_simple_peripheral import BLESimplePeripheral
import time

# Create a Bluetooth Low Energy (BLE) object
ble = bluetooth.BLE()
# Create an instance of the BLESimplePeripheral class with the BLE object
sp = BLESimplePeripheral(ble)

# Set the debounce time to 0. Used for switch debouncing
debounce_time=0

# Create a Pin object for Pin 0, configure it as an input with a pull-up resistor
pin = Pin(0, Pin.IN, Pin.PULL_UP)

while True:
    # Check if the pin value is 0 and if debounce time has elapsed (more than 300 milliseconds)
    if ((pin.value() is 0) and (time.ticks_ms()-debounce_time) > 300):
        # Check if the BLE connection is established
        if sp.is_connected():
            # Create a message string
            msg="pushbutton pressed\n"
            # Send the message via BLE
            sp.send(msg)
        # Update the debounce time    
        debounce_time=time.ticks_ms()
Code language: Python (python)

Run your code by pressing F5 or by clicking on the Run icon. Save it to your Raspberry Pi Pico and name the script main.py or any other name with a “.py” extension.

When the script runs, you will see the text “Starting advertising” displayed on the console.

Step 3: Connect To Android App & Read Data

Similar to the procedure described in the LED controlling section above, we will use the Serial Bluetooth Terminal app. Connect your Android app to Pico W via BLE.

When you press the pushbutton connected to Pico W, you must now see the text “pushbutton pressed” on the console of the Android app.

Wrapping Up

In this Raspberry Pi Pico W Bluetooth guide, we saw how we can send and receive data from an Android app over BLE. You can use this guide to make various projects such as Android app-controlled home lights, Bluetooth-controlled toy cars, remotely control Neopixel LEDs, etc.

Hope you found the article useful. Please leave your thoughts and queries in the comments below.

Also read: Arduino vs MicroPython vs CircuitPython: Which One Will You Choose?


Posted

in

by

Comments

17 responses to “Raspberry Pi Pico W Bluetooth(BLE) using MicroPython | Point-to-Point Communication”

  1. Tony Avatar
    Tony

    Fantastic article for Smart Bluetooth Terminal connection.

    Does not work with RoboRemo as pairing is required which always fails.

    How can PicoW BLESimplePeripheral be made to pair with another device?

    1. Abhilekh Das Avatar
      Abhilekh Das

      You can connect to another Pico W serving in the Central role. Use the code ble_simple_central.py found in the Github link given in the article. To connect with an Android device, you can use any of the BLE apps found in the Google Play Store, such as ‘nRF Connect for Mobile’, ‘BLE Scanner’, and ‘Bluetooth Serial Terminal’.

  2. Tom Avatar
    Tom

    What UF2 does this work with? I tried using the latest release (v1.20.0) and it says that the Bluetooth module isn’t available.

    1. Abhilekh Das Avatar
      Abhilekh Das

      Raspberry Pi documentation page mentioned in this article has the Pico W UF2 file with Wi-Fi and BLE support. Here is the direct link: https://datasheets.raspberrypi.com/soft/micropython-firmware-pico-w-130623.uf2
      Or, you can use this link to get the nightly build: https://micropython.org/resources/firmware/rp2-pico-w-20230714-unstable-v1.20.0-283-g7d66ae603.uf2

  3. Alex Avatar
    Alex

    The android app does not find any devices, but the console says “Starting advertising”. Any idea why?

    1. Abhilekh Das Avatar
      Abhilekh Das

      Hello Alex.Please check if your Android device supports BLE.

  4. Roland Kershaw Avatar
    Roland Kershaw

    This worked first time, no problems whatsoever. Thanks for your time and effort.

  5. Benja Avatar
    Benja

    i cant import module bluetooth

    1. Abhilekh Das Avatar
      Abhilekh Das

      Please check whether you are uploading the correct MicroPython UF2 file into your Pico W as mentioned in the article.

  6. Richard Avatar
    Richard

    I followed the instructions, specifically “Run your code by pressing F5 or by clicking on the Run icon. Save it to your Raspberry Pi Pico and name the script main.py or **any other name** with a “.py” extension.”
    By chance I called it bluetooth.py which didn’t work, found out the hard way!
    After renaming it, it works, thank you.

  7. Rob Avatar
    Rob

    im just wondering about changing the name from mpy-uart ,
    i thought this bit
    def __init__(self, ble, name=”mpy-uart”):
    self._ble = ble
    self._ble.active(True)
    self._ble.irq(self._irq)
    ((self._handle_tx, self._handle_rx),) = self._ble.gatts_register_services((_UART_SERVICE,))
    self._connections = set()
    self._write_callback = None
    self._payload = advertising_payload(name=name, services=[_UART_UUID])
    self._advertise()
    i could just cahge name to something else, but it doesnt seem to wrok any advice?

  8. kumarrr Avatar
    kumarrr

    Hello, I want to establish a BLE HID using Micropython, any example or code available for the same

  9. evgeni Avatar
    evgeni

    Is there a way to connect two Pico Ws via BLE? For example: pressing a pushbutton connected to Pico W-1 toggles the LED of Pico W-2.

  10. Semi83 Avatar
    Semi83

    Do anybody know how to receive this at the Raspberry Pi 4 ? (Instead of Android)

  11. Ryan Avatar
    Ryan

    Hello, can someone suggest a similar guide to use pico w only as advertising beacon ble?

  12. Thierry Avatar
    Thierry

    very good guide, works very good !!
    In case of error “gatt status 133” in the app, just relaunch connection several times.

  13. Ivan Avatar
    Ivan

    hello could someone guide me how to transfer file from mobile to pico w using bluetooth.

Leave a Reply

Your email address will not be published. Required fields are marked *