Embedded

Computing the CRC8-ATM CRC in Python

The 8-bit CRC8-ATM polynomial is used in many embedded applications, including Trinamic UART-controlled stepper motor drivers like the TMC2209:

\text{CRC} = x^8 + x^2 + x^1 + x^0

The following code provides an example on how to compute this type of CRC in Python:

def compute_crc8_atm(datagram, initial_value=0):
    crc = initial_value
    # Iterate bytes in data
    for byte in datagram:
        # Iterate bits in byte
        for _ in range(0, 8):
            if (crc >> 7) ^ (byte & 0x01):
                crc = ((crc << 1) ^ 0x07) & 0xFF
            else:
                crc = (crc << 1) & 0xFF
            # Shift to next bit
            byte = byte >> 1
    return crc

This code has been field-verified for the TMC2209.

Posted by Uli Köhler in Algorithms, Embedded, MicroPython, Python

MicroPython ESP32 minimal UART example

This example shows how to use UART on the ESP32 using MicroPython. In this example, we use UART1 which is mapped to pins GPIO9 (RX) and GPIO10 (TX).

from machine import UART
uart = UART(1, 115200) # 1st argument: UART number: Hardware UART #1

# Write
uart.write("test")

# Read
print(uart.read()) # Read as much as possible using

Don’t know how to upload the file to MicroPython so it is automatically run on boot?

Posted by Uli Köhler in Embedded, MicroPython, Python

How to fix ESP32 MicroPython ‘ValueError: pin can only be input’

Problem:

You are trying to initialize an ESP32 pin in MicroPython using

import machine
machine.Pin(34, machine.Pin.OUT)

but you see an error message like

>>> machine.Pin(34, machine.Pin.OUT)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: pin can only be input

Solution:

On the ESP32, pins with numbers >= 34 are input-only pins!

You need to use other pins < 34 if you need output capability!

For reference, see the relevant MicroPython source code section:

// configure mode
if (args[ARG_mode].u_obj != mp_const_none) {
    mp_int_t pin_io_mode = mp_obj_get_int(args[ARG_mode].u_obj);
    if (self->id >= 34 && (pin_io_mode & GPIO_MODE_DEF_OUTPUT)) {
        mp_raise_ValueError("pin can only be input");
    } else {
        gpio_set_direction(self->id, pin_io_mode);
    }
}
Posted by Uli Köhler in Embedded, MicroPython, Python

How to upload files to MicroPython over USB/serial

In this post we will investigate how to connect to a wireless network on boot

First, install ampy – a tool to modify the MicroPython filesystem over a serial connection.

sudo pip3 install adafruit-ampy

Now prepare your script – we’ll use main.py in this example.

Upload the file to the board:

ampy -p /dev/ttyUSB* put main.py

This only takes about 2-4 seconds. In case ampy is still running after 10 seconds, you might need to

  • Stop ampy (Ctrl+C), reset the board using the RESET button and retry the command
  • Stop ampy (Ctrl+C). Detach USB and ensure the board is powered off (and not powered externally). Re-Attach USB and retry the command.
  • In case that doesn’t help, try re-flashing your board with the most recent version of MicroPython. See How to flash MicroPython to your ESP32 board in 30 seconds. This will also clear the internal filesystem and hence remove any file that might cause failure to boot properly.
Posted by Uli Köhler in Embedded, MicroPython, Python

MicroPython ESP32 blink example

This MicroPython code blinks GPIO2 which is connected to the LED on most ESP32 boards.

import machine
import time
led = machine.Pin(2, machine.Pin.OUT)
while True:
    led.value(1)
    time.sleep(1)
    led.value(0)
    time.sleep(1)

Don’t know how to upload the file to MicroPython so it is automatically run on boot?

Posted by Uli Köhler in Embedded, MicroPython, Python

How to upload files to MicroPython using WebREPL using webrepl_cli.py

First, clone the webrepl repository:

