Utilisation de MODBUS sur NanoPi

MODBUS est un protocole de communication non-propriétaire, créé en 1979 par Modicon, utilisé pour des réseaux d’automates programmables, relevant du niveau applicatif, c’est-à-dire du niveau 7 du Modèle OSI. Ce protocole basé sur une structure hiérarchisée entre un client unique et plusieurs serveurs est dans le domaine public et sa spécification est publique.

Ce protocole a rencontré beaucoup de succès depuis sa création du fait de sa simplicité et de sa bonne fiabilité. Il repose sur un protocole maître-esclave.

Le protocole MODBUS peut être implémenté :

  • sur une liaison série asynchrone de type RS-232, RS-422 ou RS-485 ou TTY (boucle de courant), avec des débits et sur des distances variables ; on parle alors de MODBUS over Serial Line;
  • via TCP/IP sur Ethernet ; on parle alors de MODBUS over TCP/IP ; le port logiciel 502 est dédié à ce protocole
  • via ModBus Plus. ModBus Plus est un réseau à passage de jetons à 1 Mb/s, pouvant transporter les trames Modbus et d’autres services propre à ce réseau.

Une liaison multipoints de type RS-485 relie client et serveurs via une paire différentielle qui permet un débit élevé (jusqu’à 10 méga-bits par seconde) sur une distance importante (jusqu’à 1 200 m). Elle ne dispose que de 2 bornes qui alternativement passent les données dans un sens puis dans l’autre.

Sur une liaison série asynchrone, le codage des caractères peut être en binaire (RTU) ou en ASCII (plus lent !).

Le NanoPi est probablement le plus récent des concurrents du Raspberry Pi. FriendlyARM mise sur les prix et la compacité.

Dans cet article nous allons apprendre à contrôler des esclaves MODBUS à partir d’un NanoPi Neo utilisant une liaison RS485 (en RTU).

Le NanoPi utilise un système Armbian en version 5.41 (kernel 4.14.18), c’est important de la savoir car la configuration des liaisons série (UART) sur les noyaux à partir de la version 4 est totalement différente des précédents noyaux en version 3.4.

Le matériel

Nous avons donc besoin d’un module supplémentaire afin d’adapter les signaux de l’UART du NanoPi au RS485. Il faut qu’il soit alimenté en 3.3V, tension utilisée par tous les signaux du GPIO.

Nous pouvons choisir celui de Sparkfun qui utilise un circuit SP3485. On trouve des clones de ce module sur le site AliExpress pour quelques centimes d’euros.

Nous choisissons d’utiliser l’UART1 du NanoPi. Nous utiliserons donc les signaux:

  • UART1_TX (PG6, broche 8 du GPIO) qui sera relié au signal DI (Driver Input) du SP3485 (RX-I sur le module Sparkfun)
  • UART1_RX (PG7, broche 10 du GPIO) qui sera relié au signal RO (Receiver Output) du SP3485 (TX-O sur le module Sparkfun)
  • UART1_RTS (PG8, broche 16 du GPIO) qui sera relié aux signaux DE (Driver Enable) et /RE (Receiver Enable) du SP3485 (rélié ensemble à RTS sur le module Sparkfun). Ce signal est très important car il assure le multiplexage half-duplex sur la paire différentielle.
  • GND (broche 6 du GPIO) et 3.3V (broche 1 du GPIO) qui seront reliés respectivement aux broches GND et 3-5V sur le module Sparkfun.

La connexion du ou des esclaves MODBUS se fera conformément au standard RS485. Il convient de bien repéré la polarité de la paire différentielle (brancher à l’envers: ça ne marche pas ! hein Mickaël). Comme le décrit cet article, ce problème est loin d’être simple. En effet, certains fabricant repère leurs broches par A et B, mais ne respectent par la standard RS485.

C’est la raison pour laquelle, il est préférable de raisonner en borne + et borne -. Sur la ligne, toutes les bornes + de la paire différentielles seront reliées ensemble (idem pour le -). L’inversion de polarité est la cause de « panne » la plus courante sur un bus RS485, on vérifiera donc systématiquement ce point (en vérifiant le schéma interne de l’esclave si nécessaire) en cas de dysfonctionnement.

Une autre cause de dysfonctionnement sur RS485 est l’absence de résistances de terminaison et/ou de polarisation. Pour que la laison fonctionne dans de bonnes conditions, il faut que la paire différentielle:

  • soit terminée à chaque extrémité par une résistance de terminaison de 120 ohms (dépend de l’impédance caracttistique du câble)
  • soit polarisée en l’absence de signal par une résistante reliant le VCC à la borne + et une résistance reliant le GND à la borne – (voir cet article )

Cablage RS485

Configuration du NanoPi

Il est nécessaire de valider l’UART1 et d’activer le signal RTS sur la NanoPi.

Pour valider l’UART1, il suffit de lancer Armbian-config:

$ sudo armbian-config

Il faut ensuite chosir le menu System puis Hardware et valider uart1. Il faudra accepter le redémarrage proposé…

Armbian-config

Pour activer le signal RTS, il faut modifier le fichier /boot/armbianEnv.txt :

$ sudo nano /boot/armbianEnv.txt

On y ajoutera la ligne :

param_uart1_rtscts=1

Puis il faut redémarrer le NanoPi.

