464 lines
11 KiB
Markdown
464 lines
11 KiB
Markdown
MicroPython Workshop
|
|
====================
|
|
|
|
Getting started
|
|
---------------
|
|
|
|
### Operating systems, drivers...
|
|
|
|
You might need drivers for the CP210x USB-to-serial converter, which you can
|
|
get from [the Silicon Labs site][silabs].
|
|
|
|
On Windows, the device should show up as some COM port (e.g. COM3), on Linux as
|
|
e.g. /dev/ttyUSB0, and on OSX as /dev/tty.SLAB_USBtoUART (not as
|
|
/dev/tty.usbserial!)
|
|
|
|
[silabs]: https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers
|
|
|
|
### Installing MicroPython
|
|
|
|
Get a recent build of Pycopy, a fork of MicroPython that we will be using for this workshop. A build of the current state as of 2019-11-24 is [here](firmware.bin).
|
|
|
|
Afterwards, get `esptool` and use it to flash your board, using the correct port:
|
|
|
|
``` sh
|
|
pip install esptool
|
|
esptool.py --port /dev/ttyUSB0 erase_flash
|
|
esptool.py --port /dev/ttyUSB0 --chip esp32 \
|
|
write_flash -z 0x1000 firmware.bin
|
|
```
|
|
|
|
You can now connect to your board's REPL with e.g. `screen` or `putty`:
|
|
|
|
``` sh
|
|
screen /dev/ttyUSB0 115200
|
|
```
|
|
|
|
In Putty, select "Serial" as connection type and use your COM port as port and
|
|
115200 as speed.
|
|
|
|
You'll get a command prompt and be able to execute the first commands
|
|
|
|
``` python
|
|
help()
|
|
print("Hello, world")
|
|
```
|
|
|
|
A reference of all the default libraries can be found on
|
|
[readthedocs.io](https://pycopy.readthedocs.io/en/latest/)
|
|
|
|
### File upload using `ampy`
|
|
|
|
You can use `ampy` to manage files from the CLI:
|
|
|
|
``` sh
|
|
pip install adafruit-ampy
|
|
|
|
export AMPY_PORT=/dev/ttyUSB0
|
|
|
|
# List files
|
|
ampy ls
|
|
|
|
# Print file contents
|
|
ampy get boot.py
|
|
|
|
# Save a file
|
|
ampy get boot.py boot.py
|
|
|
|
# Create some example file
|
|
echo "print('hello, world')" > hello.py
|
|
|
|
# Run a local file, without uploading or saving it
|
|
ampy run hello.py
|
|
|
|
# Save it to the board
|
|
ampy put hello.py
|
|
|
|
# Delete it again
|
|
ampy rm hello.py
|
|
```
|
|
|
|
The contents of `boot.py` are run each time the controller resets. It may
|
|
contain instructions for setting up your network connection, local time,
|
|
peripherals, etc. Make sure not to put an infinite loop in your `boot.py`.
|
|
|
|
Hello, LED
|
|
----------
|
|
|
|
You can control the builtin LED, which is on pin 5:
|
|
|
|
``` python
|
|
from machine import Pin
|
|
led = Pin(5, Pin.OUT)
|
|
|
|
# The LED is connected between GPIO 5 and 3.3 V, so
|
|
# to allow current to flow and turn the LED on, you
|
|
# need a lower voltage on your pin:
|
|
led.value(0)
|
|
|
|
# This is (in this case somewhat confusingly) equivalent to
|
|
led.off()
|
|
|
|
# or
|
|
led(0)
|
|
|
|
# These will all turn it off:
|
|
led.value(1)
|
|
led(1)
|
|
led.on()
|
|
|
|
# You can also use booleans:
|
|
led.value(False)
|
|
led.value(True)
|
|
```
|
|
|
|
### Brightness control with PWM
|
|
|
|
To control the brightness, we will be using [Pulse-Width Modulation][pwm],
|
|
which quickly toggles the LED between on and off at some frequency, while
|
|
varying the time it is on:
|
|
|
|
``` python
|
|
from machine import PWM
|
|
pwm = PWM(led)
|
|
|
|
# Read the frequency
|
|
pwm.freq()
|
|
# Set it
|
|
pwm.freq(1000)
|
|
|
|
# Read and set the duty cycle
|
|
pwm.duty()
|
|
pwm.duty(1023)
|
|
pwm.duty(512)
|
|
pwm.duty(0)
|
|
```
|
|
|
|
Contrary to what some people with previous embedded programming experience
|
|
might expect, the frequency here is not the frequency of the counter, but
|
|
how often the LED toggles per second.
|
|
|
|
The maximum duty cycle is normally 1023 (which applies 3.3 volts for 1023 out
|
|
of 1024 cycles). For high frequencies this might change.
|
|
|
|
Frequencies as low as 1 Hz are possible - you can not use floating point
|
|
numbers as your PWM frequency.
|
|
|
|
[pwm]: https://en.wikipedia.org/wiki/Pulse-width_modulation
|
|
### Reserved pins
|
|
|
|
Some of the GPIOs are reserved for certain functions. Most of their numbers are
|
|
not visible on the TTGO-T8 board:
|
|
|
|
- UART: Pins 1 and 3 are TX/RX - visible as TXD and RXD
|
|
- Flash: 6, 7, 8, 11, 16, 17. Messing with these will probably crash your program.
|
|
- 34-39 are input only and do not have pullups. Only 34 and 35 are available
|
|
on the board at all.
|
|
|
|
All other pins can be freely used for any kind of digital input or output.
|
|
|
|
Pins 2, 4, 12, 13, 14, 15 are connected the the micro-SD slot and can not be
|
|
used while the SD card is being used.
|
|
|
|
Digital and analog input
|
|
------------------------
|
|
|
|
Use `machine.Pin` for digital input
|
|
|
|
``` python
|
|
from machine import Pin
|
|
pin = Pin(4, Pin.IN)
|
|
pin.value()
|
|
|
|
# You can use pullups:
|
|
pin = Pin(4, Pin.IN, Pin.PULL_UP)
|
|
pin = Pin(4, Pin.IN, Pin.PULL_DOWN)
|
|
```
|
|
|
|
When connecting a button between a pin and the supply voltage (3V3 pins), use a
|
|
pull-down - then the normal state will read as 0, and switch to 1 when the
|
|
button is pressed.
|
|
|
|
You can also connect the button between ground (GND) and a pin and use a pull-up,
|
|
now the pin will normally read as 1 and switch to 0 when pressed.
|
|
|
|
For analog input, use `machine.ADC`. By default, the ADC has an input range of
|
|
1.0 V, but this can be changed to up to 3.6 V using `ADC.atten`. Available pins
|
|
are 32 through 35.
|
|
|
|
``` python
|
|
from machine import Pin, ADC
|
|
pin = ADC(Pin(32, Pin.IN, None))
|
|
pin.read()
|
|
|
|
# You can set the resolution, it defaults to 12 bits
|
|
pin.width(ADC.WIDTH_10BIT)
|
|
|
|
pwm = machine.PWM(Pin(5, Pin.OUT))
|
|
while True:
|
|
pwm.duty(1023 - pin.read())
|
|
```
|
|
|
|
Timers
|
|
------
|
|
|
|
MicroPython on the ESP32 has virtual timers and 4 hardware timers. You can use
|
|
a lot more virtual timers, but they are more prone to jitter. In either case,
|
|
you can not allocate memory in a timer, as they are executed from an interrupt
|
|
context.
|
|
|
|
Timers execute a callback, which takes the timer as an argument
|
|
|
|
``` python
|
|
from machine import Timer, Pin
|
|
import utime
|
|
|
|
# Create a virtual timer with ID -1.
|
|
# IDs >= 0 are hardware timers.
|
|
timer = Timer(-1)
|
|
|
|
# Create variables used by the interrupt
|
|
data = [0] * 32
|
|
index = 0
|
|
|
|
# Use the normal LED as output
|
|
led = Pin(5, Pin.OUT)
|
|
|
|
def callback(timer):
|
|
global data, index
|
|
# Store the time since startup (in ms)
|
|
data[index] = utime.ticks_ms()
|
|
# Next write will be at the next index
|
|
# Restarts at index 0 when the array is full!
|
|
index = (index + 1) % 32
|
|
# Toggle the LED
|
|
led(not led())
|
|
|
|
# Run the callback once, after 100 ms
|
|
timer.init(mode = Timer.ONE_SHOT, period = 100, callback = callback)
|
|
|
|
# Run the callback every second
|
|
timer.init(period = 1000, callback = callback)
|
|
|
|
# After a few seconds
|
|
# Most of the values will have the same last three digits
|
|
# You might see something like
|
|
# [ 456, 5662, 6662, 7662, 8662, ... ]
|
|
print(data)
|
|
|
|
# Stop the timer
|
|
timer.deinit()
|
|
```
|
|
|
|
Files
|
|
-----
|
|
|
|
MicroPython has an internal filesystem that is stored after the firmware. You can access it in the usual way:
|
|
|
|
``` python
|
|
import os
|
|
os.listdir("/")
|
|
print( open("/boot.py").read() )
|
|
|
|
# Write to a file. Deletes existing content.
|
|
f = open("data.txt", "w")
|
|
f.write("Hello ")
|
|
f.close()
|
|
|
|
# Append
|
|
f = open("data.txt", "a")
|
|
f.write("world.\n")
|
|
f.close()
|
|
|
|
print(open("data.txt").read())
|
|
```
|
|
|
|
Sleep modes and power consumption
|
|
---------------------------------
|
|
|
|
When transmitting data to a WLAN, power consumption will be 160 - 260 mA (so
|
|
use `WLAN.active(false)` when you don't need it). Even with a deactivated
|
|
modem, the power consumption can still be up to 20 mA when the CPU is running.
|
|
|
|
### Idle
|
|
|
|
In idle mode, the CPU is paused but peripherals keep working.
|
|
|
|
``` python
|
|
import machine
|
|
|
|
# Enter idle mode indefinitely
|
|
machine.idle()
|
|
```
|
|
|
|
Execution continues when an interrupt happens, usually after a few milliseconds
|
|
|
|
### Light sleep
|
|
|
|
In light sleep, most of the microprocessor will be inactive. Power consumption will be at about 0.8 mA.
|
|
|
|
``` python
|
|
import machine
|
|
|
|
# Enter light sleep until the next interrupt
|
|
machine.lightsleep()
|
|
|
|
# Enter lightsleep until the next interrupt, but
|
|
# at most for one second
|
|
machine.lightsleep(1000)
|
|
```
|
|
|
|
### Deep sleep
|
|
|
|
In deep sleep, most of the RAM and all digital peripherals are turned off. Only the RTC and ULP co-processor stay active. Power consumption is about 10 uA. After deep sleep is exited, the ESP32 will reset.
|
|
|
|
``` python
|
|
import machine
|
|
|
|
# Same behaviour as with lightsleep
|
|
machine.deepsleep()
|
|
machine.deepsleep(10000)
|
|
|
|
# To further reduce power consumption, disable pullups:
|
|
p1 = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_HOLD)
|
|
```
|
|
|
|
After the reset, use `machine.reset_cause()` to check if we were in a deep sleep:
|
|
|
|
``` python
|
|
import machine
|
|
|
|
if machine.reset_cause() == machine.DEEPSLEEP_RESET:
|
|
print("Woke up from deep sleep")
|
|
```
|
|
|
|
### RTC
|
|
|
|
The RTC of the ESP32 is not only used to keep track of time, but can also save up to 2 kiB of data during deep sleep. This memory is still volatile, however, and will be deleted if you use the reset button or remove power
|
|
|
|
``` python
|
|
import machine
|
|
rtc = machine.RTC()
|
|
rtc.memory('some data')
|
|
rtc.memory()
|
|
```
|
|
|
|
Network
|
|
-------
|
|
|
|
To create or connect to a WLAN, we will use the `network` module.
|
|
|
|
### Creating a network
|
|
|
|
You can easily create a network with a DHCP server for any clients:
|
|
|
|
``` python
|
|
import network
|
|
wlan = network.WLAN(network.AP_IF)
|
|
wlan.config(essid = "micropython",
|
|
authmode = network.AUTH_WPA2_PSK,
|
|
password = "YELLOWSUBMARINE")
|
|
# You can look at some interface information:
|
|
# Address, subnet mask, gateway, DNS server.
|
|
# Pass in a tuple containing these values to
|
|
# set the information instead.
|
|
wlan.ifconfig()
|
|
```
|
|
|
|
### Connecting to an existing network
|
|
|
|
``` python
|
|
import network
|
|
wlan = network.WLAN(network.STA_IF)
|
|
# Activate the interface
|
|
wlan.active(True)
|
|
# Scan for visible networks
|
|
wlan.scan()
|
|
# Check connection status
|
|
wlan.isconnected()
|
|
# We are not connected. Change that
|
|
wlan.connect("micropython", "YELLOWSUBMARINE")
|
|
```
|
|
|
|
### TCP/UDP sockets
|
|
|
|
``` python
|
|
import usocket
|
|
|
|
# Create a TCP socket
|
|
tcp = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM)
|
|
|
|
# or UDP
|
|
udp = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM)
|
|
|
|
# Listen on a port and accept a connection from a client
|
|
address = usocket.getaddrinfo('0.0.0.0', 42)[0][-1]
|
|
tcp.bind(address)
|
|
tcp.listen()
|
|
conn, remote_address = tcp.accept()
|
|
|
|
# Connect to a server
|
|
address = usocket.getaddrinfo('192.168.4.1', 42)
|
|
tcp.connect(address)
|
|
|
|
# Write data
|
|
tcp.write(b"Hello\n")
|
|
|
|
# Receive data
|
|
conn.read(16)
|
|
conn.readline()
|
|
```
|
|
|
|
HTTP
|
|
----
|
|
|
|
### Requests
|
|
|
|
``` python
|
|
import upip
|
|
upip.install('urequests')
|
|
import urequests
|
|
urequests.get('https://imaginaerraum.de')
|
|
```
|
|
|
|
### Server
|
|
|
|
First, we need to install `picoweb`
|
|
|
|
``` python
|
|
import upip
|
|
upip.install("picoweb")
|
|
upip.install("pycopy-ulogging")
|
|
```
|
|
|
|
This will automatically install `picoweb` and its dependencies into `/lib/` if you have an internet connection.
|
|
|
|
Now you can create simple websites.
|
|
|
|
``` python
|
|
import picoweb
|
|
|
|
app = picoweb.WebApp(__name__)
|
|
|
|
@app.route("/")
|
|
def index(request, response):
|
|
yield from picoweb.start_response(response)
|
|
yield from response.awrite("<html><body>Hello, world</body></html>")
|
|
|
|
app.run("0.0.0.0", 80, debug=True)
|
|
```
|
|
|
|
Next steps
|
|
----------
|
|
|
|
In no particular order:
|
|
|
|
- WebREPL
|
|
- Talking to peripherals with SPI, I2C, ...
|
|
- LED libraries for WS2812, APA102, ...
|
|
- Interrupt handlers
|
|
- Using the SD card
|
|
- Using `uselect` to handle multiple sockets
|
|
- Handling sensor data with `umqtt`
|
|
|