Erschließung des HX711 Treibers für Linux-IIO

Einleitung

Bei Developing Terkin for CPython - #10 by Andreas begann eine Diskussion um die

Probleme

Für “ordentliche” Gewichtsmessungen schaut es leider so aus, als ob der Kernel neu gebaut werden muss. Der Linux-IIO Treiber für den HX711 ist zwar im aktuellen Kernel verfügbar, aber er ist nicht aktiviert bzw. es ist kein Gerätebaum-Overlay dafür hinterlegt.

Eine Anleitung dazu findet sich z.B. hier bei Enabling new hardware on Raspberry Pi with Device Tree Overlays - Bootlin's blog.

Ich habe allerdings Probleme, das aktuelle Git-Repository von Rasperry/Linux von https://github.com/raspberrypi/linux.git herunterzuladen. Auf meinem Windows Rechner bricht er ab und auch auf dem Pi Zero passiert leider das gleiche. Bei insgesammt knapp 2,7 Gb Daten passiert das natürlich ziemlich am Ende.

Das ist die Ausgabe von git clone:

git.exe clone --progress -v "https://github.com/raspberrypi/linux.git" "C:\linux"
Cloning into 'C:\linux'...
POST git-upload-pack (gzip 7075 to 3286 bytes)
remote: Enumerating objects: 9156345, done.
remote: Counting objects: 100% (17222/17222), done.
remote: Compressing objects: 100% (3927/3927), done.
remote: Total 9156345 (delta 14179), reused 16114 (delta 13289), pack-reused 9139123
Receiving objects: 100% (9156345/9156345), 2.69 GiB | 26.31 MiB/s, done.
Resolving deltas: 100% (7659018/7659018), done.
error: unable to stat just-written file drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.c: No such file or directory
error: unable to stat just-written file drivers/gpu/drm/nouveau/nvkm/subdev/i2c/aux.h: No such file or directory
error: unable to stat just-written file include/soc/arc/aux.h: No such file or directory
Checking out files: 100% (71146/71146), done.
warning: the following paths have collided (e.g. case-sensitive paths
on a case-insensitive filesystem) and only one from the same
colliding group is in the working tree:

'include/uapi/linux/netfilter/xt_CONNMARK.h'
'include/uapi/linux/netfilter/xt_connmark.h'
'include/uapi/linux/netfilter/xt_DSCP.h'
'include/uapi/linux/netfilter/xt_dscp.h'
'include/uapi/linux/netfilter/xt_MARK.h'
'include/uapi/linux/netfilter/xt_mark.h'
'include/uapi/linux/netfilter/xt_RATEEST.h'
'include/uapi/linux/netfilter/xt_rateest.h'
'include/uapi/linux/netfilter/xt_TCPMSS.h'
'include/uapi/linux/netfilter/xt_tcpmss.h'
'include/uapi/linux/netfilter_ipv4/ipt_ECN.h'
'include/uapi/linux/netfilter_ipv4/ipt_ecn.h'
'include/uapi/linux/netfilter_ipv4/ipt_TTL.h'
'include/uapi/linux/netfilter_ipv4/ipt_ttl.h'
'include/uapi/linux/netfilter_ipv6/ip6t_HL.h'
'include/uapi/linux/netfilter_ipv6/ip6t_hl.h'
'net/netfilter/xt_DSCP.c'
'net/netfilter/xt_dscp.c'
'net/netfilter/xt_HL.c'
'net/netfilter/xt_hl.c'
'net/netfilter/xt_RATEEST.c'
'net/netfilter/xt_rateest.c'
'net/netfilter/xt_TCPMSS.c'
'net/netfilter/xt_tcpmss.c'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+poonceLock+pombonce.litmus'
'tools/memory-model/litmus-tests/Z6.0+pooncelock+pooncelock+pombonce.litmus'
fatal: unable to checkout working tree
warning: Clone succeeded, but checkout failed.
You can inspect what was checked out with 'git status'
and retry the checkout with 'git checkout -f HEAD'