La liaison série de l’UART1 est accessible par le fichier /dev/ttyS1

Le logiciel

Nous utiliserons le logiciel mbpoll en version 1.4.

mbpoll est un utilitaire en ligne de commande permettant de dialoguer avec des esclaves ModBus RTU ou TCP.
Il utilise libmodbus.

mbpoll permet de:

  • lire des entrées discrètes
  • lire et écrire des sorties binaires (coil)
  • lire des registres d’entrée (input register)
  • lire et écrire des registres de sortie (holding register)

La lecture et l’écriture de registres peut se faire au format décimal, hexadécimal ou flottant simple précision.

Installation de mbpoll

Le moyen le plus rapide et le plus sûr d’installer mbpoll consiste à utiliser le référentiel APT de piduino.org. Vous devez donc procéder comme suit:

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
sudo add-apt-repository 'deb http://apt.piduino.org stretch piduino'
sudo apt update
sudo apt install mbpoll

Ce référentiel fournit les packages mbpoll et libmodbus (version 3.1.4) pour les architectures i386, amd64, armhf et arm64. Dans les commandes ci-dessus, le référentiel est une distribution Debian Stretch, mais vous pouvez également choisir Ubuntu Xenial en remplaçant stretch par xenial.

Des explications plus complètes sur l’installation et la compilation sont disponibles sur le site de mbpoll.

Utilisation de mbpoll

mbpoll dispose d’une aide en ligne avec l’option -h :

$ mbpoll -h
usage : mbpoll [ options ] device|host [ writevalues... ] [ options ]

ModBus Master Simulator. It allows to read and write in ModBus slave registers
                         connected by serial (RTU only) or TCP.

Arguments :
  device        Serial port when using ModBus RTU protocol
                  COM1, COM2 ...              on Windows
                  /dev/ttyS0, /dev/ttyS1 ...  on Linux
                  /dev/ser1, /dev/ser2 ...    on QNX
  host          Host name or dotted IP address when using ModBus/TCP protocol
  writevalues   List of values to be written.
                If none specified (default) mbpoll reads data.
                If negative numbers are provided, it will precede the list of
                data to be written by two dashes ('--'). for example :
                mbpoll -t4:int /dev/ttyUSB0 -- 123 -1568 8974 -12
General options : 
  -m #          mode (rtu or tcp, TCP is default)
  -a #          Slave address (1-255 for rtu, 0-255 for tcp, 1 is default)
                for reading, it is possible to give an address list
                separated by commas or colons, for example :
                -a 32,33,34,36:40 read [32,33,34,36,37,38,39,40]
  -r #          Start reference (1 is default)
  -c #          Number of values to read (1-125, 1 is default)
  -u            Read the description of the type, the current status, and other
                information specific to a remote device (RTU only)
  -t 0          Discrete output (coil) data type (binary 0 or 1)
  -t 1          Discrete input data type (binary 0 or 1)
  -t 3          16-bit input register data type
  -t 3:hex      16-bit input register data type with hex display
  -t 3:int      32-bit integer data type in input register table
  -t 3:float    32-bit float data type in input register table
  -t 4          16-bit output (holding) register data type (default)
  -t 4:hex      16-bit output (holding) register data type with hex display
  -t 4:int      32-bit integer data type in output (holding) register table
  -t 4:float    32-bit float data type in output (holding) register table
  -0            First reference is 0 (PDU addressing) instead 1
  -B            Big endian word order for 32-bit integer and float
  -1            Poll only once only, otherwise every poll rate interval
  -l #          Poll rate in ms, ( > 100, 1000 is default)
  -o #          Time-out in seconds (0.01 - 10.00, 1.00 s is default)
Options for ModBus / TCP : 
  -p #          TCP port number (502 is default)
Options for ModBus RTU : 
  -b #          Baudrate (1200-921600, 19200 is default)
  -d #          Databits (7 or 8, 8 for RTU)
  -s #          Stopbits (1 or 2, 1 is default)
  -P #          Parity (none, even, odd, even is default)
  -R [#]        RS-485 mode (/RTS on (0) after sending)
                 Optional parameter for the GPIO RTS pin number
  -F [#]        RS-485 mode (/RTS on (0) when sending)
                 Optional parameter for the GPIO RTS pin number

  -h            Print this help summary page
  -V            Print version and exit
  -v            Verbose mode.  Causes mbpoll to print debugging messages about
                its progress.  This is helpful in debugging connection...

Par exemple si l’on souhaite lire les 2 premiers regsitres d’entrée (1 et 2 donc) d’un esclave à l’adresse décimale 33 connecté en RS485 avec un baudrate de 38400 et une période d’interrogation de 500 millisecondes :

$ mbpoll -a33 -b38400 -t3 -c2 -R  /dev/ttyS1 -l 500
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus RTU
Slave configuration...: address = [33]
                        start reference = 1, count = 2
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 500 ms
Data type.............: 16-bit register, input register table

-- Polling slave 33... Ctrl-C to stop)
[1]:  9975
[2]:  10021
-- Polling slave 33... Ctrl-C to stop)
[1]:  9997
[2]:  10021
^C--- /dev/ttyS1 poll statistics ---
2 frames transmitted, 2 received, 0 errors, 0.0% frame loss

everything was closed.
Have a nice day !

On peut voir à l’oscilloscope que la transmission s’effectue correctement :

