Electronics

How to set a fixed exposure time using libcamera-still on the Raspberry Pi

Use the --shutter argument. If no unit is given, the number represents microseconds of fixed exposure time

Example: --shutter 10000 means: Fixed 10 milliseconds (10000 microseconds) exposure.

Full example

libcamera-still -n 1 --width 4056 --height 3040 -o test.jpg --shutter 400

 

Posted by Uli Köhler in Raspberry Pi

How to enable Custom AWB white balance using libcamera-still on the Raspberry Pi

libcamera-still -n 1 --width 4056 --height 3040 -o test.jpg --awb custom --awbgains 0.9,2.0

In --awbgains 0.9,2.0 , 0.9 is the gain for the red channel and 2.0 is the gain for the blue channel.

In other words, if your image is:

  • too red -> decrease the first number
  • too blue -> decrease the second number
  • too yellow -> increase the second number (blue)
  • too green -> increase both numbers
  • etc
Posted by Uli Köhler in Raspberry Pi

Robust Linux Python serial port filtering by name and manufacturer using udev

Important note: This code is deprecated as it has been superseded by the UliSerial library on GitHub. UIiSerial implements the serial port filtering in a platform-independent manner using pyserial built-in features.

 

Typically, when one intends to select a serial port in Python, you use a sequence such as

glob.glob('/dev/ttyACM*')[0]

or just go to straight hard-coding the port such as /dev/ttyACM0.

It should be quite obvious why this isn’t robust:

  • There might be multiple serial ports and the order is unknown
  • You code might need to work on different computers, which lead to different ports being assigned
  • When you re-plug a device, it might get assigned a differnent serial port number (e.g. /dev/ttyACM1)

The following code uses only the linux integrated tool udevadm to query information and built-in libraries. Currently it does not work on anything but Linux.

import glob
import subprocess
import re

def get_serial_ports():
    # Get a list of /dev/ttyACM* and /dev/ttyUSB* devices
    ports = glob.glob('/dev/ttyACM*') + glob.glob('/dev/ttyUSB*')
    return ports

def get_udev_info(port):
    # Run the udevadm command and get the output
    result = subprocess.run(['udevadm', 'info', '-q', 'all', '-r', '-n', port], capture_output=True, text=True)
    return result.stdout

def find_serial_ports(vendor=None, model=None, usb_vendor=None, usb_model=None, usb_model_id=None, vendor_id=None):
    # Mapping of human-readable names to udevadm keys
    attribute_mapping = {
        "vendor": "ID_VENDOR_FROM_DATABASE",
        "model": "ID_MODEL_FROM_DATABASE",
        "usb_vendor": "ID_USB_VENDOR",
        "usb_model": "ID_USB_MODEL_ENC",
        "usb_model_id": "ID_USB_MODEL_ID",
        "vendor_id": "ID_VENDOR_ID",
        "usb_path": "ID_PATH",
        "serial": "ID_SERIAL_SHORT"
    }

    # Filters based on provided arguments
    filters = {}
    for key, value in locals().items():
        if value and key in attribute_mapping:
            filters[attribute_mapping[key]] = value

    # Find and filter ports
    ports = get_serial_ports()
    filtered_ports = []

    for port in ports:
        udev_info = get_udev_info(port)
        match = True

        for key, value in filters.items():
            if not re.search(f"{key}={re.escape(value)}", udev_info):
                match = False
                break

        if match:
            filtered_ports.append(port)

    return filtered_ports

def get_serial_port_info(port):
    # Mapping of udevadm keys to human-readable names
    attribute_mapping = {
        "ID_VENDOR_FROM_DATABASE": "vendor",
        "ID_MODEL_FROM_DATABASE": "model",
        "ID_USB_VENDOR": "usb_vendor",
        "ID_USB_MODEL_ENC": "usb_model",
        "ID_USB_MODEL_ID": "usb_model_id",
        "ID_VENDOR_ID": "vendor_id",
        "ID_SERIAL_SHORT": "serial",
    }

    # Run the udevadm command and get the output
    udev_info = get_udev_info(port)
    port_info = {}

    for line in udev_info.splitlines():
        if line.startswith('E: '):
            key, value = line[3:].split('=', 1)
            if key in attribute_mapping:
                # Decode escape sequences like \x20 to a space.
                # NOTE: Since only \x20 is common, we currently only replace that one
                port_info[attribute_mapping[key]] = value.replace('\\x20', ' ')

    return port_info

