Last day

1. Python down to the machine

First thing today![1]

30 min of "lecture" time
Log on to your own PC and run Thonny.

1.1. Digital electronics

Semiconductors are not conductors and not insulators. Their electrical / mechanical (!) behavior can be changed by doping the material with impurities (n-type or p-type).

1.2. RP2040 chip

We start at the some of the lowest levels of what’s going on.

Raspberry Pi (the company) makes several products:

  • Full Linux-based single-board computers:

    • Raspberry Pi Zero 2 W

    • Raspberry Pi 3 Model B+

    • Raspberry Pi 4 Model B

    • Raspberry Pi 5

    • Raspberry Pi Compute Module 4

    • Raspberry Pi Compute Module 4S

    • Raspberry Pi Compute Module 3+

  • RP2040 microcontroller placed on boards called:

    • Raspberry Pi Pico

    • Raspberry Pi Pico W

pi pico family

rp2040 block
Figure 1. RP2040 internal block diagram

What is inside the RP2040?

  • CPU vs MCU vs "an Arduino" vs PC / Laptop / Phone

  • peripherals interact with the outside world

  • a CPU architecture: ARM Cortex-Mx, Intel x86, RISC-V, AMD, a hundred more for microcontrollers.

Going up the levels from digital electronics to Python:

  • Machine code

  • Assembly language

  • Higher-level languages

1.3. Python language

  • Compiled languages vs. interpreted

    • Compiled to machine code: C and C++. Arduino uses C++ with magic helpers.

    • Interpreted: Python, JavaScript, Ruby, Java

But the machine can only run machine code!

  • Python VM — virtual machine, runs bytecode. (this is how Python runs on your PC and on the Pi Pico without change)

  • Python → compiled to bytecode → bytecode runs on Virtual Machine

  • → which runs on the RP2040’s processor → which executes its ARM Cortex-M0+ machine code instructions.

2. Commands over the Internet using MQTT

UART stands for “universal asynchronous receiver-transmitter” and is commonly called a serial port.

Save as comms.py onto your Pico
from time import ticks_ms
from time import ticks_add

import machine


uart = machine.UART(
    0,
    baudrate=115200,
    timeout=10,
    timeout_char=100,
    )

machine_id = machine.unique_id().hex()

# TODO: defer after boot to allow time for network to be live
uart.write(f"{machine_id}: start\n")


def send(message):
    uart.write(message + "\n")


def receive(wait=False, timeout=10):
    start_time = ticks_ms()
    end_time = ticks_add(start_time, timeout*1000)

    while True:
        rawline = uart.readline()

        # have something
        if rawline is not None:
            # convert from bytes to string
            try:
                line = rawline.decode()
            except:
                #oops, ignore
                return None
            # done!
            return line.strip()  # remove start/end newline or spaces
        # Nothing.  What to do next?
        # Try again to get a line if wait==True and no timeout yet
        elif wait and (ticks_ms() < end_time):
            continue  # repeat the loop from the top
        # either we don't want to wait, or
        # we've timed out
        else:
            return None  # same as returned from .readline()

Example usage
# Built-in modules
from time import sleep

# MicroPython-specific modules
import machine
from machine import Pin


# Custom local modules
import comms


# Setup the onboard LED
onboard_led = Pin(25, Pin.OUT)


while True:
    line = comms.receive()
    
    if line:
        # do something with the line
        print(f"Received: {line}")
        comms.send(f"ACK {line}")
        
        if line == "led-on":
            onboard_led.on()
        
        elif line == "led-off":
            onboard_led.off()
        
        else:
            print("Unknown command.")
    
    #sleep(0.1)
    

Even more commands using a handle_thing(input) style:

Example usage 2
# Built-in modules
from time import sleep

# MicroPython-specific modules
import machine
from machine import Pin


# Custom local modules
import comms
import motors

# Setup the onboard LED
onboard_led = Pin(25, Pin.OUT)


def handle_go(line):
    # split into parts:
    #   go-direction-howlong  (milliseconds)

    # wrap in a try/except block to catch common errors
    # so we don't crash the robot's software
    try:
        go, direction, howlong = line.split("-")
        howlong = int(howlong)
    except:
        msg = "go: Parse error."
        print(msg)
        comms.send(msg)
        return
    
    print(f"go: {direction} for {howlong}")
    
    # do the action
    if direction == "forward":
        motors.forward()
    
    elif direction == "back":
        motors.back()
        
    elif direction == "left":
        motors.left()
        
    elif direction == "right":
        motors.right()
    
    else:
        msg = f"go: Unknown direction: {direction}."
        print(msg)
        comms.send(msg)
        return None
    
    # for the duration
    # this blocks other code
    # TODO: change to async style
    sleep(howlong / 1000)
    motors.stop()
    return True



###########################################
#
# the main thing
#
while True:
    # (try to) get a command
    line = comms.receive()
    
    # nothing?  then quit here and try again
    if line is None:
        continue


    # we have a line!  now do something with it!
    print(f"Received: {line}")
    comms.send(f"ACK {line}")
    
    if line == "led-on":
        onboard_led.on()
    
    elif line == "led-off":
        onboard_led.off()

    # a different way.
    # sub-commands
    elif line.startswith("go-"):
        handle_go(line)
        
    else:
        # send the same message to two different places
        msg = "bot: Unknown command."
        print(msg)
        comms.send(msg)

2.1. asyncio things

These are extras on top of asyncio that add lots more features for multi-tasking. With those extras, you have all of the tools needed to assemble an operating system for your device.

3. Challenge tasks

  • Follow a line down the hallway. Smooth curves. Then right-angle corners (hard!).

  • Avoid running into a wall when travelling forwards.

  • Scan for barriers and move towards the most open place (steer away from walls).

  • Provide audible feedback from the robot.

    • Something is close.

    • Lights are dark / bright.

    • Found a line / lost the line.

    • Received a command from the Internet over MQTT.

  • Scroll the message received over MQTT to the front 8x8 matrix.

4. Close out

  • Take your robot kit home!


1. or it won’t happen…​