Last day
1. Python down to the machine
First thing today![1]
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
-


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.
comms.py
onto your Picofrom 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()
# 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:
# 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)
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.