def find_serial_port(**kwargs):
    """
    Find a single serial port matching the provided filters.
    """
    ports = find_serial_ports(**kwargs)
    if len(ports) > 1:
        raise ValueError("Multiple matching ports found for filters: " + str(kwargs))
    elif len(ports) == 0:
        raise ValueError("No matching ports found for filters: " + str(kwargs))
    else:
        return ports[0]

 

Example: Find a serial port

# Example usage: Find a serial port
matching_ports = find_serial_ports(vendor="OpenMoko, Inc.")
print(matching_ports) # Prints e.g. ['/dev/ttyACM0']

Example: Print information about a given port

This is typically used so you know what filters to add for find_serial_ports().

# Example usage: Print info about a serial port
serial_port_info = get_serial_port_info('/dev/ttyACM0')
print(serial_port_info)

This prints, for example:

{
    'serial': '01010A23535223934CF29A1EF5000007',
    'vendor_id': '1d50',
    'usb_model': 'Marlin USB Device',
    'usb_model_id': '6029',
    'usb_vendor':
    'marlinfw.org',
    'vendor': 'OpenMoko, Inc.',
    'model': 'Marlin 2.0 (Serial)'
}

 

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

ESP32 HTTP POST with JSON body using ArduinoJson

This example parses a HTTP POST request body using ArduinoJson. I recommend using my HumanESPHTTP library as simple server library based on the ESP-IDF HTTP server.

constexpr size_t JsonParseBufferSize = 2048;
typedef ArduinoJson::StaticJsonDocument JsonParseBuffer;

/**
 * This buffer is used to parse /api/configure events
*/
static JsonParseBuffer document;

static const httpd_uri_t configureHandler = {
    .uri       = "/api/configure",
    .method    = HTTP_POST,
    .handler   = [](httpd_req_t *req) {
        // Receive POST body data into a new buffer
        char* jsonBody = (char*)malloc(req->content_len + 1);
        jsonBody[req->content_len] = '\0'; // NUL terminate [just in case]
        if(jsonBody == nullptr) {
            return SendStatusError(req, "Failed to allocate memory for JSON body");
        }
        int err = httpd_req_recv(req, jsonBody, req->content_len);
        if(err != req->content_len) {
            free(jsonBody);
            return SendStatusError(req, "Failed to read request body");
        }
        // Parse the body as JSON
        deserializeJson(document, jsonBody, req->content_len);
        // TODO: Do something with [document]
        // Cleanup
        free(jsonBody);
        return SendStatusOK(req);
    }
};

which uses the following utility functions:

esp_err_t SendStatusError(httpd_req_t *request, const char* description) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_send_chunk(request, "{\"status\":\"error\", \"error\": \"", HTTPD_RESP_USE_STRLEN);
    // NOTE: We silently assume that description does not have any special characters
    httpd_resp_send_chunk(request, description, HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, "\"}", HTTPD_RESP_USE_STRLEN);
    httpd_resp_send_chunk(request, nullptr, 0); // Finished
    return ESP_OK;
}

esp_err_t SendStatusOK(httpd_req_t *request) {
    httpd_resp_set_type(request, "application/json");
    httpd_resp_sendstr(request, "{\"status\":\"ok\"}");
    return ESP_OK;
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

How to fix ESP32 rmt: rmt_transmit(466): loop count is not supported

If you encounter

rmt: rmt_transmit(466): loop count is not supported

on the ESP32, this is because you have used a rmt_transmit_config_t with explicitly set loop_count.

rmt_transmit_config_t cfg = {
  .loop_count = 1,
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

but your IC (e.g. ESP32-D0WD-V3)  does not support hardware loop mode.

Fixing this is easy: Just comment out the .loop_count line:

rmt_transmit_config_t cfg = {
  //.loop_count = 1, // DISABLED as chip does not support it
  .flags = {
      .eot_level = 0,
   }
};
ESP_ERROR_CHECK(rmt_transmit(/* ... */, &cfg));

Note that if you leave .loop_count at its default, it will always act as if .loop_count = 1.

Posted by Uli Köhler in C/C++, ESP8266/ESP32

ESP32-D0WD-V3 does not support RMT sync manager

I was trying to use a RMT sync manager on the ESP32-D0WD-V3 (Version 3.1). However, trying to use rmt_new_sync_manager() returns ESP_ERR_NOT_SUPPORTED with the following message:

␛[0;31mE (560) rmt: rmt_new_sync_manager(331): sync manager not supported␛[0m
ESP_ERROR_CHECK failed: esp_err_t 0x106 (ESP_ERR_NOT_SUPPORTED) at 0x400d2681

This leads me to believe that the ESP32-D0WD-V3 does not support any RMT sync managers. So far, I have not tried with other controllers.

Posted by Uli Köhler in ESP8266/ESP32

ESP-IDF minimal Wifi client hello world example using PlatformIO

This basic example showcases how to use PlatformIO with ESP-IDF only (no Arduino) to connect to Wifi in station mode and print Hello world in a loop. It is based on the basic Hello World example from PlatformIO ESP32 with ESP-IDF minimal C++ example:

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include <esp_wifi.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_netif.h>
#include <esp_event.h>


extern "C" {
    void app_main(void);
}


static void NetworkEventHandler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data) {
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
        ESP_LOGI("wifi", "Retrying to connect to Wifi...");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI("wifi", "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
    }
}

void InitNVS() {
    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
}

void InitWifi() {

    // Initialize TCP/IP network interface (required for Wi-Fi)
    ESP_ERROR_CHECK(esp_netif_init());

    // Initialize the event loop
    ESP_ERROR_CHECK(esp_event_loop_create_default());    
    esp_netif_create_default_wifi_sta();

    // Initialize Wi-Fi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // Set Wi-Fi to station mode
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));

    // Configure the Wi-Fi connection
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "MyWifi",
            .password = "mypassword"
        }
    };
    // Register event handler
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &NetworkEventHandler,
                                                        NULL,
                                                        NULL));

    // Set Wi-Fi configuration and start Wi-Fi
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
}

void app_main() {
    InitNVS();
    InitWifi();

    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200

All sdkconfig settings have been left at their respective defaults.

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to fix ESP-IDF error: cannot convert ‘esp_interface_t’ to ‘wifi_interface_t’

Problem:

While trying to  compile your ESP-IDF app using code such as

ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));

you see an error message such as

src/main.cpp:76:41: error: cannot convert 'esp_interface_t' to 'wifi_interface_t'
   76 |     ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
      |                                         ^~~~~~~~~~~~~~~
      |                                         |
      |                                         esp_interface_t

Solution:

Instead of ESP_IF_WIFI_STA, use WIFI_IF_STA (which has the correct type):

ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32

PlatformIO ESP32 with ESP-IDF minimal C++ example

By default, when you initialize a PlatformIO ESP-IDF project, it will generate main.c – not a C++ but a pure C file

This minimal example instead uses main.cpp (you can just rename your main.c – see How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf for a detailed list of steps to do).

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to use C++ main.cpp with PlatformIO on ESP32 / esp-idf

When you are using PlatformIO to compile your firmware, it is easily possible to use a C++ main.cpp instead of the pure C main.c by just renaming main.c to main.cpp

However, you also need to properly declary app_main(). After the #include section of your main.cpp (or at the top of the file if you don’t have an #include section yet), add this code:

extern "C" {
    void app_main(void);
}

This will tell the compiler that app_main() is a C function, not a C++ function.

Full main.cpp example

#include <cstdio>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

extern "C" {
    void app_main(void);
}

