Electronics

How to run PlatformIO serial monitor from the command line

First, activate the PlatformIO virtual environment which will give you access to the pio script:

source ~/.platformio/penv/bin/activate

Now, if you have – for example – an environment called esp32dev listed in platformio.ini:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

you can build & upload using the following code:

pio run -e esp32dev -t monitor

You can also combine both commands (the virtual env activation and the pio run command) into a single line of shell script:

source ~/.platformio/penv/bin/activate && pio run -e esp32dev -t monitor
Posted by Uli Köhler in PlatformIO

How to run PlatformIO build & upload from the command line

First, activate the PlatformIO virtual environment which will give you access to the pio script:

source ~/.platformio/penv/bin/activate

Now, if you have – for example – an environment called esp32dev listed in platformio.ini:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino

you can build & upload using the following code:

pio run -e esp32dev -t upload

You can also combine both commands (the virtual env activation and the pio run command) into a single line of shell script:

source ~/.platformio/penv/bin/activate && pio run -e esp32dev -t upload
Posted by Uli Köhler in PlatformIO

A Python SLIP decoder using serial_asyncio

The following Python script receives SLIP-encoded data from a serial port (/dev/ttyACM0 in this example) and decodes the SLIP messages using the fully asynchronous (asyncio-based) serial_asyncio library which you can install using

pip install -U pyserial-asyncio

You also need to install ansicolors for colored printing on the console:

pip install -U ansicolors

Full source code

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
__author__ = "Uli Köhler"
__license__ = "CC0 1.0 Universal"

import asyncio
from colors import red
import serial_asyncio

SLIP_END = 0o300
SLIP_ESC = 0o333
SLIP_ESCEND = 0o334
SLIP_ESCESC = 0o335

def handle_slip_message(msg):
    print(f"Received message of length", len(msg))

class SLIPProtocol(asyncio.Protocol):
    def connection_made(self, transport):
        self.msg = bytes() # Message buffer
        self.transport = transport
        print('port opened', transport)
        transport.serial.rts = False  # You can manipulate Serial object via transport
        # Send "enter" to prompt output
        self.buf = b''

    def check_for_slip_message(self):
        # Identify end of message in data
        decoded = []
        last_char_is_esc = False
        for i in range(len(self.buf)):
            c = self.buf[i]
            if last_char_is_esc:
                # This character must be either
                # SLIP_ESCEND or SLIP_ESCESC
                if c == SLIP_ESCEND: # Literal END character
                    decoded.append(SLIP_END)
                elif c == SLIP_ESCESC: # Literal ESC character
                    decoded.append(SLIP_ESC)
                else:
                    print(red("Encountered invalid SLIP escape sequence. Ignoring..."))
                    # Ignore bad part of message
                    self.buf = self.buf[i+1:]
                    break
                last_char_is_esc = False # Reset state
            else: # last char was NOT ESC
                if c == 192: # END of message
                    # Remove current message from buffer
                    self.buf = self.buf[i+1:]
                    # Emit message
                    return bytes(decoded)
                elif c == SLIP_ESC:
                    # Handle escaped character next 
                    last_char_is_esc = True
                else: # Any other character
                    decoded.append(c)
        # No more bytes in buffer => no more message
        return None

    def data_received(self, data):
        # Append new data to buffer
        self.buf += data
        while True:
            msg = self.check_for_slip_message()
            if msg is None:
                break # Need to wait for more data
            else: # msg is not None
                handle_slip_message(msg)

    def connection_lost(self, exc):
        print('port closed')
        self.transport.loop.stop()

    def pause_writing(self):
        print('pause writing')
        print(self.transport.get_write_buffer_size())

    def resume_writing(self):
        print(self.transport.get_write_buffer_size())
        print('resume writing')

loop = asyncio.get_event_loop()
coro = serial_asyncio.create_serial_connection(loop, SLIPProtocol, '/dev/ttyACM0', baudrate=115200)
transport, protocol = loop.run_until_complete(coro)
loop.run_forever()
loop.close()

 

Posted by Uli Köhler in Embedded, Python

How to initialize your KiCAD 6 project on the command line

