Pico W の発売当初、ファームウェアはCYW4343 チップのWi-Fi のみサポートしていましたが、ここにきてBluetooth も使えるようになったようです。
Bluetooth という場合、2種類あります。Bluetooth Classic とBluetooth Low Energy(BLE)です。
Pico W でBluetooth を使う場合はマイコンのリソースの少なさからBLE を使うことになります。
BLE は、省電力を特徴として開発された Bluetooth 無線技術の一種です。 常にアクティブな Bluetooth Classicとは異なり、BLE はデータを送受信していないときはスリープ状態を維持できます。 そのため、スマートウォッチ、フィットネス トラッカー、セキュリティ監視デバイスなどのバッテリー駆動のデバイスでの使用に最適です。。
ここではMicroPythonを使用し、Raspberry Pi Pico WをPeripheralの役割に設定し、Bluetooth Low Energy 経由で Android デバイスとのポイントツーポイント通信を確立します。
元ネタ Raspberry Pi Pico W Bluetooth(BLE) using MicroPython | Point-to-Point Communication
Raspberry Pi Pico W には、シングルバンド 2.4 GHz Wi-Fi 4 (802.11n) と Bluetooth 5.1 を備えた Infineon CYW4343 チップが搭載されています。 MicroPython モジュールは、このチップと簡単に接続し、他のデバイスと通信するのに役立ちます。
Raspberry Pi Pico W Bluetooth オンボードを使用して LED を制御
以下のステップでAndroid アプリケーションから Bluetooth 経由で受信したデータを使用して、Raspberry Pi Pico W のオンボード LED を制御する方法を示します。 この例では、BLE モジュールを使用してシリアル通信がエミュレートされます。
Step 1: MicroPython のUF2 ファイルをインストールする
正しいバージョンの MicroPython UF2 ファイルが Raspberry Pi Pico にインストールされることを確認します。
UF2 ファイルは、Raspberry Pi MicroPython ドキュメント ページにあります。 Wi-Fi および BLE をサポートする Raspberry Pi Pico W 用の正しい UF2 ファイルをダウンロードします。
ファイルをダウンロードしたら、BOOTSEL ボタンを押しながら、USB ケーブルを使用して Raspberry Pi Pico を母艦に接続します。 RPI-RP2 という名前の新しいドライブがファイル エクスプローラーに表示されます。 UF2 ファイルをこのドライブにドラッグ アンド ドロップ します。 完了すると、ドライブはファイル エクスプローラーから自動的に消えます。
Step 2:Bluetooth モジュールを Pico W に保存
Bluetooth Low Energy 経由で通信するには、RPi Pico に 2 つの MicroPython モジュールを保存する必要があります。
ここでは Thonny IDE を使用して手順を説明します(Thonny はラズパイにプレインストールされています)。
USB ケーブルを使用して Raspberry Pi Pico W を母艦に接続します。
Thonny IDEを起動して、[ファイル] > [新規] で新しいプロジェクトを開き、 以下のコードをコピペ。
【ble_advertising.py】
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 82 83 84 85 86 87 88 89 90 91 92 93 |
# 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() |
[ファイル] > [名前を付けて保存] をクリック、[Raspberry Pi Pico] を選択し、ファイル名に ble_advertising.py を付け、「OK」を押します。
同様に、[ファイル] > [新規] で新しいプロジェクトを開き、 以下のコードをコピペ。
【ble_simple_peripheral.py】
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# 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() |
[ファイル] > [名前を付けて保存] をクリック、[Raspberry Pi Pico] を選択し、ファイル名にble_simple_peripheral.py を付け、「OK」を押します。
Step 3:LED を制御するための MicroPython Bluetooth サンプル コード
[ファイル] > [新規] で新しいプロジェクトを開き、 以下のコードをコピペ。
【main.py】
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 |
# 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 reception |
[ファイル] > [名前を付けて保存] をクリック、[Raspberry Pi Pico] を選択し、ファイル名にmain.py を付け、「OK」を押します。
F5 キーを押すか、「実行」アイコンをクリックしてコードを実行します。
スクリプトが実行されると、コンソールに「advertisingを開始しています(Starting advertising)」というテキストが表示されます。
Step 4:Android アプリのセットアップ
無料で入手できる Play ストアからアプリ Serial Bluetooth Terminal をインストールします。
アプリを開き、ナビゲーション ドロワーから [デバイス] をクリックします。
「Bluetooth LE」タブを選択し、「SCAN」をタップします。 デバイスが mpy-uart としてリストされているはずです。
接続するデバイス名を選択します
接続が確立されると、Android アプリに「接続されました」というメッセージが表示されます。
Thonny IDE のコンソールにも「新しい接続 64」というテキストが表示されます。
Step 5:Bluetooth アプリ経由でオンボード LED の切り替え
Android アプリで、引用符なしで「toggle」と入力し、送信アイコンをタップします。
Pico のLEDがONになります。
コマンドを再度送信すると、状態が ON から OFF に切り替わります。
何度も繰り返すとLEDがチカチカします。
Pico 側でデータが送られているのが確認できます。
Raspberry Pi Pico W で Bluetooth を使用してプッシュボタンのステータスを読み取る
このパートでは、Raspberry Pi Pico W から Bluetooth 経由で Android でデータを受信する方法を扱います。例として、プッシュボタンが押されたかどうかを監視し、Android アプリのコンソールにそのステータスを表示します。
Step 1:プッシュボタンを Raspberry Pi Pico W に接続する
以下の回路図に示すように、GPIO 0 と Raspberry Pi Pico W のいずれかの GND ピンの間にプッシュボタンを接続します。
Step 2:Bluetooth LE 経由でデータを読み取るための MicroPython サンプル コード
上で説明した MicroPython モジュール ble_advertising.py および ble_simple_peripheral.py も RPi Pico W に
プリロードする必要があります。
Thonny IDE を開きます。
以下の3つのコードをPico W に保存します。
【ble_advertising.py】
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 82 83 84 85 86 87 88 89 90 91 92 93 |
# 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() |
【ble_simple_peripheral.py】
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# 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() |
【main.py】
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 |
# 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() |
main.py スクリプトが実行されると、コンソールに「広告を開始しています(Start advertising)」というテキストが表示されます。
Step 3:Android アプリに接続してデータを読み取る
上記の LED 制御セクションで説明した手順と同様に、Serial Bluetooth Terminalアプリを使用します。
Android アプリを BLE 経由で Pico W に接続します。
上記と同様に、Blooth LE ー> SCAN の順でデバイスを検索しますが、以前のデータが残っていて複数表示されることがあります。ただしくconnectできる方を選びます。
無事接続
Pico W の側でも確認できます。
ボタンをプッシュしてみます。
Pico W に接続されているプッシュボタンを押すと、Android アプリのコンソールに
「プッシュボタンが押されました」というテキストが表示されます。
以下のアイコンをタップすれば、通信は切断されます。
隣のゴミ箱をタップすればログが消去されます。
以上です。
Appendix
Bluetooth Classicのデバイスの場合
マスター(Master)とスレーブ(Slave)の2種類の役割があります。
マスターはコンピュータネットワークのクライアント/サーバにおけるサーバに相当するもので、スレーブとはクライアントに相当するものです。
Bluetooth Low Energyのデバイスの場合
セントラル(Central)とペリフェラル(Peripheral)の2種類の役割があります。
セントラルは、ネットワーク上の親局で、ペリフェラルに機能を提供してもらうサーバで、スマートフォンやタブレットなどがセントラルになります。
ペリフェラルは、ネットワーク上の子局で、体重計や体温計、スマートウォッチなどのセンサーの値やバッテリーの残量など、何かしらの情報や機能を提供するデバイスがペリフェラルになります。
Appendix2
C/C++ でBluetooth を何とかしたい場合
pico-SDK + pico-examples で使えそうなものを試してみましょう。
Leave a Reply