Networking

Group Assignment -

This is our EECS section page for it - I did not participate it in this time.

https://fab.cba.mit.edu/classes/MAS.863/EECS/Week11.html

For this week I made a my desktop send commands to the pico w to run on 2 leds. Initially I wanted to control a RGB Led but as you will later see that did not work out.

I gathered my parts which were the Raspberry Pi Pico W, 3 1k Ohms 1206 format resistors, and one RED LEG 4PLCC SMD from our inventory made by Cree. The Pico has a inbuilt bluetooth module which is the large silver plate. This is why it doesn’t need an antenna. This does Bluetooth Low Energy (BLE).

I have no experience in coding bluetooth / networking related things, so ChatGPT helped me a lot here - in terms of the coding and understanding. I first needed to make my board in KiCad.

Then I did the typical routing puzzle. Now here is the interesting part, when drawing the edge cuts, I was thinking since I am just using a few pins of the pico, and they are all adjacent ones on the top left, there is no need to make my PCB for the entire Pico. So what I did was make the PCB just mount to the needed pins, and I thought that would look elegant and minimal.

I milled the board and I found it the finish to not be as clean as it usually is - maybe the the end mill wasn’t that sharp? Anyway I found a screwdriver nearby to deburr the copper off.

I liked how that looked. I then soldered all the components.

But it didn’t work! Even a simple blink code wasn’t working. I checked 2 things, 1) was there any shorts; there wasn’t. 2) was the pico sending power on the GPIO pins, and yes it was sending 3.3V. Therefore, my RGB Led wasn’t working, and I know I had it in the right orientation.

I then tried to desolder the LED, but then I accidentally pulled a trace out! I put a green and red SMD LED

This worked! I am not using the third trace, but that was because the distance between the GND was too large for me to plop a LED. So for the rest of this assignment I continued with this setup.

Now for the coding, I mainly relied on ChatGPT. This is our conversation that contains the code as well: https://chatgpt.com/share/6925bbb9-8c98-8007-a271-111e64f4f0f1


I haven’t used micro python before so I followed these guides:

https://projects.raspberrypi.org/en/projects/get-started-pico-w/1

https://projects.raspberrypi.org/en/projects/getting-started-with-the-pico/5

https://thonny.org/

https://www.raspberrypi.com/documentation/microcontrollers/micropython.html

Here is my final code that runs on the Pico.

import bluetooth
from machine import Pin
import time

green_led = Pin(1, Pin.OUT)
red_led = Pin(2, Pin.OUT)

# Nordic UART UUIDs
UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
UART_RX_UUID      = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")  # Write
UART_TX_UUID      = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")  # Notify

UART_SERVICE = (
    UART_SERVICE_UUID,
    (
        (UART_TX_UUID, bluetooth.FLAG_NOTIFY,),
        (UART_RX_UUID, bluetooth.FLAG_WRITE,),
    ),
)

# Pico W BLE IRQ event codes:
_IRQ_CENTRAL_CONNECT = 1
_IRQ_CENTRAL_DISCONNECT = 2
_IRQ_GATTS_WRITE = 3

class BLEController:
    def __init__(self):
        self.ble = bluetooth.BLE()
        self.ble.active(True)

        # Register UART service: returns ((tx_handle, rx_handle),)
        ((self.tx_handle, self.rx_handle),) = self.ble.gatts_register_services((UART_SERVICE,))

        self.ble.irq(self._irq)

        self._advertise()

    def _advertise(self):
        name = b"PicoLED"
        adv = (
            b"\x02\x01\x06" +
            bytes([len(name) + 1, 0x09]) + name +
            b"\x11\x07" + bytes(UART_SERVICE_UUID)
        )
        self.ble.gap_advertise(100_000, adv_data=adv, connectable=True)
        print("Advertising as PicoLED...")

    def _irq(self, event, data):
        if event == _IRQ_CENTRAL_CONNECT:
            print("Connected")

        elif event == _IRQ_CENTRAL_DISCONNECT:
            print("Disconnected")
            self._advertise()

        elif event == _IRQ_GATTS_WRITE:
            # Client wrote to RX characteristic
            handle = data[1]
            if handle == self.rx_handle:
                cmd = self.ble.gatts_read(self.rx_handle).decode().strip()
                print("Received:", cmd)
                self._handle_command(cmd)

    def _handle_command(self, cmd):
        if cmd == "green":
            green_led.value(1)
        elif cmd == "red":
            red_led.value(1)
        elif cmd == "off":
            green_led.value(0)
            red_led.value(0)
            

# Start BLE LED controller
ble_controller = BLEController()

while True:
    time.sleep(1)

I worked with ChatGPT to understand how does BLE work, and what is the protocol. So I covered the GATT format, and I will now walk through the basic flow of the code.

UART_SERVICE_UUID = bluetooth.UUID("6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
UART_RX_UUID      = bluetooth.UUID("6E400002-B5A3-F393-E0A9-E50E24DCCA9E")  # Write
UART_TX_UUID      = bluetooth.UUID("6E400003-B5A3-F393-E0A9-E50E24DCCA9E")  # Notify


These allow us to setup multiple services, the RX would be to read the LED status, and the TX is to Notify of a connection/advertising.

This is the GATT protocol

UART_SERVICE = (
    UART_SERVICE_UUID,
    (
        (UART_TX_UUID, bluetooth.FLAG_NOTIFY,),
        (UART_RX_UUID, bluetooth.FLAG_WRITE,),
    ),
)

Then the code advertises

adv = (
            b"\x02\x01\x06" +
            bytes([len(name) + 1, 0x09]) + name +
            b"\x11\x07" + bytes(UART_SERVICE_UUID)
        )

Which contains the Flags, name (PicoLED), and the SERVICE ID. Then when there is an event we can use gatts_read to parse the rx, and based on that can turn on the green led, red led, or turn both of them off.

Then on my desktop side, I wanted to use python to send commands. So with GPT, we used a library called Bleak, which is “is a GATT client software, capable of connecting to BLE devices acting as GATT servers.” Then here is the code, we are using the RX UUID:
6E400002-B5A3-F393-E0A9-E50E24DCCA9E

import asyncio
from bleak import BleakScanner, BleakClient

UART_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"

async def main():
    print("Scanning for PicoLED...")
    devices = await BleakScanner.discover()

    pico = None
    for d in devices:
        if d.name == "PicoLED":
            pico = d
            break

    if not pico:
        print("PicoLED not found!")
        return

    print("Found:", pico)

    async with BleakClient(pico.address) as client:
        print("Connected!")

        for i in range(100):
            print(f"Blink {i+1}")
            
            # Turn LED on
            await client.write_gatt_char(UART_RX, b"red")
            await asyncio.sleep(0.3)

            # Turn LED off
            await client.write_gatt_char(UART_RX, b"off")
            await asyncio.sleep(0.3)

            await client.write_gatt_char(UART_RX, b"green")
            await asyncio.sleep(0.3)

        print("Done blinking!")

asyncio.run(main())

   print("Scanning for PicoLED...")
    devices = await BleakScanner.discover()

    pico = None
    for d in devices:
        if d.name == "PicoLED":
            pico = d
            break

    if not pico:
        print("PicoLED not found!")
        return

    print("Found:", pico)

This part tries to find the PicoLED, and the following code is used to send commands

await client.write_gatt_char(UART_RX, b"red")

asyncio allows commands to run async - so it doesn’t block other processes. Is it it working, we can see on the terminal - the python is sending blink commands, and on Thonny we are receiving them!

Acknowledgements + Files

Here are the files, and as usual thank you to Anthony for helping me - would be quite lost without him.