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("Hello, world") 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`