386 lines
8.2 KiB
Markdown
386 lines
8.2 KiB
Markdown
|
MicroPython Workshop
|
||
|
====================
|
||
|
|
||
|
Getting started
|
||
|
---------------
|
||
|
|
||
|
### 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:
|
||
|
|
||
|
``` 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
|
||
|
```
|
||
|
|
||
|
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
|
||
|
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.
|
||
|
|
||
|
Hello, LED
|
||
|
----------
|
||
|
|
||
|
You can control the builtin LED, which is on pin 5:
|
||
|
|
||
|
``` python
|
||
|
from machine import Pin
|
||
|
led = Pin(5, Pin.OUT)
|
||
|
|
||
|
# The values are inverted, so this turns
|
||
|
# the LED on:
|
||
|
led.off()
|
||
|
|
||
|
# and this turns it off:
|
||
|
led.on()
|
||
|
|
||
|
# You can also use numbers or booleans:
|
||
|
led.value(False)
|
||
|
led.value(1)
|
||
|
```
|
||
|
|
||
|
### Brightness control with PWM
|
||
|
|
||
|
``` python
|
||
|
from machine import PWM
|
||
|
pwm = PWM(led)
|
||
|
# Read the frequency
|
||
|
pwm.freq()
|
||
|
# Set it
|
||
|
pwm.freq(20000)
|
||
|
# Read and set the duty cycle
|
||
|
pwm.duty()
|
||
|
pwm.duty(1023)
|
||
|
pwm.duty(512)
|
||
|
pwm.duty(0)
|
||
|
```
|
||
|
|
||
|
### Reserved pins
|
||
|
|
||
|
- UART: Pins 1 and 3 are TX/RX
|
||
|
- 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.
|
||
|
|
||
|
All other pins can be used for PWM.
|
||
|
|
||
|
Pins 2, 4, 12, 13, 14, 15 are connected the the micro-SD slot.
|
||
|
|
||
|
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)
|
||
|
```
|
||
|
|
||
|
For analog input, use `machine.ADC`. By default, the ADC has an input range of 1.00 V, but this can be changed to up to 3.6 V. Available pins are 32 through 39.
|
||
|
|
||
|
``` 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
|
||
|
import utime
|
||
|
|
||
|
# Create a virtual timer with ID -1.
|
||
|
# IDs >= 0 are hardware timers.
|
||
|
timer = Timer(-1)
|
||
|
|
||
|
data = [0] * 32
|
||
|
index = 0
|
||
|
|
||
|
def callback(timer):
|
||
|
global data, index
|
||
|
data[index] = utime.ticks_ms
|
||
|
index = (index + 1) % 32
|
||
|
|
||
|
# 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 some time
|
||
|
print(data)
|
||
|
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", "a")
|
||
|
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 urequests
|
||
|
urequests.get('https://hotspot.vodafone.de/api/v4/login?loginProfile=6')
|
||
|
```
|
||
|
|
||
|
### 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`
|
||
|
|