Signaux RS485

En voie 1 on a le signal RTS (DE/RE) et en voie 2 le signal TX (DI).

Si on souhaite lire l’identifiant de l’esclave (Code fonction 17: Report Slave ID) :

$ mbpoll -a33 -b38400 -u -R  /dev/ttyS1 
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Protocol configuration: Modbus RTU
Slave configuration...: address = 33, report slave id
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 1000 ms
Length: 14
Id    : 0x02
Status: On
Data  : press-1.1.58

mbpoll dispose d’un mode « mise au point » grâce à l’option -v, très pratique pour le dépannage :

$ mbpoll -a33 -b38400 -u -R  /dev/ttyS1 -v
debug enabled
Set mode to RTU for serial port
Set device=/dev/ttyS1
mbpoll 1.4 - FieldTalk(tm) Modbus(R) Master Simulator
Copyright © 2015-2018 Pascal JEAN, https://github.com/epsilonrt/mbpoll
This program comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under certain conditions; type 'mbpoll -w' for details.

Opening /dev/ttyS1 at 38400 bauds (E, 8, 1)
Set response timeout to 1 sec, 0 us
Protocol configuration: Modbus RTU
Slave configuration...: address = 33, report slave id
Communication.........: /dev/ttyS1,      38400-8E1 
                        t/o 1.00 s, poll rate 1000 ms
[21][11][D9][EC]
Sending request using RTS signal
Waiting for a confirmation...
<21><11><0E><02><FF><70><72><65><73><73><2D><31><2E><31><2E><35><38><81><B7>
Length: 14
Id    : 0x02
Status: On
Data  : press-1.1.58

Dans la commande ci-dessus, on peut voir que le NanoPi a envoyer la trame [21][11][D9][EC] et que l’esclave MODBUS lui a répondu <21><11><0E><02><FF><70><72><65><73><73><2D><31><2E><31><2E><35><38><81><B7>.

Utiliser facilement les librairies avec CodeLite

Nous avons vu l’utilisation de CodeLite dans un article précédent.

En programmation, on utilise souvent des librairies.

Une librairie fournit un ensemble de fonctions ou d’objets permettant de remplir des tâches complexes. On pourra citer dans le domaine de l’embarqué: libmodbus, libgammu, piduino