For the KiCAD 5 version of this script see How to initialize your KiCAD 5 project on the command line

TL;DR:

Inside the directory where you want to create the project, run

wget -qO- https://techoverflow.net/scripts/kicad6-init.sh | bash /dev/stdin MyProject

You should replace MyProject (at the end of the command) with your project name.

Note: This will initialize an empty KiCAD project without any libraries. This is equivalent to creating a new project in KiCAD itself (using the GUI).

Continue reading →

Posted by Uli Köhler in Electronics, KiCAD, Shell

How to fix KiBot ‘KiBoM not installed or too old’

Problem:

When running kibot-check, you see the following warning message:

* KiBoM not installed or too old
  Visit: https://github.com/INTI-CMNB/KiBoM
  Download it from: https://github.com/INTI-CMNB/KiBoM/releases
  - Mandatory for `kibom`

but even installing kibom using pip install kibom, the warning does not disappear.

Solution:

You need to install a specific fork of KiBom:

pip install git+https://github.com/INTI-CMNB/KiBoM

After that, the warning will disappear.

Posted by Uli Köhler in Electronics, KiCAD, Python

How to see PlatformIO actual commands during build or upload

Having a look at the actual commands being used by PlatformIO is pretty easy:

Instead of clicking Build or Upload, open the Advanced folder and select Verbose Build or Verbose Upload.

This will show you all raw commands such as esptool.py commands that are being run.

Posted by Uli Köhler in PlatformIO

How to fix /dev/ttyUSB0 or /dev/ttyACM0 permission error

Problem:

When trying to connect to a USB device, you see an error like

[Errno 13] Permission denied: '/dev/ttyUSB0'

and the USB connection can’t be established.

Solution:

On Debian & Ubuntu, the /dev/ttyUSBx and /dev/ttyACMx devices are owned by the dialout group. Therefore, the solution is to add your user to the dialout group:

sudo usermod -a -G dialout $USER

After that, you need to logout completely and login again (if in doubt, reboot your computer)!

While you can sometimes quickly solve this issue by running whatever command you want to use as root using sudo, this is not a recommended solution since it will allow the program you’re calling full access to the entire computer – not just access restricted to the USB device. Therefore, this approach poses a significant security risk and additionally, some programs don’t work properly when called using sudo.

Posted by Uli Köhler in Embedded, Linux

How to install ESP32 esptool / esptool.py on Ubuntu

The recommended way is to install the current version using pip since the version installed using apt might be out of date:

sudo pip install esptool

After that, you can use esptool.py – note that you need to call it as esptool.py, not just as esptool!

In case you are missing pip , install python3-dev using apt:

sudo apt -y install python3-pip

 

Posted by Uli Köhler in ESP8266/ESP32, Linux, Python

Which MikroTik switch can you use with 100M SFP modules?

Generally, 100M SFP modules can not be used with SFP+ ports. They sometimes can be used with SFP ports, however there is no guarantee it will work properly until you’ve actually tested the compatibility of the hardware!

Besides using a 100M SFP Module with a 100M-compatible SFP port, there is also the possibility of using a SFP Module with integrated converter. FS offers such a device for ~55€ but at the moment I do not know if it is compatible with an Mikrostil device.

Compatible devices

The MikroTik help page lists the CRS106-5S-1C as being compatible with both 100M and 1G SFP modules:

This unit is compatible with 100Mbit and 1.25G SFP modules

It has 5 SFP ports and 1 Combo SFP or GigE port.

Furthermore, the MikroTik wiki has a list of devices compatible with 100M fiberoptic transceivers – at the time of writing this post:

  • CCR1009-7G-1C
  • CCR1009-7G-1C-1S+
  • CRS106-1C-5S
  • CRS328-4C-20S-4S+
  • LHG XL 52 ac
  • RBD22/D23/mANTBox 52 15s/NetMetal ac²

Besides manually searching the MikroTik site for other compatible devices, I also used Google to search for similar sentences on the MikroTik site. I could not find any other MikroTik device for which any statement about 100Mbit SFP compatibility is being explicitly made.

Incompatible devices