git clone https://github.com/micropython/webrepl.git

Now use ampy to initially setup your wifi connection and setup both wifi and WebREPL on on boot (see How to autoconnect to Wifi using MicroPython on your ESP32 board):

import network
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect("MyWifi", "MyWifiPassword")

# Start webrepl
import webrepl
webrepl.start(password="Rua8ohje")

Upload via the serial port using

ampy -p /dev/ttyUSB0 put main.py

You should only have to do this once (except if you break you main.py). If the upload doesn’t work

Go to the directory where webrepl_cli.py is located:

cd webrepl

Now you can upload main.py using

./webrepl_cli.py -p Rua8ohje ../main.py espressif.local:/main.py

You might need to use a different host, but espressif.local seems to work out-of-the-box in many configurations.

The output should look like this:

op:put, host:espressif.local, port:8266, passwd:Rua8ohje.
../main.py -> /main.py
Remote WebREPL version: (1, 12, 0)
Sent 329 of 329 bytes

Now, reset your board using the reset button, so your updated main.py will be executed.

Posted by Uli Köhler in Embedded, MicroPython

How to run WebREPL without webrepl_setup in MicroPython

Problem:

You want to enable WebREPL on your MicroPython board using

import webrepl
webrepl.start()

but it is only showing this error message:

WebREPL is not configured, run 'import webrepl_setup'

However, you want to configure WebREPL programmatically instead of manually running it on every single board.

Solution:

Use

import webrepl
webrepl.start(password="Rua8ohje")

This will circumvent webrepl_setup completely and is compatible with an automated setup process.

