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