void app_main() {
    while(true) {
        printf("Hello PlatformIO!\n");
        // Wait for one second
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

 

Posted by Uli Köhler in C/C++, ESP8266/ESP32, PlatformIO

How to fix ESP32 error: ‘portTICK_PERIOD_MS’ was not declared in this scope

Problem:

While trying to compile your ESP-IDF firmware (with or without PlatformIO), you see an error message such as

src/main.cpp:13:27: error: 'portTICK_PERIOD_MS' was not declared in this scope
   13 |         vTaskDelay(1000 / portTICK_PERIOD_MS);

Solution:

Include FreeRTOS by adding the following lines to the top of the file where the error occured (src/main.cpp in this example):

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

 

Posted by Uli Köhler in ESP8266/ESP32, FreeRTOS, PlatformIO

How to fix ESP32 error: ‘vTaskDelay’ was not declared in this scope

Problem:

While trying to compile your ESP-IDF firmware (with or without PlatformIO), you see an error message such as

src/main.cpp:13:9: error: 'vTaskDelay' was not declared in this scope
   13 |         vTaskDelay(1000 / portTICK_PERIOD_MS);

Solution:

Include FreeRTOS by adding the following lines to the top of the file where the error occured (src/main.cpp in this example):

#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

 

Posted by Uli Köhler in ESP8266/ESP32, FreeRTOS, PlatformIO

What pitch do Molex Mini-Fit Jr connectors have?

According to the Molex Website, the entire Mini-Fit family has a pin pitch of 4.2mm

 

Posted by Uli Köhler in Components

JST VH connector part numbers & LCSC links

Prices shown are for budgetary purposes only. Check the LCSC page or other distributors for up-to-date pricing information.

Straight THT connector

  • 2 positions: B2P-VH 0,0343€/pc @100pcs
  • 3 positions: B3P-VH 0,0473€/pc @100pcs
  • 4 positions: B4P-VH 0,0719€/pc @100pcs
  • 5 positions: B5P-VH 0,1034€/pc @100pcs
  • 6 positions: B6P-VH 0,1113€/pc @100pcs
  • 7 positions: B7P-VH 0,1317€/pc @100pcs
  • 8 positions: B8P-VH 0,1165€/pc @100pcs
  • 9 positions: B9P-VH 0,1228€/pc @100pcs
  • 10 positions: B10P-VH0,1368€/pc @100pcs

Right-angled THT connector

Also called side-entry type

  • 2 positions: B2PS-VH 0,0924€/pc @100pcs
  • 3 positions: B3PS-VH 0,119€/pc @100pcs
  • 4 positions: B4PS-VH 0,1432€/pc @100pcs
  • 5 positions: B5PS-VH 0,1779€/pc @100pcs
  • 6 positions: B6PS-VH 0,1991€/pc @100pcs
  • 7 positions: B7PS-VH 0,2323€/pc @100pcs
  • 8 positions: B8PS-VH 0,2982€/pc @100pcs
  • 9 positions: B9PS-VH 0,1837€/pc @100pcs
  • 10 positions: B10PS-VH 0,1862€/pc @100pcs

Connector housing

  • 2 positions: VHR-2N  0.0315€/pc @100pcs
  • 3 positions: VHR-3N  0.0298€/pc @100pcs
  • 4 positions: VHR-4N 0.0369€/pc @100pcs
  • 5 positions: VHR-5N  0.0388€/pc @100pcs
  • 6 positions: VHR-6N  0.0437€/pc @100pcs
  • 7 positions: VHR-7N  0.0477€/pc @100pcs
  • 8 positions: VHR-8N  0.0575€/pc @100pcs
  • 9 positions: VHR-9N  0.0692€/pc @100pcs
  • 10 positions: VHR-10N  0.094€/pc @100pcs
  • 11 positions: VHR-11N  0.144€/pc @100pcs
Posted by Uli Köhler in Components, Electronics

Where to find 3D models for JST-VH connectors?

You can find 3D models as 3D-PDF for JST VH connectors on the JST website.

However, in order to obtain STEP or IGES models, you have to look on the jst-mfg.com site.

Also see:

Posted by Uli Köhler in Components, Electronics

How to fix “kicad-cli pcb export step” not exporting component 3D models

Problem:

You are trying to export a 3D STEP model of your PCB using a command such as

kicad-cli pcb export step MyPCB.kicad_pcb

but the resulting STEP file is missing all (or almost all) component models (i.e. only the PCB is exported).

Furthermore, in the output of the command, you can read

Cannot add a VRML model to a STEP file.

for every single component, for example

Add component R6.
Cannot add a VRML model to a STEP file.
Add component R21.
Cannot add a VRML model to a STEP file.
Add component C7.
Cannot add a VRML model to a STEP file.
Add component C16.
Cannot add a VRML model to a STEP file.

Solution:

Add the --subst-models flag to kicad-cli in order to automatically substitute VRML models with STEP models:

kicad-cli pcb export step MyPCB.kicad_pcb --subst-models

After that, all available component models will be exported just like in the viewer.

Posted by Uli Köhler in Electronics, KiCAD

Where to find Trinamic TMC5160 StepStick schematic PDF?

There are multiple manufacturers providing StepStick drivers based on the TMC5160 with open schematics.

One example is Watterott – GitHub link

Posted by Uli Köhler in Electronics

List of JST XH connector MPNs (with LCSC links)

Prices shown are for budgetary purposes only. Check the LCSC page or other distributors for up-to-date pricing information.

Female connector housing (for crimp contacts)

    • 2-pin: XHP-2 0.0158€/pc @100pcs
    • 3-pin: XHP-3 0.0172€/pc @100pcs
    • 4-pin: XHP-4 0.0167€/pc @100pcs
    • 5-pin: XHP-5 0.0182€/pc @100pcs
    • 6-pin: XHP-6 0.0217€/pc @100pcs
    • 7-pin: XHP-7 0.0276€/pc @100pcs
    • 8-pin: XHP-8 0.0266€/pc @100pcs
    • 9-pin: XHP-9 0.028€/pc @100pcs
    • 10-pin: XHP-10 0.0305€/pc @100pcs
    • 11-pin: XHP-11 0.0314€/pc @100pcs
    • 12-pin: XHP-12 0.0452€/pc @100pcs
    • 13-pin: XHP-13 0.0543€/pc @100pcs
    • 14-pin: XHP-14 0.0645€/pc @100pcs
    • 15-pin: XHP-15 0.08€/pc @100pcs Not stocked
    • 16-pin: XHP-16 0.1594€/pc @100pcs
    • 17-pin: XHP-17 Not offered on LCSC
    • 18-pin: XHP-18 Not offered on LCSC
    • 19-pin: XHP-19 Not offered on LCSC
    • 20-pin: XHP-20 0.1581€/pc @100pcs

Male straight through-hole PCB-mount connectors

with lead-free tin finish

Male right-angled (“side-entry”) THT connectors:

with lead-free tin finish

Posted by Uli Köhler in Components

How to read multimeter and paste into Excel spreadsheet cell on hotkey press using Python

The following Python script uses PyVISA / LabInstruments to read a Rigol DM3058(E) multimeter and automatically paste the voltage reading into an open spreadsheet on Shift+Alt+Q press.

First, clone LabInstruments to the current directory using

git clone https://github.com/ulikoehler/LabInstruments.git

Now install the requirements from the following requirements.txt

system_hotkey310
pyperclip
pyvisa
pyvisa-py

and run the script:

#!/usr/bin/env python3
"""
This script allows quick reading of multimeter, automatically pasting the reading
into the current e.g. excel spreadsheet and pressing "Enter" to advance to the next cell.

Press Shift+Alt+Q to read the multimeter and paste the reading into the current cell.
"""
import subprocess
import pyautogui
import time
import re
import pyperclip
import sexpdata
import pyvisa
from LabInstruments.DM3058 import DM3058

import locale
locale.setlocale(locale.LC_NUMERIC, f"{locale.getdefaultlocale()[0]}.utf-8")

rm = pyvisa.ResourceManager()
rm.list_resources()
inst = rm.open_resource('USB0::6833::2500::DM3R245003970::0::INSTR')
multimeter = DM3058(inst)
multimeter.set_speed("S")
multimeter.mode_dc_voltage()


def run():
    voltage = multimeter.read_voltage()
    
    locale_formatted_voltage = locale.format_string("%.5f", voltage, grouping=False)
    print("Voltage: ", locale_formatted_voltage)

    # Copy locale-formatted voltage to clipboard
    pyperclip.copy(locale_formatted_voltage)
    # Paste into cell
    pyautogui.hotkey('ctrl', 'v')
    # Wait for excel
    time.sleep(0.1)
    # Press Enter key to save
    pyautogui.press('enter')

# Register hotkeys
from system_hotkey import SystemHotkey
hk = SystemHotkey()
hk.register(('shift', 'alt', 'q'), callback=lambda x: run())

# Wait for exit
while True:
    time.sleep(10)

 

Posted by Uli Köhler in Electronics, Python