git did not exit cleanly (exit code 128) (658781 ms @ 31.07.2021 22:58:01)

Gedanken

Unabhängig davon finde ich das ganze viel zu aufwendig, um es nachzubauen. Wir müssten dann ein entsprechendes fertiges Image zur Verfügung stellen.

Also entweder doch lieber Variante 1. weiterverfolgen, oder bauen. Beim Bauen wäre aber sicher auch noch interessant, gleich den Linux-IIO Treiber für den NAU7802 24bit 2ch AFE for bridge sensors hinzuzufügen: linux/nau7802.c at master · torvalds/linux · GitHub.

2 Likes

Hi Michael,

vielen Dank für Deinen Bericht über die ersten Schritte in diese Richtung.

Kernelmodul hx711.ko

@weef berichtete hierzu:

Im Paket linux-modules-5.4.0-1001-raspi2_5.4.0-1001.1_arm64.deb ist das Modul bereits als /lib/modules/5.4.0-1001-raspi2/kernel/drivers/iio/adc/hx711.ko enthalten.

"modprobe hx711" könnte also bereits funktionieren, wenn man dieses Paket installiert hat [1]. Damit ist die Hürde für eine Inbetriebnahme auf diesem Weg doch signifikant kleiner geworden?

Passendes “Device Overlay” für HX711

Dieses muss noch hinzugefügt werden, ja. Da es hier scheinbar noch kein “fertiges” Rezept gibt, könnte das noch etwas Arbeit werden. Es gibt jedoch folgende Artikel bzw. Diskussionen, die sich dem Thema widmen (danke auch nochmal an @weef!):

Allgemein: Raspberry Pi Einrichten » Device Tree

Gedanken

Ich verstehe Deine Gedanken, vor allem vor dem Hintergrund “Kernel Modul auf Windows oder Raspberry Pi bauen” – dieses Thema hat sich aber ja nun in Wohlgefallen aufgelöst. Vielleicht haben wir Fortuna ja für die nächsten Schritte weiterhin auf unserer Seite?

Viele Grüße,
Andreas.


  1. P.S.: Die Unterstützung für den HX711 ist seit 2017 an Bord, siehe iio: adc: hx711: Add IIO driver for AVIA HX711 · raspberrypi/linux@c3b2fdd · GitHub, die Unterstützung für den NAU7802 sogar schon seit 2013, siehe iio: Add Nuvoton NAU7802 ADC driver · raspberrypi/linux@8b20be8 · GitHub. ↩︎

Diese beiden Schnipsel scheinen bei manchen bereits funktioniert zu haben.

@gabson berichtete am Fri Sep 27, 2019 1:13 am:

@adrianlzt berichtete am Mar 26 '20 at 17:22:

Device Tree How-Tos

Die kanonische Dokumentation im Kontext des Raspberry Pi findet sich bei Device Trees, overlays, and parameters - Raspberry Pi Documentation.

Für den Umgang mit diesen Definitionsdateien hat Phil Elwell bei Trouble adding a gpio device to the device tree - Raspberry Pi Forums eine hervorragende Handreichung.

Auch dieser Abschnitt scheint sinnvoll:

1 Like

Dieser Schnipsel (der zweite) mit "#include <dt-bindings/gpio/gpio.h>" entspricht auch dem, was im Kernelbaum in der Device Tree Bindings Dokumentation bei linux/avia-hx711.yaml at rpi-5.10.y · raspberrypi/linux · GitHub angegeben ist:

Habe ich auch schon gesehen. Bei OS-Lite ist es aber nicht aktiviert. Leider kann der Zero W kein 64Bit und ich würde gerne komplett auf eine GUI wegen der besseren Leistung verzichten.

Bin aber dabei einen Kernel auf basis von 32Bit Lite zu Bauen und auf SD zu Packen, dauert aber gefühlt eine halbe Ewigkeit.

Kannst Du das Paket denn nicht per apt-get install linux-modules oder irgendwie manuell per dpkg -i installieren?

@weef: Weißt Du, woher das kommt? Unter Index of /raspbian/pool werde ich gerade nicht fündig.

Könnte evtl. hier mit gehen. Ich versuche es mal.
https://wiki.ubuntuusers.de/module-assistant/

Edit: geht leider auch nicht. Er kann keine offenen Quellen für den kernel version: 5.10.17+
finden. Ein manuelles einfügen einer vorher runter geladenen HX711.ko geht leider nicht.

Starting the Dialog UI...

Updated infos about 27 packages
Getting source for kernel version: 5.10.17+
apt-get install linux-headers-5.10.17+
Reading package lists... Done
Building dependency tree
Reading state information... Done
E: Unable to locate package linux-headers-5.10.17
E: Couldn't find any package by glob 'linux-headers-5.10.17'
E: Couldn't find any package by regex 'linux-headers-5.10.17'
apt-get install build-essential
Reading package lists... Done
Building dependency tree
Reading state information... Done
build-essential is already the newest version (12.6).
0 upgraded, 0 newly installed, 0 to remove and 36 not upgraded.

Done!

Installiert mal das meta-Paket linux-generic, das zieht dann alle zum kernel-Bau benötigten Dinge hinterher - vor allen die header, aber auch u.a. modules und modules-extra. Die speziellen Versionen sind von der kernel-Version abhängig, daher kann ich hier keinen genaueren filenamen angeben.

2 Likes

Bin jetzt endlich mal ein wenig weiter gekommen.
nachdem ich mehrmals versucht habe den Kernel nach Anleitung inkl. Molulen neu zu bauen, bin ich nun nach dieser Anleitung aus den Beispielen aus der README dieses Git-Branches
https://github.com/RPi-Distro/rpi-source weiter gekommen.
also nicht den kompletten Kernel ( sondern nur die benötigten Module)

pi@raspberrypi:~ $ lsiio -v
Device 001: bme280
   in_temp_input
   in_humidityrelative_input
   in_pressure_input

Device 000: hx711
   in_voltage0_raw
   in_voltage1_raw

pi@raspberrypi:~ $

Aktuell fehlt mir noch eine nau7802.dts bzw .dtb/.dtbo,
bin mir noch nicht sicher, wie IIO-I²C-Devices in den Device-Tree hinzugefügt werden müssen.
Habe bisher nur eine .dts mit einem I²C Multiplexer und Den Nau´s gefunden.

Werde wenn ich alles geprüft und am laufen habe versuchen meinen Weg nochmal zu rekonstruieren, bin auf alle fälle in ein paar Sackgassen und halbgare Lösungsansätze getappt.

Edit der Nau7802 scheint jetzt auch mit dem iio Treiber zu gehen leider ohne den internen Temperatursensor.

Device 001: 1-002a
   in_voltage0_raw
   in_voltage1_raw

Device 002: bme280
   in_temp_input
   in_humidityrelative_input
   in_pressure_input

Device 000: hx711
   in_voltage0_raw
   in_voltage1_raw
pi@raspberrypi:~ $

finde aber den Grund nicht, warum er den Nau7802 nicht richtig benennt.
i2c-sensor-overlay.dts.txt (10,9 KB)

2 Likes

Hi Michael,

Das sieht ja astrein aus, gratuliere! Kommen da schon plausible Werte raus?

Viele Grüße,
Andreas.

P.S.:

Musstest Du irgendein Device Overlay dazu konfigurieren oder anpassen?

Ahja, das wäre exzellent! Vielen Dank schon im Voraus.

Falls ja, und Bezug nehmend auf:

Bei bienen/bienen.c at 110b9a4ad98f12a0c1c72e718ec1d7b891817961 · it-klinger/bienen · GitHub kann man z.B. sehen, wie die Daten ausgelesen werden, nämlich einfach aus dem Dateisystem unterhalb von bw_iio_dir. Das ist in C geschrieben natürlich alles ziemlich viel Aufwand, wenn das noch einigermaßen konfigurierbar sein darf, aber in Python wird das deutlich kompakter, egal ob mit oder ohne Hilfsbibliothek.

Die Frage ist also, bekommst Du bereits plausible Werte von den unter /sys/bus/iio/devices eingeblendeten Geräten ausgelesen?

Der Ansatz für den nächsten Schritt, das in Terkin zu integrieren, ist bereits vorbereitet.

Gerätetreiber

Bei terkin-datalogger/linux.py at 0.12.0 · hiveeyes/terkin-datalogger · GitHub findest Du bereits einen Gerätetreiber, um Meßwerte aus dem Linux sysfs auszulesen, nämlich den für die DS18B20 Sensoren unter /sys/bus/w1/devices.

Dort müssten dann noch entsprechende Treiber für IIO-basierte Geräte zur Seite gestellt werden, um Meßwerte aus /sys/bus/iio/devices auszulesen.

Sensoradapter

Bei terkin-datalogger/ds18x20_sensor.py at 0.12.0 · hiveeyes/terkin-datalogger · GitHub sieht man, wie der sysfs-Gerätetreiber für den DS18x20 dann im Sensoradapter benutzt wird. Das müsste man dann entweder in terkin-datalogger/hx711_sensor.py at 0.12.0 · hiveeyes/terkin-datalogger · GitHub oder parallel dazu wohl ebenfalls tun, um den alternativen HX711-Auslesepfad im Framework zur Verfügung zu stellen [1]. Das wird noch etwas Arbeit.


  1. Da wir an dieser Stelle vermutlich noch keine Bus-Paradigmen über Linux IIO implementieren wollen, können wir uns die Anpassungen hinsichtlich des “Bus”-Adapters (siehe terkin-datalogger/core.py at 0.12.0 · hiveeyes/terkin-datalogger · GitHub) vermutlich sparen. ↩︎

Ja, da kommen schon Werte raus die zumindest, vom Verständnis hin kommen. Kann gerade nicht zeigen, da die Wägezelle nicht angeschlossen ist, aber es funktioniert beim HX711 und beim Nau7802

Die device werden nach der Reihenfolge der Aktivierung in der config.txt fortlaufend benannt.
Den richtigen Namen erfährt man unter der Datei name im entsprechendem Unterordner.

pi@raspberrypi:/sys/bus/iio/devices $ ls
iio:device0  iio:device1  iio:device2
pi@raspberrypi:/sys/bus/iio/devices $ cat iio:device0/name
hx711
pi@raspberrypi:/sys/bus/iio/devices $ cat iio:device1/name
1-002a
pi@raspberrypi:/sys/bus/iio/devices $ cat iio:device2/name
bme280

über die Datei uevent lassen sich noch weitere Infos abrufen

pi@raspberrypi:/sys/bus/iio/devices $ cat  iio:device0/uevent
MAJOR=240
MINOR=0
DEVNAME=iio:device0
DEVTYPE=iio_device
OF_NAME=weight
OF_FULLNAME=/weight
OF_COMPATIBLE_0=avia,hx711
OF_COMPATIBLE_N=1
pi@raspberrypi:/sys/bus/iio/devices $ cat  iio:device1/uevent
MAJOR=240
MINOR=1
DEVNAME=iio:device1
DEVTYPE=iio_device
OF_NAME=nau7802
OF_FULLNAME=/soc/i2c@7e804000/nau7802@2a
OF_COMPATIBLE_0=nau7802
OF_COMPATIBLE_N=1
pi@raspberrypi:/sys/bus/iio/devices $ cat  iio:device2/uevent
MAJOR=240
MINOR=2
DEVNAME=iio:device2
DEVTYPE=iio_device
OF_NAME=bme280
OF_FULLNAME=/soc/i2c@7e804000/bme280@76
OF_COMPATIBLE_0=bosch,bme280
OF_COMPATIBLE_N=1

Die Verzeichnisse sehen so aus:

pi@raspberrypi:/sys/bus/iio/devices $ ls iio:device0
buffer                   dev              in_voltage0_scale_available  in_voltage1_scale_available  name     power          subsystem  uevent
current_timestamp_clock  in_voltage0_raw  in_voltage1_raw              in_voltage_scale             of_node  scan_elements  trigger
pi@raspberrypi:/sys/bus/iio/devices $ ls iio:device1
dev  in_voltage0_raw  in_voltage1_raw  in_voltage_sampling_frequency  in_voltage_scale  in_voltage_scale_available  name  of_node  power  sampling_frequency_available  subsystem  uevent
pi@raspberrypi:/sys/bus/iio/devices $ ls iio:device2
dev  in_humidityrelative_input  in_humidityrelative_oversampling_ratio  in_pressure_input  in_pressure_oversampling_ratio  in_temp_input  in_temp_oversampling_ratio  name  of_node  power  subsystem  uevent
pi@raspberrypi:/sys/bus/iio/devices $

Wie man die verfügbaren werte über das Filesystem abfragen kann habe ich noch nicht gefunden.
Könnte also etwas Arbeit werden, da ein system zu finden.

beim Nau7802 können wir noch überlegen, im Treiber den 3. Kanal im Muxer freizuschalten, um auch Temperaturwerte zu erhalten.

Vielleicht so?

# iio:device0
cat /sys/bus/iio/devices/iio:device0/in_voltage0_raw
cat /sys/bus/iio/devices/iio:device0/in_voltage1_raw
cat /sys/bus/iio/devices/iio:device0/in_voltage_scale

# iio:device1
cat /sys/bus/iio/devices/iio:device1/in_voltage0_raw
cat /sys/bus/iio/devices/iio:device1/in_voltage1_raw

ja genau so :rofl:
aber ich habe nicht rausgefunden, ob es eine Auflistung über die verfügbaren Werte wie bei lsiio gibt

pi@raspberrypi:/sys/bus/iio/devices/iio:device0/scan_elements $ lsiio -v
Device 001: 1-002a
   in_voltage0_raw
   in_voltage1_raw

Device 002: bme280
   in_temp_input
   in_humidityrelative_input
   in_pressure_input

Device 000: hx711
   in_voltage0_raw
   in_voltage1_raw

lsiio liest ja vermutlich genau diesen Dateisystembaum aus. z.B. naiverweise ganz einfach so:

ls -alF /sys/bus/iio/devices/iio:device?/in_*

Nicht ganz, da würde dann noch ein wenig mehr aufgezählt.
mom… wir haben ja den Code.
lsiio muss man vor Benutzung aus den Kernel Ressourcen selbst Compilern und kann es nicht per apt-get install installieren

pi@raspberrypi:~/linux/tools/iio $ cat lsiio.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Industrial I/O utilities - lsiio.c
 *
 * Copyright (c) 2010 Manuel Stahl <manuel.stahl@iis.fraunhofer.de>
 */

#include <string.h>
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/dir.h>
#include "iio_utils.h"

static enum verbosity {
        VERBLEVEL_DEFAULT,      /* 0 gives lspci behaviour */
        VERBLEVEL_SENSORS,      /* 1 lists sensors */
} verblevel = VERBLEVEL_DEFAULT;

const char *type_device = "iio:device";
const char *type_trigger = "trigger";

static inline int check_prefix(const char *str, const char *prefix)
{
        return strlen(str) > strlen(prefix) &&
               strncmp(str, prefix, strlen(prefix)) == 0;
}