Pour utiliser une librairie en langage C ou C++, il faut disposer de 2 choses:

  • Un ou plusieurs fichiers .h qui décrivent l’interface d’utilisation (API) et qui doivent être ajoutés au début des fichiers où on utilise la librairie (avec un #include)
  • Un fichier contenant le code exécutable et les données statiques. Dans Linux, on trouve 2 formats:
    • le fichier statique (extension .a) qui permettra d’inclure les informations au moment de la compilation
    • le fichier dynamique (extension .so) qui chargera les informations au moment de l’exécution.

Il faut préciser qu’il est parfois nécessaire pour compiler un programme correctement avec une librairie d’ajouter des options sur la ligne de commande du compilateur (gcc).

Lorsqu’on souhaite utiliser CodeLite, il faut donc lui donc lui donner toutes ses informations, ce qui peut devenir fastidieux !

Heureusement, la plupart du temps, les librairies sont fournies avec des fichiers qui décrivent ses informations. Pour lire ses informations, on utilise le programme pkg-config.

Faisons un essai avec libmodbus (qu’on suppose installée…):

$ pkg-config --cflags --libs libmodbus
-I/usr/local/include -L/usr/local/lib -lmodbus
--cflags permet de demander les options à passer au compilateur, alors que --libs permet de demander les options à passer à l’éditeur de liens (linker).

Dans CodeLite, nous pouvons utiliser pkg-config pour configurer automatiquement les options de compilation et d’édition de liens.

En faisant, un clic-droit sur le dossier du projet dans le bandeau de gauche, on sélectionne Settings, puis Compiler:

On sélectionne C Compiler Options, puis on clic sur les 3 petits points

On entre dans la boite blanche la commande $(shell pkg-config --cflags libmodbus)

Puis on sélectionne Linker

On entre dans la boite blanche la commande $(shell pkg-config --libs libmodbus)

On valide par Ok

On peut alors voir lors de la compilation, que les options ont été automatiquement passées au compilateur:

Utiliser CodeLite en sudo

Il est parfois nécessaire de lancer CodeLite en sudo pour effectuer la mise au point d’un programme qui nécessite les permissions d’accès root.

Si l’on lance CodeLite avec sudo, cela échoue avec le message :

X11 connection rejected because of wrong authentication.
23:20:43: Error: Unable to initialize GTK+, is DISPLAY set properly?

Par défaut, X-Window refuse d’exécuter un connexion en root.

Il est possible de feinter le système de la façon suivante:

sudo -s
xauth merge /home/pascal/.Xauthority
exit

Dans les commandes ci-dessus, on commence par passer en root, puis on récupère les informations de connexion de l’utilisateur courant (pascal ici), puis on revient en utilisateur normal.

On peut maintenant lancer la commande

sudo codelite &

CodeLite se lance sans problème…

Utilisation de CodeLite

CodeLite est environnement de développement très léger qui peut facilement être utilisé sur NanoPi (ou toute autre carte Pi).

Dans ce tutoriel, nous allons utiliser Codelite pour créer un programme en langage C simple sur un NanoPi Neo sous ArmBian 5.41 (basé sur une Debian Stretch). Notre programme contiendra un simple printf, pour l’instant, mais 3 autres articles utiliserons ce premier tutoriel comme base afin d’effectuer des opérations sur MODBUS…

Dans un premier temps nous allons nous connecter au NanoPi par SSH à partir d’un système Linux (pour faire simple). Il faut savoir qu’il est possible d’effectuer les mêmes opérations sous MS Windows à condition d’installer un client SSH comme PuTTY, de le configurer pour utiliser le X11 Forwarding et d’installer un serveur X-Window comme Xming. Comme nous voulons faire simple, nous allons nous connecter à partir de Linux 😉

Connexion au NanoPi

Pour se connecter au NanoPi à l’adresse 192.168.122.70 avec mon identifiant (pascal), je tape :

    $ ssh pascal@192.168.122.70

Avant de lancer CodeLite, il faut l’installer ainsi que quelques paquets nécessaires (mais non installés automatiquement) :

    $ sudo apt upate
    $ sudo apt install codelite codelite-plugins hicolor-icon-theme gtk2-engines-pixbuf lxterminal libatk-adaptor libgail-common libcanberra-gtk-module xauth

Une fois les installations effectuées, on se déconnecte, puis on se reconnecte en ajoutant l’option -X et on peut alors lancer CodeLite :

    $ exit
    $ ssh pascal@192.168.122.70 -X
    $ codelite &

La fenêtre ci-dessous s’ouvre.

Création de l’espace de travail

CodeLite utilise des espaces de travail qui permettent de regrouper un ensemble de projets. Pour un espace de travail, on utilise le menu File, puis New, puis Workspace. La fenêtre ci-dessous s’ouvre.

On remplit le nom de notre nouveau workspace (libmodbus-tutorial), on choisit le dossier en cochant la case demandant de créer un nouveau répertoire.

Après avoir cliqué sur Ok, on se retrouve avec le nouvel espace de travail présent dans le bandeau latéral à gauche de la fenêtre de CodeLite:

Création du projet

Dans CodeLite, un programme correspond à un projet. Il existe un grand nombre de modèles présent, mais dans notre cas, nous allons créer un simple programme C en ligne de commande.

Pour créer un nouveau projet, on utilise le menu File, puis New, puis Project (on peut aussi faire un clic-droit sur l’espace de travail dans le bandeau de gauche).

La fenêtre suivante nous permet de choisir le modèle de projet (Simple executable (gcc)):

Il faut alors choisir un nom pour notre projet (hello-world ici) et cocher la case demandant de créer un nouveau répertoire dans celui de l’espace de travail:

Le choix de la chaîne de compilation doit laisser par défaut, il suffit donc de cliquer sur Finish dans la fenêtre suivante:

On voit alors dans le bandeau de gauche notre nouveau projet, en cliquant sur le petit triangle à gauche du dossier bleu hello-world, on peut voir qu’il contient un dossier « virtuel » nommé src que l’on peut lui même dérouler. On voit que le projet contient un seul fichier main.c Un double clic que le fichier main.c permet de l’ouvrir:

Compilation du projet

Pour compiler le programme il faut utiliser le menu Build, puis Build Project. On peut aussi utiliser la touche F7 ou l’icône .

Un rapport en bas de la fenêtre indique que la compilation s’est effectuée avec succès.

Exécution du programme

Pour exécuter le programme on utilise le menu Build, puis Run. On peut aussi utiliser la touche Ctrl+F5 ou l’icône .

Le programme se lance, une fenêtre de terminal s’ouvre et nous affiche le message correspondant à notre prinf:

Un appui sur la touche Entrée met fin au programme.

Exécution en mode pas à pas

Il est possible d’exécuter le programme en mode pas à pas.

Pour lancer l’exécution en mode pas à pas, on utilise le menu Debugger puis Start/Continue Debugger. On peut aussi utiliser la touche F5 ou l’icône

Le mode permet :

  • d’exécuter instruction par instruction (touche F10)
  • d’exécuter instruction par instruction en rentrant dans les fonctions (touche F11)
  • de placer des points d’arrêts afin de stoper le programme lorsqu’il exécute une instruction à une ligne donnée (touche F9)
  • de lancer le programme jusqu’au prochain point d’arrêt ou jusqu’à la fin (touche F5)
  • de relancer le programme (touche Shift+Ctrl+F5)
  • de surveiller le contenu des variables locales (bandeau de droite, onglet Locals)
  • de surveiller d’autres variables: globales … (bandeau de droite, onglet Watch)

Et bien d’autres choses.

Toutes ses actions sont disponible à partir de la barre d’outils dans la bandeau:

Installation d’une RTC sur ArmBian

Sur les Pi Board comme le Raspberry Pi ou le Nano Pi, on ne dispose pas d’horloge temps réel (RTC pour Real Time Clock) capable de mémoriser la date et l’heure lorsqu’on coupe l’alimentation. Les concepteurs de ces cartes sont tout simplement partie du postulat que la carte sera reliée à une réseau disposant d’un serveur de temps NTP. Dans la plupart des cas c’est vrai, mais dans d’autres c’est fortement handicapant ! Cela est d’autant plus navrant, que les processeurs AllWinner H3 et H5 utilisés par les cartes Nano Pi disposent de RTC sur la puce du SoC ! Les concepteurs n’ont juste pas jugé utile de sortir sur une pastille la tension de backup de cette RTC ! La solution est donc d’ajouter un circuit intégré RTC généralement connecté à la carte par le bus I2c.

Le but de ce tutoriel est d’expliquer la procédure d’installation et de configuration d’un circuit RTC sur un système embarqué utilisant ArmBian.

Dans ce document nous utiliserons, pour l’exemple, une RTC Texas Instruments BQ32000 que nous relierons à une carte NanoPi Neo. La version de ArmBian utilisée est la Debian Stretch 5.41 (kernel 4.14.18).

NanoPi Neo

Les broches SDA et SCL de la RTC seront reliées aux broches correspondantes du bus I2c0 du connecteur GPIO (broche 3 et 5).

Vérification de la présence du driver de la RTC

Dans un premier temps, il faut vérifier la disponibilité du driver de la RTC BQ320000 sur le système :

$ sudo find / -name "*bq32*.ko"
/lib/modules/4.14.18-sunxi/kernel/drivers/rtc/rtc-bq32k.ko

Ici le driver est présent et s’appelle rtc-bq32k.ko. Si le fichier n’est pas présent, c’est qu’il n’a pas été compilé lors de la compilation du kernel 🙁 Dans ce cas, il faudra valider la compilation du driver correspondant et recompiler le système. Cette opération sort du contexte de ce document, mais il faut savoir que cela n’est pas très compliqué à condition de disposer d’un PC puissant sous Ubuntu Xenial ! Le site de ArmBian explique la procédure.

Chargement du driver au boot

Avec les noyaux modernes, le chargement des drivers se fait par l’intermédiaire de Device Tree. Device Tree que l’on peut traduire par « Arborescence matérielle » est une façon de décrire tout le matériel disponible sur un système.

Si ce nouveau système a facilité la vie des développeurs de Linux, il a sacrément compliqué celle des utilisateurs qui souhaitent juste ajouter une « chite » RTC sur leur PiBoard préférée. Disons le clairement : Device Tree c’est de la bonne veille « Usine à gaz » conçue par des informaticiens totalement crazy 😀

Heureusement les concepteurs de ArmBian ont choisi de faciliter la vie de leurs utilisateurs, mais il va falloir quand même se retrousser les manches et mettre les mains dans le cambouis !

La site ArmBian présente la procédure.

Ce qu’il faut savoir :

  • Une configuration matérielle est décrite par un fichier overlay d’extension .dtbo qui sont des fichiers binaires (dtbo pour device tree binary overlay)
  • Un fichier overlay est créé à l’aide du compilateur dtc à partir d’un fichier source .dts qui respecte la syntaxe fdt pour Flat Device Tree (et qui est loin d’être abordable pour le non-informaticien…)
  • Les fichiers overlay décrivant la configuration de base de la carte se trouvent dans /boot/dtb
  • Les fichiers overlay des circuits ajoutés se trouvent dans /boot/overlay-user

Choisir la configuration du driver

Chaque driver de circuit disposent d’options pouvant être ajoutées à l’overlay pour modifier son comportement. Ces options sont documentées directement dans les sources du kernel Sic ! Pour la BQ32000 et notre version de noyau c’est ici, voilà le fichier :

* TI BQ32000                I2C Serial Real-Time Clock

Required properties:
- compatible: Should contain "ti,bq32000".
- reg: I2C address for chip

Optional properties:
- trickle-resistor-ohms : Selected resistor for trickle charger
       Values usable are 1120 and 20180
       Should be given if trickle charger should be enabled
- trickle-diode-disable : Do not use internal trickle charger diode
       Should be given if internal trickle charger diode should be disabled
Example:
       bq32000: rtc@68 {
               compatible = "ti,bq32000";
               trickle-resistor-ohms = <1120>;
               reg = <0x68>;
       };

Comme on peut le voir au paragraphe 7.3.3 du datasheet, le BQ32000 peut être utilisé avec une pile ou un super condensateur (de plusieurs Farads !), afin de maintenir l’heure lorsque l’alimentation du Nano Pi est coupée. Dans le cas d’une pile, le circuit de charge (trickle charging) devra être désactivé, dans le cas de l’utilisation d’un super condensateur il doit être activé. C’est le sens des options :

  • trickle-diode-disable qui permet de désactiver le circuit de charge (TCH2 ouvert). Si cette option n’est pas indiquée, le circuit de charge est activé, ce qui peut conduire à l’explosion de la pile !
  • trickle-resistor-ohms qui permet de choisir la résistance du circuit de charge (TCFE ouvert ou fermé). Il sera nécessaire de consulter la documenation du super condensateur pour choisir la configuration correcte (résistance de 1120 ohms, la plupart du temps, cad TCFE ouvert)

Une explication des courants/temps de charge/décharge est présent sur le site de Maxim

Création du fichier source .dts

A partir des fichiers du dossier examples du dépôt sunxi-DT-overlays, on créée le fichier i2c-bq32000.dts suivant :

/dts-v1/;
/plugin/;

/ {
  compatible = "allwinner,sun4i-a10", "allwinner,sun7i-a20", "allwinner,sun8i-h3", "allwinner,sun50i-a64", "allwinner,sun50i-h5";

  /* 
   * Aliases can be used to set the external RTC as rtc0
   * Needs supplying the correct path to the I2C controller RTC is connected to,
   * this example is for I2C0 on H3
   * NOTE: setting time at boot by the kernel
   * may not work in some cases if the external RTC module is loaded too late
   */
  fragment@0 {
    target-path = "/aliases";
    __overlay__ {
      rtc0 = "/soc/i2c@01c2ac00/bq32000@68";
    };
  };

  fragment@1 {
    target = <&i2c0>;
    __overlay__ {
      #address-cells = <1>;
      #size-cells = <0>;
      bq32000@68 {
        compatible = "ti,bq32000";
        reg = <0x68>;
        trickle-diode-disable;
        status = "okay";
      };
    };
  };
};

L’option trickle-diode-disable; étant présente, on est dans le cas de l’utilisation d’une pile, si ce n’est pas le cas, il faudra la remplacer par trickle-resistor-ohms = <1120>; (ou 20180…).

Il faut noter que sur le NanoPi Neo, le bus I2c utilisé est le bus 0. L’adresse de son contrôleur est 0x01c2ac00. Si on utilise une autre carte et/ou un autre bus, il faudra rechercher cette adresse à l’aide de la commande :

$ sudo dtc -qq -I fs /proc/device-tree | grep -e '/soc/i2c@'

Dans le cas du NanoPi Neo cela nous donne à l’affichage :

i2c1 = "/soc/i2c@01c2b000";
i2c2 = "/soc/i2c@01c2b400";
i2c0 = "/soc/i2c@01c2ac00";
r_i2c = "/soc/i2c@01f02400";
i2c0 = "/soc/i2c@01c2ac00";

On voit que le SoC H3 du NanoPi Neo dispose de 3 bus I2c aux adresses 0x01c2ac00 pour le bus 0, 0x01c2b000 pour le bus 1 et 0x01c2b400 pour le bus 2.

Les lignes rtc0 = « /soc/i2c@… et target = <&i2c… devront alors être modifiées en conséquence.

Installation du fichier overlay .dtbo

Pour compiler les fichiers overlay, il faut installer les fichiers en-tête du kernel utilisé, cela se fait grâce à la commande :

$ sudo armbian-config

On choisit le menu Software puis Headers pour installer (attention si cela a déjà été effectuée cela désisnstalle les fichiers !). Faire Cancel et Cancel pour sortir.

Pour compiler et installer notre overlay, on utilise la commande :

$ sudo armbian-add-overlay i2c-bq32000.dts

Cela affiche le message suivant :

  Compiling the overlay
  Copying the compiled overlay file to /boot/overlay-user/
  Reboot is required to apply the changes

Un petit ls /boot/overlay-user nous permet de vérifier la précence du fichier .dtbo :

$ ls /boot/overlay-user
i2c-bq32000.dtbo

Vérifications de bon fonctionnement de la RTC

Après reboot, on peut vérifier le chargement du driver dans le journal du kernel :

$ dmesg | grep rtc
[    4.556601] sun6i-rtc 1f00000.rtc: rtc core: registered rtc-sun6i as rtc0
[    4.556613] sun6i-rtc 1f00000.rtc: RTC enabled
[    5.168535] bq32k 0-0068: rtc core: registered bq32k as rtc1

Si on ne trouve pas de trace du driver bk32k, il faudra observer les messages du kernel au démarrage du système sur la liaison série de debug (UART0).

Notre RTC a donc été connectée au fichier /dev/rtc1. Comme on peut le voir ci-dessus, la RTC du SoC a elle aussi été connectée au système par l’intermédiaire du fichier /dev/rtc0. Cette RTC n’ayant pas de tension de backup, il faudra la désactiver à l’étape suivante.

Dans un premier temps vérifions le fonctionnement de notre RTC BQ32000 en afffichant la date/heure système (NTP) et celle de notre RTC :

$ date && sudo hwclock -r -f /dev/rtc1
samedi 17 mars 2018, 16:26:02 (UTC+0100)
2018-03-17 16:24:47.160983+0100

Nous constatons que notre RTC fonctionne, mais qu’elle n’est pas à l’heure ! Pour un affichage simple de l’heure RTC, on effectue la commande

$ sudo hwclock -r -f /dev/rtc1

Pour mettre à l’heure notre RTC à partir de l’heure NTP :

$ sudo hwclock -w -f /dev/rtc1

Vérifions :

$ date && sudo hwclock -r -f /dev/rtc1
samedi 17 mars 2018, 16:30:48 (UTC+0100)
2018-03-17 16:30:47.162255+0100

Désactivation de la RTC du SoC H3

La RTC du SoC H3 doit être désactivée car le kernel va systématiquement la reconfigurer comme RTC par défaut (/dev/rtc) puisque son driver est compilé à l’intérieur du noyau !

Pour désactiver la RTC du SoC H3, il faut créer un fichier overlay…

Nous allons dans un premier temps, « décompiler » la configuration du NanoPi grâce à la commande :

$ sudo dtc -qq -I fs /proc/device-tree > nanopi-neo.dts

Cela créée le fichier source nanopi-neo.dts, une recherche avec Geany, nous permet de voir les partie qui concerne la RTC du SoC :

....
rtc = "/soc/rtc@01f00000";
....
rtc@01f00000 {
  compatible = "allwinner,sun6i-a31-rtc";
  interrupts = <0x0 0x28 0x4 0x0 0x29 0x4>;
  phandle = <0x69>;
  reg = <0x1f00000 0x54>;
  linux,phandle = <0x69>;
};
....

Le fichier source overlay rtc-disable.dts permettant de la désactiver est le suivant :

/dts-v1/;
/plugin/;

/ {
  compatible = "allwinner,sun4i-a10", "allwinner,sun7i-a20", "allwinner,sun8i-h3", "allwinner,sun50i-a64", "allwinner,sun50i-h5";

 fragment@0 {
   target = <&rtc>;

   __overlay__ {
     status = "disabled";
   };
 };
};

Une fois compilé et installé à l’aide de sudo armbian-add-overlay rtc-disable.dts et après redémarrage, on peut constater que cette rtc est effectivement désactivée :

rtc@01f00000 {
  compatible = "allwinner,sun6i-a31-rtc";
  status = "disabled";
  interrupts = <0x0 0x28 0x4 0x0 0x29 0x4>;
  phandle = <0x69>;
  reg = <0x1f00000 0x54>;
  linux,phandle = <0x69>;
};

Un **ls -l /dev/rtc***, nous permet de voir qu’il n’a plus qu’une seule rtc, la rtc0 :

lrwxrwxrwx 1 root root      4 nov.   3  2016 /dev/rtc -> rtc0
crw------- 1 root root 253, 0 nov.   3  2016 /dev/rtc0

Une recherche dans le journal du kernel nous permet de voir que c’est la BQ32000 :

$ dmesg | grep rtc
[    5.182244] bq32k 0-0068: rtc core: registered bq32k as rtc0

Un sudo hwclock -r nous permet de lire les informations de notre RTC :

2018-03-17 21:50:43.386528+0100

Mise à l’heure RTC au boot

Le circuit RTC est maintenant détecté et configuré par le noyau, mais ce n’est pas pour autant qu’il est utilisé par le système !

Nous allons créer un script qui sera lancé automatiquement par le démon de démarrage du système systemd. Cette étape est dérivée de celle présentée sur le site hackable.fr

Tout d’abord on créée un script shell qui utilise hwclock pour mettre à l’heure le système avec l’heure RTC, le fichier rtc-setup est le suivant :

#!/bin/sh
hwclock -s --utc
echo "System Time synced with RTC Time"

Puis on rend ce script exécutable :

$ chmod +x rtc-setup

On peut tester ce script et vérifier que l’heure système et bien la même que l’heure RTC :

$ sudo ./rtc-setup
$ date && sudo hwclock -r
samedi 17 mars 2018, 18:06:36 (UTC+0100)
2018-03-17 18:06:36.829015+0100

On créée ensuite le répertoire /usr/lib/systemd/scripts et on y copie notre script rtc-setup :

$ sudo mkdir -p /usr/lib/systemd/scripts
$ sudo cp rtc-setup /usr/lib/systemd/scripts

On créée ensuite un fichier service systemd pour automatiser le démarrage de rtc-setup au boot, le fichier rtc-init.service est le suivant :

[Unit]
Description=RTC Clock Setup and Time Sync
Before=cron.service

[Service]
Type=oneshot
ExecStart=/usr/lib/systemd/scripts/rtc-setup

[Install]
WantedBy=multi-user.target

On copie ce fichier dans le répertoire /etc/systemd/system :

$ sudo cp rtc-init.service /etc/systemd/system

Il ne reste plus qu’à valider le service rtc-init et à invalider le service fake-hwclock :

$ sudo systemctl enable rtc-init
$ sudo systemctl disable fake-hwclock

fake-hwclock est un service qui palie à l’absence de RTC sur un système embarqué, son rôle est d’écrire régulièrement dans un fichier l’heure système (généralement récupérée par NTP) et la restaurer au démarrage. Cela permet, en cas d’absence de réseau, d’avoir une heure cohérente mais complétement fausse.

Il est maintenant nécessaire de redémarrer et de vérifier que tout fonctionne :

$ sudo reboot
....
$ date && sudo hwclock -r

Retour à la configuration par défaut

Pour revenir à la configuration par défaut, si par exemple, on retire le circuit RTC, il suffit de dévalider le service rtc-init et de valider le service fake-hwclock :

$ sudo systemctl disable rtc-init
$ sudo systemctl enable fake-hwclock

Puis de redémarrer :

$ sudo reboot

Corriger une adresse MAC aléatoire dans Armbian sur NanoPi

Constatation du problème

Lors de chaque démarrage, l’adresse MAC de eth0 est modifiée de façon aléatoire.

Ce problème a été constaté sur des cartes FriendlyArm NanoPi Neo v1.1 et v1.2 avec Armbian utilisant un kernel 4.x. On constate que ce problème n’existe pas quand on utilise une image FriendlyCore (Xenial avec kernel 4.14.0).

Version du noyau utilisé dans ce tuto:

Linux nanopineo 4.14.18-sunxi #2 SMP Sat Feb 10 19:46:30 CET 2018 armv7l GNU/Linux

Solution rapide

Pour ceux qui n’ont pas de temps à perdre avec les explications:

sudo armbian-config

On va dans System puis Freeze. Puis:

git clone http://github.com/epsilonrt/armbian-nanopi-ethaddr-patch
cd armbian-nanopi-ethaddr-patch
cp /boot/boot.cmd .
patch -p1 < boot.cmd.patch
mkimage -C none -A arm -T script -d boot.cmd boot.scr
sudo mv /boot/boot.cmd /boot/boot.cmd.orig
sudo mv /boot/boot.scr /boot/boot.scr.orig
sudo cp boot.* /boot
sudo dtc -I dtb -O dts -o sun8i-h3-nanopi-neo.dts /boot/dtb/sun8i-h3-nanopi-neo.dtb
sudo patch -p1 < sun8i-h3-nanopi-neo.dts.patch
sudo dtc -I dts -O dtb -o sun8i-h3-nanopi-neo.dtb sun8i-h3-nanopi-neo.dts
sudo mv /boot/dtb/sun8i-h3-nanopi-neo.dtb /boot/dtb/sun8i-h3-nanopi-neo.dtb.orig
sudo cp sun8i-h3-nanopi-neo.dtb /boot/dtb
sudo reboot

Analyse du problème

Dans la mainline du kernel 4.x, le driver de la carte Ethernet (dwmac-sun8i), renvoit une adresse MAC aléatoire. Cela est dû, sans doute, à une mauvaise intégration de Device Tree dans ce driver. Sur le site linux-sunxi il est dit :

« This driver is mainline, but DT was reverted in 4.13-rc7. DT should be back soon.« 

Voilà ce qu’indique le kernel au démarrage (dmesg) :

[ 10.889856] dwmac-sun8i 1c30000.ethernet eth0: device MAC address 1a:b2:4a:84:f7:fc
[ 10.890960] Generic PHY 0.1:01: attached PHY driver > [Generic PHY] (mii_bus:phy_addr=0.1:01, irq=POLL)
….
[ 14.009054] dwmac-sun8i 1c30000.ethernet eth0: Link is Up – 100Mbps/Full – flow control off

Le programme de boot du nanopi (u-boot) a une variable ethaddr mais qui pas utilisée par le kernel ! On peut lire cette variable en accédant à la ligne de commande de u-boot. Il faut, pour cela, connecter un adaptateur série-usb sur le connecteur UART0 (debug) du NanoPi. Au boot, il faut appuyer tout de suite sur la touche Espace, puis utiliser printenv:

printenv ethaddr

On doit avoir une adresse commencant par 02:81

Recherche de la solution

Cette solution est issue du reverse engineering du BSP de FriendlyArm.

u-boot utilise le fichier /boot/boot.scr (script de démarrage), ce fichier est une version « compilée » du fichier /boot/boot.cmd.

Dans le fichier /boot/boot.cmd de FriendlyArm, on peut voir les 2 lignes ci-dessous:

# setup MAC address 
fdt set ethernet0 local-mac-address ${mac_node}

La ligne avec fdt a pour but de passer l’adresse MAC (local-mac-address) au kernel par le device tree. Elle fait référence à ethernet0 qui utilisé pour désigner la carte eth0.

Dans le fichier sun8i-h3-nanopi-neo.dts de FriendlyArm, on peut voir que ethernet0 est un alias de /soc/ethernet@1c30000. Dans le bloc de ethernet@1c30000, on peut voir un paramètre local-mac-address:

status = "okay";
local-mac-address = [00 00 00 00 00 00];

Solution trouvée !

Mise en oeuvre détaillée de la solution

Il faut commencer par geler la mise à jour du noyau et de u-boot:

sudo armbian-config

On va dans System puis Freeze. Si on veut vérifier :

dpkg -l | grep ^hi

hi linux-dtb-next-sunxi 5.41 armhf Linux DTB, version 4.14.18-sunxi
hi linux-image-next-sunxi 5.41 armhf Linux kernel, version 4.14.18-sunxi
hi linux-stretch-root-next-nanopineo 5.41 armhf Armbian tweaks for stretch on nanopineo (next branch)
hi linux-u-boot-nanopineo-next 5.41 armhf Uboot loader 2017.11

On clone le dépôt:

git clone http://github.com/epsilonrt/armbian-nanopi-ethaddr-patch
cd armbian-nanopi-ethaddr-patch

Puis on patche le script u-boot:

cp /boot/boot.cmd .
patch -p1 < boot.cmd.patch
mkimage -C none -A arm -T script -d boot.cmd boot.scr
sudo mv /boot/boot.cmd /boot/boot.cmd.orig
sudo mv /boot/boot.scr /boot/boot.scr.orig
sudo cp boot.* /boot

Ensuite, on décompile le device tree, on le patche et on le recompile:

sudo dtc -I dtb -O dts -o sun8i-h3-nanopi-neo.dts /boot/dtb/sun8i-h3-nanopi-neo.dtb
sudo patch -p1 < sun8i-h3-nanopi-neo.dts.patch
sudo dtc -I dts -O dtb -o sun8i-h3-nanopi-neo.dtb sun8i-h3-nanopi-neo.dts
sudo mv /boot/dtb/sun8i-h3-nanopi-neo.dtb /boot/dtb/sun8i-h3-nanopi-neo.dtb.orig
sudo cp sun8i-h3-nanopi-neo.dtb /boot/dtb

Il ne faut pas tenir compte des warning affichés par dtc.

Voilà ! Il ne reste plus qu’à rebooter:

sudo reboot

Il faut juste espérer que les responsables du driver règle ce problème un jour…