Note: At the time of writing this you can only use passwords with 8 characters max! (see How to fix MicroPython WebREPL ValueError in File &#8222;webrepl.py&#8220;, line 72, in start )

Posted by Uli Köhler in Embedded, MicroPython, Python

How to fix MicroPython WebREPL ValueError in File “webrepl.py”, line 72, in start

Problem:

You want to configure your MicroPython WebREPL programmatically using webrepl.start(password="...") but you see a stacktrace like

>>> webrepl.start(password="Rua8ohjedo")
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
 File "webrepl.py", line 72, in start
ValueError:

Solution:

Use a shorter password with 8 characters max:

webrepl.start(password="Rua8ohje")

 

Posted by Uli Köhler in Embedded, MicroPython, Python

How to autoconnect to Wifi using MicroPython on your ESP32 board

In this post we will investigate how to connect to a wireless network on boot

First, install ampy – a tool to modify the MicroPython filesystem over a serial connection.

sudo pip3 install adafruit-ampy

Now download main.py and save it in your current working directory and insert your wifi credentials:

import network
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect("YourWifiName", "EnterYourWifiPasswordHere")

Upload the file to the board:

ampy -p /dev/ttyUSB* put main.py

In case ampy shows no output within 5 seconds, try resetting the board, waiting for 5-10 seconds and retrying the upload using ampy

Note: You can list the files on the board’s filesystem using

ampy -p /dev/ttyUSB0 ls

You can verify the content of main.py using

ampy -p /dev/ttyUSB0 get main.py
Posted by Uli Köhler in Embedded, MicroPython, Python

How to connect your ESP32 MicroPython board to your Wifi in 20 seconds

Didn’t flash MicroPython on your ESP32 board yet? See How to flash MicroPython to your ESP32 board in 30 seconds

Copy this script and enter your Wifi credentials:

import network
station = network.WLAN(network.STA_IF)
station.active(True)
station.connect("YourWifiName", "EnterYourWifiPasswordHere")

Now you need to open a REPL to MicroPython via USB.

If your Wifi is within range of the board and the password is correct, your should see output like

>>> import network
>>> station = network.WLAN(network.STA_IF)
I (457700) wifi: wifi driver task: 3ffd2a80, prio:23, stack:3584, core=0
I (459702) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (459712) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (459832) wifi: wifi firmware version: 10f4364
I (459832) wifi: config NVS flash: enabled
I (459832) wifi: config nano formating: disabled
I (459832) wifi: Init dynamic tx buffer num: 32
I (459842) wifi: Init data frame dynamic rx buffer num: 32
I (459842) wifi: Init management frame dynamic rx buffer num: 32
I (459852) wifi: Init management short buffer num: 32
I (459852) wifi: Init static rx buffer size: 1600
I (459862) wifi: Init static rx buffer num: 10
I (459862) wifi: Init dynamic rx buffer num: 32
>>> station.active(True)
W (459872) phy_init: failed to load RF calibration data (0x1102), falling back to full calibration
I (460012) phy: phy_version: 4102, 2fa7a43, Jul 15 2019, 13:06:06, 0, 2
I (460062) wifi: mode : sta (24:6f:28:b0:28:b4)
True
I (460062) wifi: STA_START
>>> station.connect("MyWifi", "ThisIsMyWifiPassword")
>>> I (461782) wifi: new:<5,0>, old:<1,0>, ap:<255,255>, sta:<5,0>, prof:1
I (462632) wifi: state: init -> auth (b0)
I (462632) wifi: state: auth -> assoc (0)
I (462642) wifi: state: assoc -> run (10)
I (462672) wifi: connected with MyWifi, channel 5, BW20, bssid = 9a:c7:44:33:22:11
I (462672) wifi: pm start, type: 1

I (462672) network: CONNECTED
I (463672) tcpip_adapter: sta ip: 192.168.178.42, mask: 255.255.255.0, gw: 192.168.178.1
I (463672) network: GOT_IP
>>>

Note that the output might take a few seconds to appear since it might take some time to connect to the access point.

Note the IP address (192.168.178.42 in this example)

Posted by Uli Köhler in Embedded, MicroPython

How to get into a MicroPython REPL using USB in 10 seconds

On Linux, use picocom:

picocom /dev/ttyUSB0 -b115200

and press enter to get a REPL (or press the RESET button on the board if that does not work).

You should now see the

>>>

prompt.

Don’t have picocom installed?

sudo apt -y install picocom

Got permission denied errors?

sudo usermod -a -G dialout $USER

then logout and log back in or reboot!

Posted by Uli Köhler in MicroPython

How to flash MicroPython to your ESP32 board in 30 seconds

This script does all the neccessary steps to flash MicroPython to your ESP32 board on Linux. Run this script with your board plugged in via USB:

#!/bin/sh
# Call without arguments
# Download esptool
git clone https://github.com/espressif/esptool.git -b v2.8
cd esptool
# Erase flash. Press the reset button while Connecting.
python3 esptool.py --chip esp32 --port /dev/ttyUSB* erase_flash
# Download firmware
wget -qO micropython.bin https://micropython.org/resources/firmware/esp32-idf4-20191220-v1.12.bin
# Upload firmware to board
python3 esptool.py --chip esp32 --port /dev/ttyUSB* --baud 460800 write_flash -z 0x1000 micropython.bin

Remember to press the RESET button on the board if the script is telling you Connecting.... Not sure which button it is? Just press either one, and if the output doesn’t show Chip is ESP32 within 2 seconds, press the other one.

This script was built for my ESP-WROOM-32 board.

Now you can try to get into a REPL using USB.

Posted by Uli Köhler in Embedded, MicroPython

How to fix Platform IO “No tasks to run found. Configure tasks…”

If you see this message while trying to run a PlatformIO task like Build or Upload:

No tasks to run found. Configure tasks...

you can fix that easily: Open Preferences: Open settings (JSON) in Visual Studio code (the default keybinding to open the action menu is Ctrl+Shift+P).

Then look for this line:

"task.autoDetect": "off"

and delete it.

Now save the file. You can immediately run PlatformIO tasks after saving settings.json without restarting Visual Studio Code !

Posted by Uli Köhler in PlatformIO

How to fix Raspberry Pi GPIO “No access to /dev/mem. Try running as root!”

Problem:

You want to toggle a Raspberry Pi GPIO pin using RPi.GPIO but you see an error message like

Traceback (most recent call last):
  File "gpio.py", line 8, in <module>
    GPIO.setup(pin1, GPIO.OUT)
RuntimeError: No access to /dev/mem.  Try running as root!

Solution:

Add your user to the gpio group:

sudo usermod -a -G gpio $USER

then log out and log back in (or reboot, if that doesn’t help).

The reason for this error is that /dev/gpiomem is only accessible by users belonging to the gpio group. By default, only the pi user belongs to the gpio group – hence, if you run a script as any users other than pi or root, you will see that error message.

Posted by Uli Köhler in Electronics, Raspberry Pi

mbed STM32F4DISCOVERY simple LED demo

This demo shows you how to control the STM32F4DISCOVERY LEDs using mbed.
I use mbed from inside PlatformIO.

#include <mbed.h>

DigitalOut greenLED(PD_12);
DigitalOut orangeLED(PD_13);
DigitalOut redLED(PD_14);
DigitalOut blueLED(PD_15);

int main() {
  while(1) {
    // Cycle LEDs in order
    // NOTE: You can toggle a LED using
    //  blueLED = !blueLED;
    blueLED = 0;
    greenLED = 1;
    wait(0.25);
    greenLED = 0;
    orangeLED = 1;
    wait(0.25);
    orangeLED = 0;
    redLED = 1;
    wait(0.25);
    redLED = 0;
    blueLED = 1;
    wait(0.25);
  }
}

 

Posted by Uli Köhler in mbed, PlatformIO

STM32F4DISCOVERY LED pin reference / pinout

The STM32F4 DISCOVERY board has these LEDs:

  • Green LED: PD12, active-high (LED emits light when PD12 is high)
  • Orange LED: PD13, active-high (LED emits light when PD13 is high)
  • Red LED: PD14, active-high (LED emits light when PD14 is high)
  • Blue LED: PD15, active-high (LED emits light when PD15 is high)
Posted by Uli Köhler in Electronics, Embedded

Is pypng 16-bit PNG encoding faster using pypy on the Raspberry Pi?

In our previous post How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng we investigated how to use the pypng library to save 10-bit raw Raspberry Pi Camera images to 16-bit PNG files.

However, saving a single image took ~26 seconds using CPython 3.7.3. Since pypy can provide speedups to many Python workloads, we tried using pypy3 7.0.0 (see How to install pypy3 on the Raspberry Pi) to speed up the PNG encoding.

Results

pypng PNG export seems to be one of the workloads that are much slower using pypy3.

  • CPython 3.7.3: Encoding took 24.22 seconds
  • pypy3 7.0.0: Encoding took 266.60 seconds

Encoding is more that 10x slower when using pypy3!

Hence I don’t recommend using pypy3 to speed up pypng encoding workloads, at least not on the Raspberry Pi!

Full example

This example is derived from our full example previously posted on How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng:

#!/usr/bin/env python3
import time
import picamera
import picamera.array
import numpy as np
import png

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

# Write to PNG
print("Writing 16-bit PNG...")
t0 = time.time()
with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)
t1 = time.time()

print(f"Encoding took {(t1 - t0):.2f} seconds")

 

Posted by Uli Köhler in Python, Raspberry Pi

How to install pypy3 on the Raspberry Pi

This post shows you an easy way of getting pypy3 running on the Raspberry Pi. I used Raspbian Buster on a Raspberry Pi 3 for this example. On Raspbian buster this will install pypy3 7.x!

First install pypy3 and virtualenv:

sudo apt update && sudo apt -y install pypy3 pypy3-dev virtualenv

Now we can create a virtualenv to install pypy packages into:

virtualenv -p /usr/bin/pypy3 ~/pypy3-virtualenv

Now we can activate the virtualenv. You need to do this every time you want to use pypy, for each shell / SSH connection separately:

source ~/pypy3-virtualenv/bin/activate

If your shell prompt is now prefixed by (pypy3-virtualenv) you have successfully activated the virtualenv:

(pypy3-virtualenv) uli@raspberrypi:~ $

Now python points to pypy3 and pip will install packages locally to ~/pypy3-virtualenv.

Now you can use e.g.

python3 myscript.py

to run your script (both python and python3 will point to pypy3 if you activated the virtual environment!).

Note: Installing pypy3-dev is not strictly neccessary to get pypy3 running, but you need it in order to compile native librarie like numpy.

Posted by Uli Köhler in Python, Raspberry Pi

How to save Raspberry Pi raw 10-bit image as 16-bit PNG using pypng

In our previous post How to capture RaspberryPi camera 10-bit raw image in Python we showed how you can use the picamera Python library to capture raw 10-bit image data.

The PNG image format supports storing 16-bit image data. This post shows you how to do that using the NumPy arrays we generated in our previous post. We are using the pypng library.

with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)

Note that the resulting PNGs are ~9.9 Megabytes in size and saving them using pypng takes about 27 seconds to save the image on my Raspberry Pi 3!

For comparison, the raw NumPy data is ~29 Megabytes whereas the compressed NumPy data is 9.3 Megabytes,

  • Raw NumPy data (np.save): 29 Megabytes, takes 0.11 seconds to save.
  • Compressed NumPy data (np.savez_compressed): 9.3 Megabytes, take 12 seconds to save.

So if your motivation for using PNG is to save space, you might be better off using NumPy compressed data, especially if you need to save many camera frames in quick succession and hence are limited.

In case you need to use PNGs, you might want to check Pypy since pypng is a pure Python library and hence might benefit from Pypy’s increased execution speed. However, in practice, pypy3 is more than 10x slower. Please read our detailed analysis at Is pypng 16-bit PNG encoding faster using pypy on the Raspberry Pi?

Full example:

#!/usr/bin/env python3
import picamera
import picamera.array
import numpy as np
import png

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

# Write to PNG
print("Writing 16-bit PNG...")
with open('16bit.png', 'wb') as outfile:
    writer = png.Writer(width=rawimg.shape[1], height=rawimg.shape[0], bitdepth=16, greyscale=False)
    # rawimg is a (w, h, 3) RGB uint16 array
    # but PyPNG needs a (w, h*3) array
    png_data = np.reshape(rawimg, (-1, rawimg.shape[1]*3))
    # Scale 10 bit data to 16 bit values (else it will appear black)
    # NOTE: Depending on your photo and the settings,
    #  it might still appear quite dark!
    png_data *= int(2**6)
    writer.write(outfile, png_data)

 

Posted by Uli Köhler in Raspberry Pi

How to capture RaspberryPi camera 10-bit raw image in Python

You can use the picamera Python library to capture a raw sensor image of a camera attached to the Raspberry Pi via CSI:

#!/usr/bin/env python3
import picamera
import picamera.array
import numpy as np

# Capture image
print("Capturing image...")
with picamera.PiCamera() as camera:
    with picamera.array.PiBayerArray(camera) as stream:
        camera.capture(stream, 'jpeg', bayer=True)
        # Demosaic data and write to rawimg
        # (stream.array contains the non-demosaiced data)
        rawimg = stream.demosaic()

rawimg is a numpy uint16 array of dimensions (w, h, 3), e.g. (1944, 2592, 3) and contains integer values from 0 to 1023.

You can, for example, save it in a NumPy file using

np.save("rawimg.npy", rawimg) # Reload with np.load("rawimg.npy")

or save it in a compressed format using

np.savez_compressed("rawimg.npz", rawimg) # Reload with np.load("rawimg.npz")
Posted by Uli Köhler in Python, Raspberry Pi