Raspberry Pi Pico W jetzt mit Bluetooth - Teil 2 - Robot Car und Controller - AZ-Delivery

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,

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

Smart Robot Car Kit

old

Any kit with chassis and wheels/engines and
L298N Motor Controller Board with DC-DC-Converter 5V (part of the above Kits)

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

 This code is sent as a string and decoded in the robot car's program. To do this, it is converted back to an integer number and broken down into the three fragments (see above).


 

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.

Projekte für anfängerRaspberry pi

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery