Frédéric Boulanger
CentraleSupélec LRI
Département informatique Équipe MODHEL
3 rue Joliot-Curie Bât 650 Ada Lovelace, Université Paris Sud
F-91192 Gif-sur-Yvette cedex, France Rue Noetzlin, 91190 Gif-sur-Yvette, France
Téléphone : +33 [0]1 69 85 14 84
Télécopie : +33[0]1 69 85 14 99
frederic.boulanger@centralesupelec.fr frederic.boulanger@lri.fr
MicroPython

Contents MicroPython is a version of Python 3 which runs on microcontrollers.

I use it on a pyboard for hands-on lab sessions in a computer architecture course (in French). It is like an Arduino on steroids, with an embedded Python 3 interpreter, which makes it very suitable for teaching in French “grandes écoles” since all students in prep classes now use Python in their “computer science” course.

Using the pyboard

When plugged into a USB port of a computer, the pyboard appears as a disk. You can edit the main.py file on this disk, then eject it, and press the RST button on the pyboard to reboot it and execute main.py.

However, there is a more interactive way of playing with the pyboard which has a REPL (Read-Eval-Print Loop). For this, you need to connect to the /dev/ttyACM0 (Linux) or /dev/tty.usbmodemXXXX (MacOS) using a serial communication program. The Unix screen command works fine, but I prefer to use minicom.

Pyterm Pyterm

The following script, which I use to call pyterm, will connect to a plugged pyboard using minicom:

#!/usr/bin/python
import sys
import os
import fnmatch

"""
Usage: pyterm [device]
Calls minicom on "device". If device is not given, looks for /dev/ttyACM* devices
(the name of the pyboard under Linux) or /dev/tty.usbmodem* devices (name of the
pyboard under MacOS).
"""
def main():
	if len(sys.argv) > 1:
		ttymodems = [sys.argv[1]]
	else:
	    # look for pyboard v1 under Linux
		ttymodems = fnmatch.filter(os.listdir('/dev'), 'ttyACM*')
		if len(ttymodems) == 0:
            # look for pyboard v1 under MacOS
			ttymodems = fnmatch.filter(os.listdir('/dev'), 'tty.usbmodem*')
		if len(ttymodems) == 0:
            # look for wipy under MacOS
			ttymodems = fnmatch.filter(os.listdir('/dev'), 'tty.usbserial-*')
		if len(ttymodems) == 0:
			print('Error: no pyboard found.')
			sys.exit(1)
		ttymodems[0] = '/dev/' + ttymodems[0]
	os.system('minicom -D '+ttymodems[0])

if __name__ == "__main__":
    main()

Once connected, you can type Python commands and discover many possibilities of the pyboard. For instance:

led = pyb.LED(1)
led.on()

will turn on the red LED on the pyboard.

led = pyb.LED(1)
def blink(timer):
  led.toggle()
tim = pyb.Timer(4, freq=2, callback=blink)

will make the red LED blink at 1Hz (it is toggled on/off at 2Hz).

You will find more information about what you can do with the pyboard in the MicroPython tutorial.

Loading code into the pyboard Loading code

For writing more complex code, there are two possibilities:

  1. edit the files on the pyboard, and load them into the REPL using import
  2. edit the files on your computer, and paste them into the REPL

The first solution works fine and is the only way to work with code that is split into different modules.

The second solution is perhaps easier, but you cannot paste too much text in the REPL without getting errors. You have to switch to the raw REPL by hitting Ctrl-A, pasting your code, and hitting Ctrl-D to finish the transfer. Your code will be running on the pyboard. Hit Ctrl-C to kill it if it does not terminate by itself, then hit Ctrl-B to exit the raw REPL and switch back to the friendly REPL. You can perform a soft reset of the pyboard by hitting Ctrl-D.

Note than when using minicom, you have to hit Ctrl-A twice to send a Ctrl-A to the REPL because Ctrl-A is by default the escape key of minicom. You can use the paste file command in minicom to send a file to the REPL. For this:

  1. hit Ctrl-A twice to enter the raw REPL
  2. hit Ctrl-A Y to execute the paste file minicom command
  3. navigate to your file in the file selection screen that appears and select it
  4. hit Ctrl-D to finish the transfer

Your file is then loaded. If it contains just definitions, hit Ctrl-B to exit the rawREPL and use the definitions in the regular REPL.

Pyboard.py

Another way of executing code on the pyboard is to use the pyboard.py script available in the tools directory of the MicroPython git repository. Exit minicom with Ctrl-A X, then load your file onto the pyboard with pyboard.py file_to_load.py. You can connect to the REPL by launching minicom again.

You can modify the pyboard.py script to find the pyboard device automatically by changing the beginning of the main function as follows:

def main():
    import argparse
    import os
    import fnmatch
    # look for pyboard v1 under Linux
    ttymodems = fnmatch.filter(os.listdir('/dev'), 'ttyACM*')
    if len(ttymodems) == 0:
        # look for pyboard v1 under MacOS
        ttymodems = fnmatch.filter(os.listdir('/dev'), 'tty.usbmodem*')
    if len(ttymodems) == 0:
        # look for wipy under MacOS
        ttymodems = fnmatch.filter(os.listdir('/dev'), 'tty.usbserial-*')
    if len(ttymodems) == 0:
        print('Error: no pyboard found.')
        sys.exit(1) 
    cmd_parser = argparse.ArgumentParser(description='Run scripts on the pyboard.')
    cmd_parser.add_argument('--device', default='/dev/' + ttymodems[0], help='the serial device of the pyboard')

An already patched version (may not be up to date with respect to the git repository) is available.

Fritzing parts

I use Fritzing to draw my sketches. I have made a Fritzing part for the pyboard. You can also get the SVG pictures I made for the PCB, the schematics and the icon.

You can see how it looks on the example below:

I have also made a Fritzing part for an 8x8 red LED matrix with a MAX7219: RedLEDmatrixWithMAX.fzpz. The SVG pictures for the breadboard, PCD and icon, and for the schematic are also available: RedLEDmatrixWithMAX7219.svg and RedLEDmatrixWithMAX7219_schematic.svg.

Code samples

Here are some code samples that work on the pyboard. I give them here, hoping they can be useful to someone, but there is no guaranty about their correctness or accuracy. I welcome any constructive remark about this code since I am not an expert in Python nor in coding for microcontrollers.

Using the MMA7660 accelerometer of the pyboard Accelerometer

The pyboard has a builtin 3 axis accelerometer. You can find how it is interfaced with the pyboard on the schematic view. When powered on, it appears on I2C bus 1, and the documentation of the chip is also available. Although the MicroPython library contains an Accel class to access the data provided by the accelerometer, I have written an MMA7660 class which gives more in-depth control of the chip and allows you to configure it for generating interrupts.

As an example, here is how you can code a spirit level using this module and an 8x8 LED matrix (see below for the module used to drive the matrix):

#
# SpiritLevel.py
#
# frederic.boulanger@centralesupelec.fr
# 2015-06-16
#
# This code uses the built-in MMA7660 accelerometer of the pyboard and
# an attached 8x8 LED matrix driven by a MAX7219 to build a spirit level.
# Everything is handled using interrupts.
#
# SpiritLevel.py by CentraleSupélec is licensed under a
# Creative Commons Attribution-ShareAlike 4.0 International License.
#

# Import the MMA7660 driver
from MMA7660 import *
# Import the LED matrix driver
from LEDMatrixWithMAX import *

mma = MMA7660()             # our accelerometer
led = LEDMatrixWithMAX(2)   # the LED matrix, connected on SPI bus 2

accel = bytearray(3)        # buffer for reading data samples from the accelerometer

# Buffer for the window avarage
wsize = 16          # width of the window
xbuf = [0] * wsize  # buffer for x acceleration
ybuf = [0] * wsize  # buffer for y acceleration
idx = 0             # current index for writing into the buffer

"""
Compute the integer mean of a buffer
"""
def mean(buf):
	m = 0
	for i in range(len(buf)):  # cannot use "for x in buf" in an interrupt handler
		m += buf[i]
	return m // len(buf)

"""
Read the accelerometer data, update the average window,
and move the bubble on the LED matrix accordingly.
"""
def moveBubble(line):
	global idx

	mma.getSample(accel)      # get a sample of data from the accelerometer
	x = accel[0]              # acceleration along the x axis
	if x > 31:                # interpret as 2's complement
		x -= 64
	xbuf[idx] = x             # update window average buffer
	y = accel[1]              # same processing for y axis acceleration
	if y > 31:
		y -= 64
	ybuf[idx] = y
	idx = (idx + 1) % wsize   # advance buffer index
	x = mean(xbuf) + 1        # get the mean x axis average over the window
	y = mean(ybuf) + 1        # get the mean y axis average over the window
	x = (6 * x) // 32         # scale the value from -32/31 to -6/+5
	x += 3                    # center of the matrix is (3,3)
	if x < 0: x = 0           # avoid indexing out of range
	if x > 6: x = 6
	x = 6 - x                 # reverse x axis display
	y = (6 * y) // 32         # same processing for the y axis
	y += 3
	if y < 0: y = 0
	if y > 6 : y = 6
	led.clearBitmap()         # clear the LED matrix
	led.setPixel(x, y, True)  # switch on 4 LEDs (the bubble) on the matrix
	led.setPixel(x+1, y, True)
	led.setPixel(x, y+1, True)
	led.setPixel(x+1, y+1, True)
	led.updateDisplay()       # update the display

# Get 16 samples per second
mma.setActiveSamplingRate(MMA7660.AM16)
# Install the moveBubble function as interrupt handler
mma.setInterruptHandler(moveBubble)
# Enable interrupts when the accelerometer data is refreshed
mma.enableInterrupt(MMA7660.GINT)
# Switch the accelerometer to active mode
mma.on(True)
# Switch the LED matrix on
led.on(True)

Using a MAX7219 to drive LED displays MAX7219

The MAX7219 provides a serial interface for driving LED displays with up to 8 7-segment digits, or 8x8 LED matrices. Here is a MAX7219 module to communicate with a MAX7219 and display digits or control individual segments.

Built on this module, I also have a LEDMatrixWithMAX module for controlling an 8x8 LED matrix, addressing individual pixels and even displaying pictures.

Using a HT16K33 to drive LED displays HT16K33

The 7-segment displays with backpack by Adafruit use an HT16K33 to control the display through an I2C interface. Here is a module to use them with the pyboard, with a short documentation and an example of use:

from led4x7 import LED4x7

disp = LED4x7() # LED4x7 at address 0x70 on I2C bus 1
disp.on()

disp.displayNumber(1234)
pyb.delay(1000)

disp.displayString('cool')

disp.set_brightness(15)
for level in range(15,-1,-1):
	disp.set_brightness(level)
	pyb.delay(200)

for level in range(15):
	disp.set_brightness(level)
	pyb.delay(200)
disp.clear()

snake = (
	0b00000001,
	0b10000010,
	0b01000000,
	0b10010000,
	0b00001000,
	0b10000100,
	0b01000000,
	0b10100000
)
sl = len(snake)
for i in range(100):
	for d in range(4):
		disp.display(d, snake[(i + d) % sl])
	pyb.delay(50)
disp.clear()

disp.displayDigit(0,4)
pyb.delay(500)
disp.displayDigit(1,3)
pyb.delay(500)
disp.displayDigit(2,2)
pyb.delay(500)
disp.displayDigit(3,1)
pyb.delay(500)

for i in range(10000):
	disp.displayNumber(i)
	pyb.delay(3)

disp.setDots(True)

for i in range(4):
	disp.display(i, 0xFF)

disp.blink(LED4x7.blink2Hz)
pyb.delay(2000)

disp.displayString('mPython = cool StuFF')

disp.blink(LED4x7.blinkOff)

Firmware update

In order to update the firmware of the pyboard, you must place it into DFU (Device Firmware Update) mode. For this, disconnect everything from the pyboard, connect the DFU pin to the 3V3 pin (they are next to the X16 and X17 pins), and connect your pyboard to your computer with a USB cable.

The latest firmware for your pyboard can be found at http://micropython.org/download/.

To upload it to the pyboard, you need a DFU utility. You can use dfu-util:

sudo dfu-util --alt 0 -D <firmware.dfu>

or the pydfu Python script:

python pydfu.py -u <firmware.dfu>

where <firmware.dfu> is the exact name of the firmware you want to put on the pyboard.

pydfu.py relies on libusb and the PyUSB module.

Detailed instructions can be found on https://github.com/micropython/micropython/wiki/Pyboard-Firmware-Update.