static inline int check_postfix(const char *str, const char *postfix)
{
        return strlen(str) > strlen(postfix) &&
               strcmp(str + strlen(str) - strlen(postfix), postfix) == 0;
}

static int dump_channels(const char *dev_dir_name)
{
        DIR *dp;
        const struct dirent *ent;

        dp = opendir(dev_dir_name);
        if (!dp)
                return -errno;

        while (ent = readdir(dp), ent)
                if (check_prefix(ent->d_name, "in_") &&
                   (check_postfix(ent->d_name, "_raw") ||
                    check_postfix(ent->d_name, "_input")))
                        printf("   %-10s\n", ent->d_name);

        return (closedir(dp) == -1) ? -errno : 0;
}

static int dump_one_device(const char *dev_dir_name)
{
        char name[IIO_MAX_NAME_LENGTH];
        int dev_idx;
        int ret;

        ret = sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_device), "%i",
                     &dev_idx);
        if (ret != 1)
                return -EINVAL;

        ret = read_sysfs_string("name", dev_dir_name, name);
        if (ret < 0)
                return ret;

        printf("Device %03d: %s\n", dev_idx, name);

        if (verblevel >= VERBLEVEL_SENSORS)
                return dump_channels(dev_dir_name);

        return 0;
}

static int dump_one_trigger(const char *dev_dir_name)
{
        char name[IIO_MAX_NAME_LENGTH];
        int dev_idx;
        int ret;

        ret = sscanf(dev_dir_name + strlen(iio_dir) + strlen(type_trigger),
                     "%i", &dev_idx);
        if (ret != 1)
                return -EINVAL;

        ret = read_sysfs_string("name", dev_dir_name, name);
        if (ret < 0)
                return ret;

        printf("Trigger %03d: %s\n", dev_idx, name);

        return 0;
}

static int dump_devices(void)
{
        const struct dirent *ent;
        int ret;
        DIR *dp;

        dp = opendir(iio_dir);
        if (!dp) {
                fprintf(stderr, "No industrial I/O devices available\n");
                return -ENODEV;
        }

        while (ent = readdir(dp), ent) {
                if (check_prefix(ent->d_name, type_device)) {
                        char *dev_dir_name;

                        if (asprintf(&dev_dir_name, "%s%s", iio_dir,
                                     ent->d_name) < 0) {
                                ret = -ENOMEM;
                                goto error_close_dir;
                        }

                        ret = dump_one_device(dev_dir_name);
                        if (ret) {
                                free(dev_dir_name);
                                goto error_close_dir;
                        }

                        free(dev_dir_name);
                        if (verblevel >= VERBLEVEL_SENSORS)
                                printf("\n");
                }
        }
        rewinddir(dp);
        while (ent = readdir(dp), ent) {
                if (check_prefix(ent->d_name, type_trigger)) {
                        char *dev_dir_name;

                        if (asprintf(&dev_dir_name, "%s%s", iio_dir,
                                     ent->d_name) < 0) {
                                ret = -ENOMEM;
                                goto error_close_dir;
                        }

                        ret = dump_one_trigger(dev_dir_name);
                        if (ret) {
                                free(dev_dir_name);
                                goto error_close_dir;
                        }

                        free(dev_dir_name);
                }
        }

        return (closedir(dp) == -1) ? -errno : 0;

error_close_dir:
        if (closedir(dp) == -1)
                perror("dump_devices(): Failed to close directory");

        return ret;
}

int main(int argc, char **argv)
{
        int c, err = 0;

        while ((c = getopt(argc, argv, "v")) != EOF) {
                switch (c) {
                case 'v':
                        verblevel++;
                        break;

                case '?':
                default:
                        err++;
                        break;
                }
        }
        if (err || argc > optind) {
                fprintf(stderr, "Usage: lsiio [options]...\n"
                        "List industrial I/O devices\n"
                        "  -v  Increase verbosity (may be given multiple times)\n");
                exit(1);
        }

        return dump_devices();
}