Programming Pygame Zero to interface with USB communication from the Pico
Pygame Zero comes installed with Mu. Python by itself is not great at drawing graphics that update quickly or playing sounds, but the Pygame Zero framework is setup for this purpose.
The test data that the Pico prints to the computer and recieves from the keyboard is sent through a type of USB communication called CDC, which emulates anolder protocol usually refered to as "serial" communication. In serial communication, the USB device is given a name called a port, which can be opened to read and write text data. In Windows, the port name is typically the word COM, followed by a number, like COM3 or COM4. You can find the name in the program Device Manager. In Mac and Linux, the port name is typically the word /dev/tty., followed by the word usbserial or usbmodem, followed by a number, like /dev/tty.usbmodem14201. You can find the name by opening the program terminal, and typing "ls /dev/tty.*", and the available ports will be listed.
Ports can only be open by one program at a time, so close the Serial window in Mu before trying to open the port with Pygame Zero code.
The trick for getting a Pico to communicate with Pygame Zero code is to invent a communication protocol, so that the messages can de decyphered.
In the following example, the Pico constantly prints the letter B followed by a number read from the ADC. If a pushbutton is pressed, the letter A is printed. If the letter a is recieved, the servo angle in incremented. If the letter b is recieved, the servo angle is decremented. A special python package, nonblocking_serialinput is used from the Circuit Python Community Bundle to be able to read messages from the computer without calling input(), which would cuase the code to wait until a message was recieved without being able to move on.
The Pygame Zero code opens the port and jumps back and forth between the update() and draw() functions. The ser.readline() function reads the messages from the Pico, but note that the messages contain lots of characters that you usually do not see in the Serial window, so you have to find your message in the string.
Pico code:
# Pico code that talks to Pygame Zero
import board
import pwmio
import time
import digitalio
from analogio import AnalogIn
from adafruit_simplemath import map_range
import nonblocking_serialinput as nb_serialin # from circuitpython community bundle, also need ansi_escape_code folder
servo = pwmio.PWMOut(board.GP15, variable_frequency=True)
servo.frequency = 50 # hz
buttonpin = digitalio.DigitalInOut(board.GP14)
buttonpin.direction = digitalio.Direction.INPUT
potentiometerpin = AnalogIn(board.A0)
servo_angle = 90
# to be able to call input() without blocking the rest of the code
# must use the new my_input.print() instead of the regular print()
my_input = nb_serialin.NonBlockingSerialInput()
while True:
my_input.update() # update the usb communication
input_string = my_input.input() # grab any new data recieved
if input_string is not None:
my_input.print("got: "+input_string)
if ("a" in input_string):
servo_angle = servo_angle + 3
if (servo_angle > 180):
servo_angle = 180
if ("s" in input_string):
servo_angle = servo_angle - 3
if (servo_angle < 0):
servo_angle = 0
# command the rc servo position
servo_duty = int(map_range(servo_angle,0,180,65535*0.5/20,65535*2.5/20))
servo.duty_cycle = servo_duty
# print the potentiometer voltage
my_input.print("B "+str(potentiometerpin.value)+",")
# print if the button is pressed
if (buttonpin.value == 0):
my_input.print("A")
time.sleep(0.05)
Pygame Zero code:
# use pygame zero in Mu to draw graphics and play sounds
# must import pyserial into Third Party Packages using Mu Administration (gear) button in bottom right
import serial
# the name of your port here. On Windows, COM#, on Mac, /dev/tty.usbmodem#
ser = serial.Serial('/dev/tty.usbmodem14201')
print('Opening port: ')
print(ser.name)
# size of the screen to draw
WIDTH = 500
HEIGHT = 500
# open an image from the mu_code/images folder
my_img = Actor('rpi', center = (250,250))
# draw() is called automatically
def draw():
screen.clear()
my_img.draw()
# update is called automatically
def update():
n_bytes = ser.readline() # read a line from the serial port, as bytes
s = str(n_bytes) # turn the bytes into a string
#print(s)
#print(len(s))
# lots of crazy stuff in the string, look for your specific message
if (len(s)>30):
if (s[26] == "A"):
#print("A")
#sounds.boinggg.play() # play a sound from the mu_code/sounds folder, will overlap itself
# play a sound from mu_code/music, but only if it is not already playing
if music.is_playing('boinggg.wav'):
pass
else:
music.play_once('boinggg.wav')
if (s[26] == "B"):
#print("B")
result = s[s.find('B')+1:s.find(',')] # pull the number out of the string
#print(result)
n_int = int(result)
#print(n_int)
my_img.angle = 360*n_int / 65536 # scale the analog read to angle
# this function is automatically called when the keyboard is pressed
def on_key_down(key):
#print(key)
if key == keys.A:
#print("a")
ser.write(("a\n").encode()) # print to the serial port
if key == keys.S:
#print("s")
ser.write(("s\n").encode())