For the following devices I have checked the respective MikroTik help page and it does not list compatibility with 100M SFP modules. This does not automatically mean they aren’t compatible but it’s much less likely. Possibly the help page will be updated in the future to indicate compatibility. I have not physically tested any of those devices with 100M transceivers.

  • CRS310-1G-5S-4S+IN
  • CRS112-8P-4S-IN
  • hEX S
  • CRS109-8G-1S-2HnD-IN
  • CRS212-1G-10S-1S+IN

Often, the help pages with read something like Compatible with 1.25G SFP modules. This means that standard 100Mbit SFP modules are incompatible.

Posted by Uli Köhler in Electronics, MikroTik, Networking

Which Ethernet PHY to use for 100Base-FX (SFP) operation?

For new designs I primarily recommend the Texas Instruments DP83822. It comes in a 5x5mm QFN package and provides RMII, MII and even RGMII interfaces to the Microcontroller or other Ethernet MAC.

Be sure to select the DP83822xF – the F means Fiber!

Since at the time of writing this article, the DP83822 has supply shortages, the following alternatives are available for 100Base-FX operation:

  • DP83869HM is a Gigabit Ethernet transceiver that supports 100Base-FX but does not support MII or RMII (only the Gigabit interfaces RGMII & SGMII)
  • DP83620 is a plain 10/100 PHY with RMII & fiber support
  • BCM5221 (MII & RMII) & BCM5241 (MII) are plain old 10/100 PHYs but in typical broadcom fashion, they don’t even give you the datasheet to download on their website. But you can find it via Google
  • BCM5248 is a 8-port PHY with fiber support
  • ST802RT1 (LQFP48)
  • Marvell’s 88E3015 & 88E3018 feature fiber support and have good documentation on the fiber interface. They are available in QFN packages but only support MII & RGMII – RMII is not supported !
  • KS8721BL, KS8721SL & KSZ8721CL (LQFP/SSOP)
  • KSZ8001L (LQFP/SSOP)
  • KSZ8041FTL (LQFP/SSOP – you must buy the FTL variant!)
  • (LQFP/SSOP)
  • The VSC8211 is a Gigabit Ethernet PHY but supports 100Base-FX & RMII interface. It is available in a 117-pin BGA package and hence rather difficult to use.
  • The LAN9355 3-port Ethernet switch features two 100Base-FX fiber interfaces and a RMII interface. It is more complex to use than a standalone PHY but can forward traffic without software interaction.
  • The KSZ8893FQL 3-port Ethernet switch features one 100Base-FX fiber interface and a RMII interface. It is rather expensive
  • The Cortina LXT971A is a simple 100Base-FX PHY from a rather unknown manufacturer. It only has MII, not RMII ! Cortina appears to have been bought by Intel.
  • LU3X34FTR is a 4-port 10/100 PHY with fiber support

Compared to the DP83822, within the context of 100Base-FX operation, there are few technical differences in whether you use the DP83822. In my experience, Ethernet PHYs are mostly difference with regards to their electrical immunity (ESD and so on) which is not really relevant in the fiber context unless someone directly touches the PCB, and the ability to compensate for a degrated Ethernet signal (which is not really relevant for fiber contexts). The only real difference between the DP83822 and many other parts is that the DP83822 comes in a small VQFN package, which the Micrel/Microchip KSZ devices come in much larger SSOP or LQFP packages. My recommendation is to select based on availability first, on size second.

Posted by Uli Köhler in Electronics, Networking

How to enable USB-C host mode on Raspberry Pi

If you want to connect an USB device such as a 3D printer mainboard to your Raspberry Pi 4 using the USB-C connector as opposed to the larger USB-A connector, you need to first configure the Raspberry Pi kernel to use host mode for the USB-C connector.

To temporarily enable it:

sudo modprobe -r dwc2 && sudo dtoverlay dwc2 dr_mode=host && sudo modprobe dwc2

This method has the advantage of not requiring a reboot.

To permanently enable it:

Edit /boot/config.txt and add

dtoverlay=dwc2,dr_mode=host

at the end of the file (in the [all] section). Then

reboot

Posted by Uli Köhler in Raspberry Pi

