In the first part we had introduced how the Raspberry Pi Pi Pico W with the temperature sensors BME280 and DHT20 locally shows the temperature and humidity on a 0.96 ”or 1.3“ OLED display and at the same time sends the data via Bluetooth BLE to a smartphone or tablet . This time I would like to treat my favorite topic Robot Cars, first with one Pico W on the chassis and another for the controller. The basis of the programs are the examples
ble_simple_peripheral.py,
and as a module on the Pico W the file ble_advertising.py,
which can be found on github under https://github.com/micropython/micropython/tree/master/examples/bluetooth
It is best to download all "Examples" as a ZIP file right away.
To use Bluetooth with the Raspberry Pi Pico W, you need the latest firmware that besides WiFi is now also supporting Bluetooth since June 2023. When writing the blog post it was this file: Micropython firmware pico-w-130623.uf2. Then you hold the boot button pressed and connect the Pico W via USB with the PC. In the Explorer, the Pico appears like a USB stick, so that the file *.uf2 can be moved to the pico with "drag and drop". The USB drive disappears a few seconds later and the Pico W can be programmed with Thonny.
As helpful sources, I would like to recommend the following websites again:
https://www.raspberrypi.com/documentation/microcontrollers/raspberry-pi-pico.html
https://datasheets.raspberrypi.com/picow/connecting-to-the-internet-with-pico-w.pdf
https://docs.micropython.org/en/latest/library/bluetooth.html
Used hardware
2 |
Raspberry Pi Pico W With current firmware |
|
1 |
||
old |
Any kit with chassis and wheels/engines and |
|
2 |
Breakboard, Jumper cable, battery box With 4 AA batteries |
|
PC with Thonny, Android smartphone/tablet |
Circuit diagrams
Program code
The sample program ble_simple_peripheral.py forms the basis for the program on the Robot Car. It is important that the Micropython example program ble_advertising.py as a module is copied to this Pico W. Otherwise you will get an error message at the line “From ble_advertising import advertising_payload ". As strange as it may sound, the peripheral device offers the Bluetooth LE service, the controller (see below the program example based on the basis of ble_simple_central.py) searches the provider based on the UUID and then connects.
A brief explanation of Uuid (Universally unique identifier). They don't have to be as unique as the name sounds. If your device is the only one, the Uuids can be reused. Otherwise you can go to the website https://www.uuidgenerator.net/ and generate new and unique Uuids.
The changes made compared to the sample programs are at the beginning when importing and instantiating the pins for engine control and self-defined function motor (cY, cX) with two parameters. In the main part at the end, the received 5-digit code is broken down into its components - the first two digits for front or back, the second two digits for left/right and the last number for the joystick button. The values of the two joystick potentioneters are "mapped" in the range of 0 to 31; The middle position is 16. I subtract this value from the respective code fragment cy and cx, to get values between 1 and 15 for forward ride or -1 to -15 for reverse ride. I add or subtract half the cx value for cornering. The last place of the code is not yet used (0 = button not pressed, 1 = button pressed); This could be used for a flashing light, a horn or to switch between remote control and autonomous mode.
My modified program works right away and the motors turn (rather by accident) in the right direction. If the motors turn in the wrong direction, you either have to switch the connections or change the pin assignment in the program code.
Here is the code for the Robot Car for Download:
# Robot Car With Raspberry Pi Pico W BL
# Modified from Official Rasp Pi Example here:
# https://github.com/micropython/micropython/tree/master/examples/bluetooth
# by Bernd54albrecht for AZ-Delivery 2023
import bluetooth
import random
import struct
import time
From ble_advertising import Advertising_payload
From micropython import const
From machine import Pin code, PWM
## Define 3 Fragments of the Code, Idle is 16160
cycling = 16 # Forward/Backward, Digit 1 and 2
CX = 16 # Left/Right, Digit 3 and 4
CB = 0 # Button, Digit 5
# Initialization of Blue LED
Led_blue = Pin code(14, Pin code.OUT)
# Initialization of Motors
vbatt = 6
m1e = PWM(Pin code(11))
m11 = Pin code(13,Pin code.OUT)
m12 = Pin code(12,Pin code.OUT)
m2e = PWM(Pin code(20))
m21 = Pin code(19,Pin code.OUT)
m22 = Pin code(18,Pin code.OUT)
m1e.freq(1000)
m2e.freq(1000)
factor = 655.35 * 6/vbatt # Max PwM / 100 * 6 / VBATT
# Self-Defined Function for 2 Motors with PwM
def engine(cycling, CX):
y = cycling - 15 # Forward/Backward
X = CX - 15 # Left/Right
Leftwheel = y + 0.5 * X
Rightwheel = y - 0.5 * X
IF Leftwheel > 15:
Leftwheel = 15
IF Leftwheel < -15:
Leftwheel = -15
IF Rightwheel > 15:
Rightwheel = 15
IF Rightwheel < -15:
Rightwheel = -15
IF Leftwheel < -2:
m11.off()
m12.on()
elif Leftwheel > 2:
m11.on()
m12.off()
Else:
m11.off()
m12.off()
# M1e.duty_u16 (0)
IF Rightwheel < -2:
m21.off()
m22.on()
elif Rightwheel > 2:
m21.on()
m22.off()
Else:
m21.off()
m22.off()
# M2e.duty_u16 (0)
LeftPWM = intimately(factor * (25 + 5*Section(Leftwheel)))
print("Leftwheel =",Leftwheel,"LeftPwm =", LeftPWM)
Rightpwm = intimately(factor * (25 + 5*Section(Rightwheel)))
print("Rightwheel =",Rightwheel,"Rightpwm =", Rightpwm)
m1e.duty_u16(LeftPWM)
m2e.duty_u16(Rightpwm)
## Taken from ble_simple_peripheral.py
_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, bleed, Surname="MPY-UART"):
self._Ble = bleed
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(Surname=Surname, services=[_Uart_uuid])
self._advertisise()
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._advertisise()
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
# This is the MAIN LOOP
def demo(): # This part modified to control Robot Car
ble = bluetooth.BLE()
p = BLESimplePeripheral(ble)
def on_rx(code): # code is what has been received
code = int(code)
cy = int(code/1000) # digit 1 and 2
cx = int((code-1000*cy)/10) # digit 3 and 4
cb = code - 1000*cy - 10*cx # digit 5
print("cy = ",cy," cx = ",cx," cb = ",cb) # Print code fragments
motor(cy,cx) # call function motor with 2 parameters
p.on_write(on_rx)
if __name__ == "__main__":
demo()
The program code for the controller is based on the example program ble_simple_central.py. The classes Pin and PWM are added from the µPython module machine, with which the joystick module is initialized and queried. With poti1 = ADC(26).read_u16() and poti2 = ADC(27).read_u16() the numerical values for the x and y direction between 0 and 65535 are determined. Whole number division by 2048 gives the value range of 0 to 31. The y-values are multiplied by 1000, the x-values by 10 to get the first 4 digits of the five-digit code. The state of the button is added to the last digit.
Examples: code 16161 means idle and button pressed,
Code 31160 means fastest straight ahead and button not pressed,
Code 25310 means moderate speed and sharp right turn,
Code 8080 means moderate reversing and slight left turn (the zero
at the beginning is inevitably omitted).
And here the code for the controller to Download:
# PicoW_BLE_Robot_Controller.py
# Joystick with two 10K potentiometers on ADC0 and ADC1
# Modified from Official Rasp Pi example here:
# https://github.com/micropython/micropython/tree/master/examples/bluetooth
# by Bernd54Albrecht for AZ-Delivery 2023
import bluetooth
import random
import struct
import time
import micropython
from ble_advertising import decode_services, decode_name
from micropython import const
# Additional code for joystick
from machine import ADC, Pin
button = Pin(15, Pin.IN, Pin.PULL_UP)
## taken from ble_simple_central.py
_IRQ_CENTRAL_CONNECT = const(1)
_IRQ_CENTRAL_DISCONNECT = const(2)
_IRQ_GATTS_WRITE = const(3)
_IRQ_GATTS_READ_REQUEST = const(4)
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)
_IRQ_PERIPHERAL_CONNECT = const(7)
_IRQ_PERIPHERAL_DISCONNECT = const(8)
_IRQ_GATTC_SERVICE_RESULT = const(9)
_IRQ_GATTC_SERVICE_DONE = const(10)
_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11)
_IRQ_GATTC_CHARACTERISTIC_DONE = const(12)
_IRQ_GATTC_DESCRIPTOR_RESULT = const(13)
_IRQ_GATTC_DESCRIPTOR_DONE = const(14)
_IRQ_GATTC_READ_RESULT = const(15)
_IRQ_GATTC_READ_DONE = const(16)
_IRQ_GATTC_WRITE_DONE = const(17)
_IRQ_GATTC_NOTIFY = const(18)
_IRQ_GATTC_INDICATE = const(19)
_ADV_IND = const(0x00)
_ADV_DIRECT_IND = const(0x01)
_ADV_SCAN_IND = const(0x02)
_ADV_NONCONN_IND = const(0x03)
_UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_RX_CHAR_UUID = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
_UART_TX_CHAR_UUID = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
class BLESimpleCentral:
def __init__(self, ble):
self._ble = ble
self._ble.active(True)
self._ble.irq(self._irq)
self._reset()
def _reset(self):
# Cached name and address from a successful scan.
self._name = None
self._addr_type = None
self._addr = None
# Callbacks for completion of various operations.
# These reset back to None after being invoked.
self._scan_callback = None
self._conn_callback = None
self._read_callback = None
# Persistent callback for when new data is notified from the device.
self._notify_callback = None
# Connected device.
self._conn_handle = None
self._start_handle = None
self._end_handle = None
self._tx_handle = None
self._rx_handle = None
def _irq(self, event, data):
if event == _IRQ_SCAN_RESULT:
addr_type, addr, adv_type, rssi, adv_data = data
if adv_type in (_ADV_IND, _ADV_DIRECT_IND) and _UART_SERVICE_UUID in decode_services(
adv_data
):
# Found a potential device, remember it and stop scanning.
self._addr_type = addr_type
self._addr = bytes(
addr
) # Note: addr buffer is owned by caller so need to copy it.
self._name = decode_name(adv_data) or "?"
self._ble.gap_scan(None)
elif event == _IRQ_SCAN_DONE:
if self._scan_callback:
if self._addr:
# Found a device during the scan (and the scan was explicitly stopped).
self._scan_callback(self._addr_type, self._addr, self._name)
self._scan_callback = None
else:
# Scan timed out.
self._scan_callback(None, None, None)
elif event == _IRQ_PERIPHERAL_CONNECT:
# Connect successful.
conn_handle, addr_type, addr = data
if addr_type == self._addr_type and addr == self._addr:
self._conn_handle = conn_handle
self._ble.gattc_discover_services(self._conn_handle)
elif event == _IRQ_PERIPHERAL_DISCONNECT:
# Disconnect (either initiated by us or the remote end).
conn_handle, _, _ = data
if conn_handle == self._conn_handle:
# If it was initiated by us, it'll already be reset.
self._reset()
elif event == _IRQ_GATTC_SERVICE_RESULT:
# Connected device returned a service.
conn_handle, start_handle, end_handle, uuid = data
print("service", data)
if conn_handle == self._conn_handle and uuid == _UART_SERVICE_UUID:
self._start_handle, self._end_handle = start_handle, end_handle
elif event == _IRQ_GATTC_SERVICE_DONE:
# Service query complete.
if self._start_handle and self._end_handle:
self._ble.gattc_discover_characteristics(
self._conn_handle, self._start_handle, self._end_handle
)
else:
print("Failed to find uart service.")
elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT:
# Connected device returned a characteristic.
conn_handle, def_handle, value_handle, properties, uuid = data
if conn_handle == self._conn_handle and uuid == _UART_RX_CHAR_UUID:
self._rx_handle = value_handle
if conn_handle == self._conn_handle and uuid == _UART_TX_CHAR_UUID:
self._tx_handle = value_handle
elif event == _IRQ_GATTC_CHARACTERISTIC_DONE:
# Characteristic query complete.
if self._tx_handle is not None and self._rx_handle is not None:
# We've finished connecting and discovering device, fire the connect callback.
if self._conn_callback:
self._conn_callback()
else:
print("Failed to find uart rx characteristic.")
elif event == _IRQ_GATTC_WRITE_DONE:
conn_handle, value_handle, status = data
print("TX complete")
elif event == _IRQ_GATTC_NOTIFY:
conn_handle, value_handle, notify_data = data
if conn_handle == self._conn_handle and value_handle == self._tx_handle:
if self._notify_callback:
self._notify_callback(notify_data)
# Returns true if we've successfully connected and discovered characteristics.
def is_connected(self):
return (
self._conn_handle is not None
and self._tx_handle is not None
and self._rx_handle is not None
)
# Find a device advertising the environmental sensor service.
def scan(self, callback=None):
self._addr_type = None
self._addr = None
self._scan_callback = callback
self._ble.gap_scan(2000, 30000, 30000)
# Connect to the specified device (otherwise use cached address from a scan).
def connect(self, addr_type=None, addr=None, callback=None):
self._addr_type = addr_type or self._addr_type
self._addr = addr or self._addr
self._conn_callback = callback
if self._addr_type is None or self._addr is None:
return False
self._ble.gap_connect(self._addr_type, self._addr)
return True
# Disconnect from current device.
def disconnect(self):
if self._conn_handle is None:
return
self._ble.gap_disconnect(self._conn_handle)
self._reset()
# Send data over the UART
def write(self, v, response=False):
if not self.is_connected():
return
self._ble.gattc_write(self._conn_handle, self._rx_handle, v, 1 if response else 0)
# Set handler for when data is received over the UART.
def on_notify(self, callback):
self._notify_callback = callback
def demo(): # This is the MAIN LOOP
ble = bluetooth.BLE()
central = BLESimpleCentral(ble)
not_found = False
def on_scan(addr_type, addr, name):
if addr_type is not None:
print("Found peripheral:", addr_type, addr, name)
central.connect()
else:
nonlocal not_found
not_found = True
print("No peripheral found.")
central.scan(callback=on_scan)
# Wait for connection...
while not central.is_connected():
time.sleep_ms(100)
if not_found:
return
print("Connected")
with_response = False
# Modified section for joystick and calculation of code
while central.is_connected():
try:
# Read the raw potentiometer value from specified potentiometer
poti1 = ADC(26).read_u16()
poti2 = ADC(27).read_u16()
y = int(poti1/2048)* 1000
x = int(poti2/2048)*10
z = abs(button.value()-1)
code = str(y + x + z)
print("y = ", y)
print("x = ", x)
central.write(code, with_response)
except:
print("TX failed")
time.sleep_ms(1000)
time.sleep_ms(400 if with_response else 30)
print("Disconnected")
if __name__ == "__main__":
demo()
After testing on the PC, the two programs are saved on the Pico Ws under the name main.py in order to activate the autostart function when operating with batteries. The Pico W on the robot car must be started a few seconds beforehand so that the BLE service can be offered when the controller is switched on.
If the video is not displayed, please check your browser's cookie settings.
Have fun with your experiments with the Raspberry Pi Pico W and Bluetooth.