How to flash Marlin 2.x to BTT SKR 1.4 using PlatformIO

The best way to flash Marlin via PlatformIO onto the LPC1768 MCU on a BTT SKR v1.4 board is to insert an SD card into it, connect the computer via USB and use the mbed as upload_protocol.

In order to do this, edit ini/lpc176x.ini from within Visual Studio Code and add the following new code to [env:LPC1768]:

upload_protocol = mbed
upload_port=/media/uli/A87B-A154/

with upload_port being the directory where the SD card is mounted (while you can do this using an SD card reader, it is so much easier by just connecting the BTT SKR v1.4 via USB directly to your computer, allowing both serial port and SD card access at the same time).

Full example of the [env:LPC1768] section:

[env:LPC1768]
platform = ${common_LPC.platform}
extends  = common_LPC
board    = nxp_lpc1768
upload_protocol = mbed
upload_port=/media/uli/A87B-A154/

In the PlatformIO menu, choose LPC1768/Upload. There is no special configuration for the BTT SKR v1.4 but of course you need to configure Configuration.h etc correctly – see https://www.makenprint.uk/3d-printing/3d-printing-guides/skr-v1-4-configuration-h-marlin-2-setup-part-2/

After uploading, press the reset button on the Board to apply the firmware update. You can use picocom to connect to your printer, see How to connect to your 3D printer using picocom, e.g.:

picocom -b 115200 /dev/ttyACM0 --imap lfcrlf --ech

Enter M115 to check if you have correctly updated the fiwa

You might need to manually re-mount the SD card using your file manager after a firmware update in order to enabled PlaformIO doing another update.

Posted by Uli Köhler in 3D printing, Embedded, PlatformIO

How to install tailscale on Ubuntu

In order to instal tailscale, on any Ubuntu version, you can use the official tailscale install command:

sudo apt -y install curl apt-transport-https
curl -fsSL https://tailscale.com/install.sh | sh
Posted by Uli Köhler in Headscale, Raspberry Pi, VPN

Fullscreen camera view using libcamera-vid

libcamera-vid --width 1920 --height 1080 -f -t 10000000000

-t 10000000000 means to run for 10000000000 milliseconds (almost 4 months)

Posted by Uli Köhler in Audio/Video, Electronics, Raspberry Pi

How to fix raspistill ERROR: the system should be configured for the legacy camera stack

Problem:

While trying to capture an image using raspistill, you see the following error message

ERROR: the system should be configured for the legacy camera stack

and no image is being produced

Solution 1 (recommended): Use libcamera

Recent versions of Raspbian use libcamera instead of the broadcom legacy camera API. You can capture an image using libcamera-still similarly to raspistill:

libcamera-still -o test.jpg

Solution 2: Enable legacy camera API

If you absolutely need raspistill specifically to work, you can still enable the legacy camera API using rpi-config:

sudo raspi-config

Go to:

  • 3 Interface Config
  • I1 Legacy Camera

and choose Yes to enable the legacy camera API.

Posted by Uli Köhler in Audio/Video, Raspberry Pi

Raspberry Pi libcamera VLC livestreaming (1920×1080)

On the Pi, run

libcamera-vid -t 0 --width 1920 --height 1080 --codec h264 --inline --listen -o tcp://0.0.0.0:8888

On the client, run

vlc tcp/h264://192.168.1.185:8888/

where 192.168.1.185 is the IP address of the Raspberry Pi.

Posted by Uli Köhler in Audio/Video, Raspberry Pi

How to DC-sweep resistive voltage divider using PySpice

The following code simulates a resistive 10kΩ / 1kΩ voltage divider using PySpice using a DC sweep and can serve as a suitable starting point for simulating simple circuits. We sweep from 0V to 5V in steps of 10mV.

Also see our previous post How to simulate resistive voltage divider using PySpice for an alternate version using transient analysis:

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
# Create voltage source: 5V DC
source = circuit.VoltageSource('in', 'in', circuit.gnd, dc_value=5@u_V)
# Create resistor divider
r1 = circuit.R('R1', 'in', 'n1', 10@u_kΩ)
r2 = circuit.R('R2', 'n1', circuit.gnd, 1@u_kΩ)
# Simulate for 1 second with steps of 1 millisecond
simulator = circuit.simulator(temperature=25, nominal_temperature=25)
analysis = simulator.dc(Vin=slice(0, 5.0, 0.01))

You can access the array of output voltages of the divider (i.e. node n1) using analysis['n1']Keep in mind that if the first argument of circuit.VoltageSource is 'in', the argument to simulator.dc will be valled Vin,  not just  in! A Vwill automatically be  prepended!

 

This is the code we used to plot this:

import matplotlib.ticker as mtick
import matplotlib.pyplot as plt
from UliEngineering.EngineerIO import format_value

def format_volts(value, pos=None):
    return format_value(value, 'V')

plt.style.use("ggplot")
plt.xlabel("Input voltage")
plt.xlabel("Divider output voltage")
plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(format_volts))
plt.gca().xaxis.set_major_formatter(mtick.FuncFormatter(format_volts))
plt.gcf().set_size_inches(8,5)
plt.plot(analysis["in"], analysis["n1"], label="Input voltage")
plt.savefig("/ram/PySpice-Voltage-Divider-Sweep.svg")
Posted by Uli Köhler in Electronics, Python, SPICE

How to simulate resistive voltage divider using PySpice

The following code simulates a resistive 10kΩ / 1kΩ voltage divider using PySpice and can serve as a good starting point for simulating simple circuits.

This post shows you how to simulate the voltage divider using transient analysis. Also see an alternative variant of this post using DC sweep analysis instead: How to DC-sweep resistive voltage divider using PySpice

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
# Create voltage source: 5V DC
source = circuit.VoltageSource('V1', 'in', circuit.gnd, dc_value=5@u_V)
# Create resistor divider
r1 = circuit.R('R1', 'in', 'n1', 10@u_kΩ)
r2 = circuit.R('R2', 'n1', circuit.gnd, 1@u_kΩ)
# Simulate for 1 second with steps of 1 millisecond
simulator = circuit.simulator(temperature=25, nominal_temperature=25)
analysis = simulator.transient(step_time=1@u_ms, end_time=1@u_s)

You can access the array of output voltages of the divider (i.e. node n1) using analysis['n1']:

This is the code we used to plot this:

import matplotlib.ticker as mtick
import matplotlib.pyplot as plt
from UliEngineering.EngineerIO import format_value

def format_volts(value, pos=None):
    return format_value(value, 'V')

plt.style.use("ggplot")
plt.xlabel("Time [ms]")
plt.ylim([0.0, 5.5])
plt.gca().yaxis.set_major_formatter(mtick.FuncFormatter(format_volts))
plt.gcf().set_size_inches(8,5)
plt.plot(analysis["in"], label="Input voltage")
plt.plot(analysis["n1"], label="Voltage divider output")
plt.gca().legend()
plt.savefig("/ram/PySpice-Voltage-Divider.svg")

 

Posted by Uli Köhler in Electronics, Python, SPICE

How to create resistor in PySpice: Minimal example

This creates a resistor named R1 which has its + pin connected to node n1 and its - pin connected to GND. The value of the resistor is set to 1kΩ.

r1 = circuit.R('R1', 'n1', circuit.gnd, 1@u_kΩ)

Full example:

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
r1 = circuit.R('R1', 'n1', circuit.gnd, 1@u_kΩ)

 

Posted by Uli Köhler in Electronics, Python, SPICE

How to create constant voltage source in PySpice

This creates a voltage source named V1 which has its + pin connected to node n1 and its - pin connected to GND. The voltage source is set to 5V DC.

source = circuit.VoltageSource('V1', 'n1', circuit.gnd, dc_value=5@u_V)

Full example:

import PySpice.Logging.Logging as Logging
logger = Logging.setup_logging()

from PySpice.Probe.Plot import plot
from PySpice.Spice.Netlist import Circuit
from PySpice.Unit import *

circuit = Circuit("MyCircuit")
source = circuit.VoltageSource('V1', 'n1', circuit.gnd, dc_value=5@u_V)

 

Posted by Uli Köhler in Electronics, Python, SPICE