libmodbuspp

Un wrapper C++ pour la librairie libmodbus

libmodbus est une librairie opensource pour envoyer/recevoir données selon le protocole MODBUS. Cette librairie est écrite en C et supporte Communications RTU(série) et TCP(Ethernet).

La librairie libmodbuspp fournit une surcouche C++ pour libmodbus, un wrapper n’ayant pas d’autre dépendance que libmodbus et libstdc++.

Cet article fait suite à celui présentant l’utilisation de MODBUS sur une carte Nano Pi.

Un bon exemple étant toujours meilleur qu’une longue explication, voilà un très exemple simple:


uint16_t values[2]; // array to store the values of the input registers
Master mb (Rtu, "COM1", "19200E1"); // new master on RTU
mb.open(); // open a connection
Slave & slv = mb.addSlave (33); // to the slave at address 33
slv.readInputRegisters (1, values, 2);
cout << values[0] << endl;
cout << values[1] << endl;

Cet exemple lit les registres d’entrée numéro 1 et 2 de l’esclave MODBUS (serveur) à l’adresse 33.

Guide de démarrage rapide

Installation

Le moyen le plus rapide et le plus sûr d’installer libmodbuspp sur Debian, Armbian, Raspbian… est d’utiliser le dépôt APT de piduino.org, voilà comment s’y prendre :

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 libmodbuspp-dev libmodbuspp-doc 

Ce dépôt fournit les packages libmodbus pour les architectures i386,amd64, armhf et arm64. Dans les commandes ci-dessus, le dépôt est une distribution Debian Stretch, mais vous pouvez également choisir Ubuntu Trusty, Xenial ou Bionic en remplaçant stretch par trusty, xenial ou bionic.
Il peut être nécessaire d’installer le paquet software-properties-common si la commande add-apt-repository n’est pas disponible.

Pour Raspbian, vous devez faire un peu différent:

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
echo 'deb http://raspbian.piduino.org stretch piduino' | sudo tee /etc/apt/sources.list.d/piduino.list
sudo apt update
sudo apt install libmodbuspp-dev libmodbuspp-doc

Le dépôt Raspbian fournit des packages Piduino pour l’architecture armhf uniquement pour Stretch.

Si vous voulez construire à partir de sources, vous pouvez suivre le Wiki.

Exemple

Voici un exemple complet qui peut être compilé sans erreur :

#include <iostream>
#include <string>
#include <modbuspp.h>

using namespace std;
using namespace Modbus;

int main (int argc, char **argv) {
  string port ("/dev/ttyUSB0");

  if (argc > 1) {

    port = argv[1]; // the serial port can be provided as a parameter on the command line.
  }

  Master mb (Rtu, port , "38400E1"); // new master on RTU
  // if you have to handle the DE signal of the line driver with RTS,
  // you should uncomment the lines below...
  // mb.rtu().setRts(RtsDown);
  // mb.rtu().setSerialMode(Rs485);

  Slave slv (mb.addSlave (33)); // SolarPi Pressure meter

  cout << "Reads input registers of slave[" << slv.slave() << "] on " <<
       mb.backend().connection() << " (" << mb.backend().settings() << ")" << endl;

  if (mb.open ()) { // open a connection
    // success, do what you want here
    uint16_t values[2];

    if (slv.readInputRegisters (1, values, 2) == 2) {

      cout << "R0=" << values[0] << endl;
      cout << "R1=" << values[1] << endl;
    }
    else {
      cerr << "Unable to read input registers ! "  << mb.lastError() << endl;
      exit (EXIT_FAILURE);
    }
    mb.close();
  }
  else {
    cerr << "Unable to open MODBUS connection to " << port << " : " << mb.lastError() << endl;
    exit (EXIT_FAILURE);
  }

  return 0;
}

Entrez le texte de ce programme avec votre éditeur de texte préféré et enregistrez le fichier dans main.cpp

Pour construire, vous devez taper la commande:

g++ -o read-input-registers main.cpp $(pkg-config --cflags --libs libmodbuspp)

Vous pouvez ensuite l’exécuter:

./read-input-registers 
R0=9964
R1=10029

Vous trouverez plusieurs exemples dans le dossier /usr/share/doc/modbuspp/examples

Avec Codelite ce sera beaucoup plus facile et amusant car l’installation du paquet libmodbuspp-dev ajoute un modèle de projet modbuspp à Codelite.

Débogage avec Codelite

Pour créer un nouveau programme modbuspp dans Codelite, il faut, une fois votre workspace créé, utiliser le menu Workspace/New Project et sélectionner le modèle Simple Executable (C++ MODBUSPP) :

Documentation

Le paquet libmodbuspp-doc fournit de la documentation.

Les classes fournies par la librairie sont documentées par les pages de man:

  • La page Modbus_Master pour la classe Modbus::Master
  • La page Modbus_Data pour la classe Modbus::Data
  • La page Modbus_RtuLayer pour la classe Modbus::RtuLayer
  • La page Modbus_TcpLayer pour la classe Modbus::TcpLayer
  • La page Modbus_Timeout pour la classe Modbus::Timeout

L’API complète est documentée dans le dossier /usr/share/doc/modbuspp/api-manual/index.html

À propos de Modbus

MODBUS est un protocole de messagerie de la couche application, fournissant Communication maître/esclave entre appareils connectés entre eux par des bus ou des réseaux. Sur le modèle OSI, MODBUS est positionné au niveau 7. MODBUS est destiné à être un protocole de requête/réponse et fournit les services spécifiés par les codes de fonction. Les codes de fonction de MODBUS sont des éléments requête/réponse de MODBUS ou PDU (Unité de données de protocole).

Piduino par l’exemple

Comme piduino utilise la même API que Arduino, il vous suffit de vous rendre sur la référence Arduino, ou sur n’importe quel site expliquant les fonctions du langage Arduino pour en avoir une documentation.

Premier exemple, Blink !

Le dossier exemples contient des exemples du monde Arduino qui peuvent être utilisés directement avec piduino. La seule chose à ajouter par rapport à l’exemple correspondant Arduino est la ligne :

#include <Piduino.h>

Voici le code source de l’exemple Blink qui fait clignoter une led :

#include <Piduino.h> // all the magic is here ;-)

const int ledPin = 0; // Header Pin 11: GPIO17 for RPi, GPIOA0 for NanoPi

void setup() {
  // initialize digital pin ledPin as an output.
  pinMode (ledPin, OUTPUT);
}

void loop () {
  // Press Ctrl+C to abort ...
  digitalWrite (ledPin, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay (1000);                 // wait for a second
  digitalWrite (ledPin, LOW);   // turn the LED off by making the voltage LOW
  delay (1000);                 // wait for a second
}

Évidement, vous devez connaître le numéro de broche où vous avez connecté la led ! Ce nombre dépend de votre modèle de carte Pi, pour le savoir rapidement, vous pouvez taper la commande pido readall 1, qui nous donne, par exemple, l’affichage suivant sur un Raspberry Pi B:

                                    P1 (#1)
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | V | Ph || Ph | V | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+
|     |     |     3.3V |      |   |  1 || 2  |   |      | 5V       |     |     |
|   2 |   8 |    GPIO2 |   IN | 1 |  3 || 4  |   |      | 5V       |     |     |
|   3 |   9 |    GPIO3 |   IN | 1 |  5 || 6  |   |      | GND      |     |     |
|   4 |   7 |    GPIO4 |   IN | 1 |  7 || 8  | 1 | ALT0 | TXD0     | 15  | 14  |
|     |     |      GND |      |   |  9 || 10 | 1 | ALT0 | RXD0     | 16  | 15  |
|  17 |   0 |   GPIO17 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO18   | 1   | 18  |
|  27 |   2 |   GPIO27 |   IN | 0 | 13 || 14 |   |      | GND      |     |     |
|  22 |   3 |   GPIO22 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO23   | 4   | 23  |
|     |     |     3.3V |      |   | 17 || 18 | 0 | IN   | GPIO24   | 5   | 24  |
|  10 |  12 |   GPIO10 |   IN | 0 | 19 || 20 |   |      | GND      |     |     |
|   9 |  13 |    GPIO9 |   IN | 0 | 21 || 22 | 0 | IN   | GPIO25   | 6   | 25  |
|  11 |  14 |   GPIO11 |   IN | 0 | 23 || 24 | 1 | IN   | GPIO8    | 10  | 8   |
|     |     |      GND |      |   | 25 || 26 | 1 | IN   | GPIO7    | 11  | 7   |
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | V | Ph || Ph | V | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+---+----++----+---+------+----------+-----+-----+

La colonne iNo correspond au numéro ‘Arduino’, le numéro 0 pin correspond donc à la broche 11 du connecteur GPIO (GPIO17).

Pour compiler le programme blink sur la ligne de commande, vous devez taper le commander:

$ g++ -o blink blink.cpp $(pkg-config --cflags --libs piduino)

La dernière partie de la commande utilise pkg-config pour ajouter les options de construction à g ++ afin de compiler le programme correctement.

Vous pouvez ensuite exécuter le programme:

 $ sudo ./blink

sudo est nécessaire pour accéder à la zone de mémoire du GPIO. Vous pouvez activer le bit setuid pour éviter sudo à l’avenir:

$ sudo chmod u+s blink
$ ./blink

Pour disposer d’un environnement de développement plus convivial, il est conseillé d’utiliser Codelite, l’installation de piduino ajoute un modèle de programme pour piduino. Pour créer un nouveau programme piduino dans Codelite, il faut, une fois votre workspace créé, utiliser le menu Workspace/New Project et sélectionner le modèle Simple Executable (C++ piduino) :

Modèle de projet Codelite pour piduino

Dans Codelite, on peut non seulement compiler, mais aussi éditer et surtout déboguer le programme :

Débogage avec Codelite

Deuxième exemple

le deuxième exemple rtc_bq32k, utilise la bibliothèque Wire pour lire l’heure dans un circuit BQ32000 RTC.

Cela permet de découvrir 2 différences importantes entre une carte Arduino et une carte Pi :

  1. Tout d’abord, sur une carte Pi, l’interface homme-machine (écran et clavier) est fait sur la ligne de commande (la console !). Sur Arduino, le port série est utilisé.
  2. Sur une carte Pi, un programme peut se terminer pour donner la main à l’utilisateur. Sur Arduino, le programme ne s’arrête jamais (en fait, sur un système Linux, le programme du noyau ne s’arrête jamais non plus …)

Pour résoudre le premier problème, piduino définit un objet Console dont le l’utilisation est identique à l’objet Serial (c’est une classe dérivée de Stream).

Afin de permettre la compilation sur les deux plates-formes sans modifier le code source, nous pouvons ajouter au début du programme un bloc qui teste si la plate-forme cible est un système Unix/Linux (piduino), si c’est le cas, on inclut le fichier Piduino.h, sinon on définit un alias de Console qui correspond à Serial, c’est-à-dire que l’interface homme-machine est sur le port série.

#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Wire.h>

void printBcdDigit (byte val, bool end = false) {
  val = (val / 16 * 10) + (val % 16); // BCD to DEC

  if (val < 10) {
    Console.write ('0'); // leading zero
  }
  if (end) {

    Console.println (val);
  }
  else {

    Console.print (val);
    Console.write (':');
  }
}

void setup() {

  Console.begin (115200);
  Wire.begin(); // Starting the i2c master
}

void loop() {

  Wire.beginTransmission (0x68); // start of the frame for the RTC at slave address 0x68
  Wire.write (0); // write the address of the register in the RTC, 0 first register
  Wire.endTransmission (false); // restart condition
  Wire.requestFrom (0x68, 3); // 3-byte read request

  if (Wire.available() == 3) { // if the 3 bytes have been read
    byte sec = Wire.read();
    byte min = Wire.read();
    byte hour = Wire.read() & 0x3F; // remove CENT_EN and CENT LSB bits

    // time display
    printBcdDigit (hour);
    printBcdDigit (min);
    printBcdDigit (sec, true);
  }
  exit (0); // exit the loop() function without ever coming back.
  // On Arduino, exit() performs an infinite loop as explained on
  // https://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html
  // on a Pi board, exit () stops the program by returning the supplied value.
}

Pour résoudre le second problème, il est possible d’utiliser sur les 2 les plates-formes, la fonction exit() (définie dans le bibliothèque standard). Cette fonction, compatible avec les deux plates-formes, permet d’arrêter l’exécution la fonction loop().

Sur un système Unix/Linux, cela arrête le programme et revient à la ligne de commande, sur Arduino, cela effectue une boucle infinie (après avoir appelé le destructeur d’objets C++).

Configuration de piduino

piduino détecte le modèle de carte sur lequel il s’exécute au moment du lancement du programme qui l’utilise, ainsi si vous installez le même paquet Armbian libpiduino, en architecture armhf par exemple, sur une carte Nano Pi Neo et sur Orange Pi Zero, piduino détectera automatiquement le bon modèle de carte !

Comment fait-il ?

Il utilise les fichiers présents sur la machine hôte pour le savoir. Il commence par scruter les fichiers /etc/friendlyelec-release puis /etc/armbian-release à la recherche d’un champ BOARD lui indiquant la signature de la carte (tag), si il ne trouve rien, il scrute le fichier /proc/cpuinfo à la recherche d’un numéro de révision correspondant à une carte Raspberry Pi de la base de données, si il ne le trouve pas, il ne peut le deviner ! vous aurez donc une erreur.

Il est possible de forcer le choix du modèle de carte Pi en utilisant le fichier de configuration /etc/piduino.conf.

Cela peut être nécessaire lorsque le programme ne peut pas détecter la configuration de la carte. Par exemple, dans le cas du NanoPi Neo Core/Core2, nous pouvons indiquer que la carte est sur son shield, dans ce cas, l’affichage du connecteur par la commande pido readall sera adaptée.

Comme expliqué précédemment, la détection de modèle de carte Pi utilise deux méthodes:
* La première méthode, qui s’applique aux cartes Raspberry Pi, lit le Fichier /proc/cpuinfo pour obtenir le modèle de microprocesseur dans le champ Hardware et la version du matériel dans le champ Revision. Ce numéro de révision est comparé avec le base de données pour en déduire le modèle RaspberryPi.
* La deuxième méthode, qui s’applique aux cartes utilisant ArmBian, vient de la lecture /etc/armbian-release ou /etc/friendlyelec-release pour obtenir le modèle de carte dans BOARD. piduino compare cette signature avec la base de données pour en déduire le modèle RaspberryPi.

Dans le fichier de configuration /etc/piduino.conf, nous trouverons ces deux possibilités, qu’il faut renseigner (l’une ou l’autre, mais jamais les deux !).

Par exemple, si nous voulons indiquer que notre NanoPi Neo Core2 est installé sur son shield, nous mettrons la valeur du champ tag à nanopineocore2shield:

# PiDuino configuration file
connection_info="sqlite3:db=/usr/local/share/piduino/piduino.db"

# Allows you to force the board tag (Armbian)
# !! Be careful, this is at your own risk !!
# !! Forcing an incorrect value may destroy GPIO pins !!
tag="nanopineocore2shield"

# Allows forcing the revision of the board (Raspbian)
# !! Be careful, this is at your own risk !!
# !! Forcing an incorrect value may destroy GPIO pins !!
#revision=0xa02082

On peut constater que le fichier de configuration contient également l’adresse de la base de données à utiliser. La base de données est par défaut un fichier SQLite3 local, mais cette base de données peut être installée sur un serveur MySQL par exemple (pour le format de la ligne connection_info Voir la documentation de CPPDB).

Interface Homme-Machine pour boîtier sur rail DIN

Open-source-hardware-logo.png

Dans cet article nous allons nous intéresser au projet Hmi4DinBox. La partie matérielle de Hmi4DinBox a été conçue par 2 étudiants Lionel et Justine du BTS Systèmes Numériques, Option “Électronique et communication” du Lycée Rouvière de Toulon.

Hmi4DinBox est une interface homme-machine conçue pour être implémentée à l’avant d’un boîtier de rail DIN.

Voilà ce que ça donne une fois implantée dans le boîtier DIN :

et voilà le cahier des charges exprimé en SysML qui résume les spécifications de cette interface :

Requirement Diagram Hmi4DinBox

Caractéristiques

Ses caractéristiques principales sont les suivantes :

  • LCD 2×16 caractères avec rétro-éclairage (basé sur ST7032),
  • un bouton de navigation avec (4 directions + 1 bouton poussoir),
  • 5 leds,
  • piloté par un simple bus I2C et un signal binaire HIRQ qui indique qu’un ou plusieurs actions ont été effectuées sur le bouton de navigation (appui et relâchement de touche dans les 5 directions stockées dans une mémoire tampon),
  • alimenté par 5V ou 3.3V permettant de l’utiliser avec une carte Arduino ou Pi,
  • Interface de programmation USB compatible avec Arduino IDE,
  • Interface JTAG pour la programmation du chargeur de démarrage et le débogage du firmware.

Publié sous licence open-source hardware, il est composé de :

Comment connecter cette IHM à votre carte ?

L’IHM est connectée à votre carte par le connecteur J1 MOLEX KK254 à 5 broches situé à l’arrière du circuit imprimé.

Le brochage du connecteur J1 est le suivant:

# Nom Description
1 VCC Alimentation 5V ou 3,3V, protégée contre les surtensions et les inversions de polarité
2 SCL Horloge I2C, pas de résistance au pull-up sur cette ligne! mettez-en une sur la carte principale !
3 SDA Données I2C, pas de résistance au pull-up sur cette ligne! mettez-en une sur la carte principale !
4 HIRQ Indique qu’une ou plusieurs actions ont été effectuées sur le bouton de navigation
5 GND Masse

HIRQ est à l’état haut tant qu’il y a des actions non lues dans le tampon (capacité de 32 actions). Il passe à l’état bas quand toutes les actions ont été lues avec la méthode key ().

Comment installer la bibliothèque ?

Cette bibliothèque permet le contrôle côté client de Hmi4DinBox connecté par un bus I2C. Elle est destinée à être utilisée dans un environnement Arduino (également compatible avec le piduino sur des cartes Pi telles que Raspberry Pi ou NanoPi).

Pour installer la bibliothèque pour Arduino IDE, téléchargez simplement son fichier zip et l’intégrer dans votre croquis en fonction de explications sur le site Arduino.

Comment utiliser l’IHM dans votre programme?

Au début du fichier Hmi4DinBox.h, nous voyons la définition de constantes décrivant la solution matérielle:

#define LED1 0
#define LED2 1
#define LED3 2
#define LED4 3
#define LED5 4

Nous avons 5 leds, la numérotation commence à 0.

#define LED_RED LED1
#define LED_GREEN1 LED2
#define LED_GREEN2 LED3
#define LED_YELLOW1 LED4
#define LED_YELLOW2 LED5

Nous définissons des constantes plus explicites correspondant aux couleurs des leds : la led N°1 est rouge, les N°2 et 3 vertes…

Ensuite, on trouve les constantes pour la fonction bouton de navigation :

#define KUP 1
#define KDOWN 2
#define KLEFT 3
#define KRIGHT 4
#define KCENTER 5

Nous avons 5 actions (ou touches, keys) correspondant aux 4 directions du bouton de navigation et le bouton central.

Pour utiliser cette IHM dans un programme, vous devez déclarer une variable globale de type Hmi4DinBox:

Hmi4DinBox hmi;

Si on utilise le pavé de navigation, il est nécessaire lors de cette déclaration de spécifier le numéro de la broche Arduino utilisée par l’IHM pour indiquer que des actions sur le bouton sont dans le tampon interne (il faudra donc les lire …).

const int hirqPin = 7;
Hmi4DinBox hmi (hirqPin);

Ensuite, nous pouvons accéder :

  • à l’afficheur LCD grâce à hmi.lcd,
  • aux leds grâce à hmi.led,
  • au bouton de navigation grâce à hmi.keyb,
  • au rétroéclairage avec hmi.backlight.

Utilisation de l’écran LCD

La classe WireLcd est dérivée de la classe Arduino Print. Vous pouvez donc utiliser les fonctions “print(),println(),write()`:

int i = 5;
hmi.lcd.print("Helloworld ! ");
hmi.lcd.println(i++);
hmi.lcd.write('C');

Voici le croquis HelloWorld.ino :

#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Hmi4DinBox.h>

Hmi4DinBox hmi;

void setup() {

  Console.begin (115200);
  if (!hmi.begin (24, false)) {

    Console.println("hmi.begin() failed !");
    exit (1); // HMI failed to start !
  }
}

void loop() {
  static int counter = 0;

  // Write a piece of text on the first line...
  hmi.lcd.setCursor (0, 0); //LINE 1, ADDRESS 0
  hmi.lcd.print ("Hello World");

  // Write the counter on the second line...
  hmi.lcd.setCursor (1, 0);
  hmi.lcd.print (counter / 10, DEC);
  hmi.lcd.write ('.');
  hmi.lcd.print (counter % 10, DEC);
  hmi.lcd.write (' ');
  counter++;
  delay (500);
}

Comme on peut le voir ci-dessus, on fera,

Pour effacer l’écran:

hmi.lcd.clear();

Pour aller au début de la ligne 2:

hmi.lcd.setCursor (1, 0);

Pour revenir en haut à droite:

hmi.lcd.home();

Pour éteindre l’écran:

hmi.lcd.noDisplay();

Pour allumer l’écran:

hmi.lcd.display();

Pour activer le curseur:

hmi.lcd.cursor();

Pour désactiver le curseur:

hmi.lcd.noCursor();

Pour activer le curseur clignotant:

hmi.lcd.blink();

Pour désactiver le clignotement du curseur:

hmi.lcd.noBlink();

Le contraste de l’écran LCD peut être modifié, la valeur peut varier de 0 à 63:

hmi.lcd.setcontrast (24);

Le contraste peut également être lu et ajusté par rapport à la valeur actuelle:

byte c = hmi.lcd.getcontrast();
hmi.lcd.adjcontrast (-1); // decrement the contrast value by 1

Utilisation des leds

#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Hmi4DinBox.h>

Hmi4DinBox hmi;
void printLed (int led);

void setup() {

  Console.begin (115200);
  if (!hmi.begin (24, false)) {

    Console.println("hmi.begin() failed !");
    exit (1); // HMI failed to start !
  }
  hmi.lcd.cursor();
  hmi.lcd.blink();
  hmi.lcd.clear();
  hmi.lcd.print ("12345");
}

void loop() {

  // turn on all leds
  hmi.led.writeAll();
  // Pause
  delay (2000);

  // turn off all leds
  hmi.led.writeAll (0);
  // Pause
  delay (2000);

  // turn on the LEDs one by one.
  hmi.led.set (LED1);
  delay (500);
  hmi.led.set (LED2);
  delay (500);
  hmi.led.set (LED3);
  delay (500);
  hmi.led.set (LED4);
  delay (500);
  hmi.led.set (LED5);
  delay (500);
  // Pause
  delay (2000);

  // turn off the LEDs one by one with a for loop
  for (int led = LED1; led <= LED5; led++) {
    hmi.led.clear (led);
    delay (500);
  }
  // Pause
  delay (2000);

  // turn on the LEDs one by with a for loop, using toggle and get value.
  for (int led = LED1; led <= LED5; led++) {
    hmi.led.toggle (led);
    printLed (led);
    delay (500);
  }
  // Pause
  delay (2000);

  // turn off the LEDs one by with a for loop, using toggle() and get()
  for (int led = LED1; led <= LED5; led++) {
    hmi.led.toggle (led);
    printLed (led);
    delay (500);
  }
  // Pause
  delay (2000);
}

void printLed (int led) {
  hmi.lcd.setCursor (1, led);
  if (hmi.led.get (led)) {
    hmi.lcd.write ('O');
  }
  else {

    hmi.lcd.write ('_');
  }
}

Comme on peut le voir dans le croquis LedDemo.ino, ci-dessus :

hmi.led.set (LED1);

permet d’allumer la LED1,

hmi.led.clear (LED1);

permet d’éteindre la LED1,

hmi.led.toggle (LED1);

permet de basculer l’état de LED1,

hmi.led.get (LED1);

permet de lire l’état de la LED1,

La fonction hmi.led.writeAll() vous permet de modifier toutes les leds en même temps. Par défaut, un appel à cette fonction sans paramètre active toutes les leds. Si on lui passe un paramètre, cela correspond à l’état des leds. Le bit 0 de ce paramètre est utilisé pour contrôler LED1, bit 1 pour LED2 …

Utilisation du bouton de navigation

#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Hmi4DinBox.h>

const int hirqPin = 7;
Hmi4DinBox hmi (hirqPin);

void setup() {

  Console.begin (115200);
  if (!hmi.begin (24, false)) {

    Console.println("hmi.begin() failed !");
    exit (1); // HMI failed to start !
  }

  hmi.lcd.clear();
  hmi.lcd.cursor();
  hmi.lcd.blink();
}

void loop() {
  static int column = 0;
  static int row = 0;

  if (hmi.keyb.available()) { // check if keys are available
    byte key = hmi.keyb.key(); // get the next key

    if (hmi.keyb.released()) { // this key was released ?

      hmi.lcd.write ('R'); // yes, print R
      hmi.led.clear(LED_GREEN1);
    }
    else {

      hmi.lcd.write ('P'); // no, print P
      hmi.led.set(LED_GREEN1);
    }

    hmi.lcd.print (key); // print the key

    // Move the cursor when you reach the end of the line.
    column += 2;
    if (column >= 16) { // end of line ?
      // yes
      if (++row > 1) { // end of screen ?
        // wait for reading last key and clear the LCD
        delay(500);
        row = 0;
        hmi.lcd.clear();
      }
      column = 0;
      hmi.lcd.setCursor (row, column);
    }

  }
}

Comme on peut le voir dans le croquis KeyboardDemo.ino, ci-dessus :

hmi.keyb.available ()

permet de vérifier si des actions sur le bouton sont disponibles pour la lecture,

key = hmi.keyb.key ();

permet de lire la prochaine action mémorisée,

hmi.keyb.released ()

vous permet de savoir si cette action est un relâchement (et donc pas un appui),

hmi.keyb.pressed ()

vous permet de savoir si cette action est un appui.

Utilisation du rétro-éclairage

#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Hmi4DinBox.h>

const int hirqPin = 7;
Hmi4DinBox hmi (hirqPin);

byte bl;
void printBacklight ();

void setup() {

  Console.begin (115200);
  if (!hmi.begin (24, false)) {

    Console.println("hmi.begin() failed !");
    exit (1); // HMI failed to start !
  }

  // read value stored in EEPROM.
  bl = hmi.backlight.read();

  // print this value
  hmi.lcd.clear();
  hmi.lcd.print ("Backlight");
  printBacklight();
}

void loop() {

  if (hmi.keyb.available()) { // check if keys are available

    if (hmi.keyb.pressed()) {
      byte key = hmi.keyb.key(); // get the next key

      if ( (key == KUP) || (key == KDOWN)) { // if key UP or DOWN

        if (key == KUP) {

          bl += 8; // UP for increase
        }
        else {

          bl -= 8;  // DOWN for decrease
        }

        // change value and print
        hmi.backlight.write (bl);
        printBacklight();
        hmi.led.toggle (LED_GREEN1);
      }
    }
  }
}

void printBacklight () {

  hmi.lcd.setCursor (0, 10);
  hmi.lcd.print (bl);
  hmi.lcd.print ("  ");
}

Comme on peut le voir dans le croquis BacklightDemo.ino ci-dessus,

hmi.backlight.write (bl);

permet de modifier la valeur du rétro-éclairage (entre 0 et 255), comme cette valeur est stockée dans la mémoire EEPROM de l’IHM, il est possible de lire la valeur actuelle grâce à:

bl = hmi.backlight.read ();

Programmation Orientée Objets en embarqué

Nous commençons une série d’articles qui présentent la mise en œuvre d’une approche par objets en programmation embarquée.

Le but de ce premier article est de présenter succinctement les concepts indispensables à la compréhension des programmes que nous allons réaliser. Une fois ces notions présentées, nous effectuerons une présentation du cahier des charges et une approche par objets, puis nous “réaliserons” chaque objet pour remplir le cahier des charges.

Le sujet choisi est la réalisation d’un chenillard à led avec une carte Nano Pi ou Raspberry Pi en langage C++. Comme nous utiliserons la librairie piduino dont l’API est compatible avec Arduino, nous pourrons aussi effectuer ses activités avec une carte Arduino (avec quelques adaptations qui seront signalées).

On parle souvent de POO pour Programmation Orientée Objet. Je préfère le concept d’approche par objets car cette problématique ne concerne pas que la programmation, elle concerne toute la démarche de projet. D’ailleurs ont pourra noter que le langage SysML est une extension pluri-technologique du langage UML initialement utilisé en informatique. L’informatique a donc permis de faire évoluer d’ingénierie d’une façon générale.

Cette série d’articles suppose que vous ayez quelques connaissances en C. Les particularités de C++ pourront être approfondies par la lecture du cours C++ fait par Henri Garreta. Pour ceux qui ont un peu plus de temps et qui souhaitent une approche plus “tuto”, je vous conseille le cours C++ sur OpenClassroom.

Alors que ces cours se concentrent sur l’apprentissage du langage, nous, nous allons nous concentrer sur la méthode d’approche par objets. Comme nous souhaitons que la méthode soit translatable sur n’importe quelle plateforme ou langage, nous introduirons l’utilisation du langage de modélisation graphique UML.

Cette série d’articles s’adresse à des débutants qui ont déjà réalisé des programmes dans un contexte de “Faire soi-même” (DIY) sur une carte Arduino, Raspberry Pi, Nano Pi Pi … et qui souhaitent améliorer leurs méthodes de programmation. Ces articles pourront aussi être utiles à des étudiants en sciences du numérique souhaitant mettre en œuvre rapidement cette démarche sur un cas pratique.

 Qu’est-ce que l’approche par objets ?

L’approche par objets vise à décrire un problème en identifiant les objets qui le compose.

Les objets seront décrits en terme de fonctionnalités et de propriétés. L’approche par objets ne doit pas être confondue avec l’utilisation d’un langage objets (comme C++, Java …) car on peut très bien utiliser un langage objets sans jamais faire d’approche par objets. Beaucoup de débutants en C++ pensent qu’il suffit d’utiliser ce langage pour faire de l’objet, ce n’est pas le cas.

Bien évidement, l’utilisation d’un langage objets est préférable dans une approche par objets ! Ils ont juste été inventé pour ça… On peut noter qu’il existe des projets utilisant l’approche par objets qui n’utilisent pas de langage objet, le plus connus est la boite à outils GTK qui est développée en langage C. GTK est utilisée par exemple dans le projet GNOME.

Le but de cette méthode est d’organiser la programmation afin de la rendre plus efficace, plus solide et plus évolutive.

Le but est de décrire un programme avec des concepts qui se rapprochent du concret. Dans notre quotidien nous manipulons des objets “réels” en permanence, nous utiliserons souvent ce parallèle avec la réalité pour expliquer des concepts “abstraits” de la POO.

Concepts de base de l’approche par objets

 L’encapsulation

L’approche par objets repose sur le principe d’encapsulation, on parle aussi de concept de “boîtes noires”, c’est à dire, qu’on va décrire l’interface d’un objet qui sera accessible du public, la programmation en elle-même (on parle de l’implémentation) sera cachée du public (elle est privée, private en anglais). Un langage objets disposera donc de ce concept sous forme de mots clés, en C++ c’est public et private.

L’héritage

Le principe est que, sauf correction de bug ou d’erreur de conception, on ne modifie pas l’implémentation d’un objet. Il faut donc disposer d’un mécanisme qui va permettre d’étendre les fonctionnalités d’un objet. Prenons un exemple concret, un vélo électrique est un vélo, on s’attachera donc à modéliser un vélo, puis on créera un nouveau modèle de vélo, un vélo électrique qui est dérivé de l’objet initial. Ce mécanisme s’appelle l’héritage. Si un programme est prévu pour utiliser un vélo, il pourra quand même utiliser un vélo électrique dans ses fonctions de base (celles définies pour l’objet vélo).

 La classe

Un autre concept essentiel à comprendre en programmation orientée objet est le concept de classe. Une classe permet de modéliser un objet en décrivant ses fonctionnalités, on parle de méthodes, et ses propriétés qui sont des valeurs que l’on peut lire et/ou modifier. Les méthodes et les propriétés sont rattachés à une classe, on dit qu’ils sont les membres de la classe. On peut aussi utiliser le terme fonction membre pour désigner une méthode.

En C++, il y a 2 mots clés pour définir une classe : class et struct. Ceux qui connaissent le langage C auront notés que struct est utilisé dans ce langage pour décrire les structures (de données uniquement en C). Cela montre que le concept de classe est une extension aux fonctions de la notion de structure. Tous les membres d’une classe définie avec struct sont par défaut publics, alors que ceux d’une classe définie avec class sont par défaut privés. Un bon principe pour respecter l’encapsulation est donc de se limiter à n’utiliser que le mot clé class. C’est ce que nous ferons dans ces articles.

La classe est une description statique d’un objet. Un objet est la “concrétisation” dynamique d’une classe, on dit qu’un objet est une instance de classe, dans les faits un objet est l’association d’une classe et d’un bloc de variables stockées en mémoire vive (RAM donc…).

 Constructeur et destructeur de classe

Comme un objet est l’association d’une classe et d’un blocs de variables, il faut bien initialiser ces variables afin de mettre l’objet dans un état connu. C’est le rôle du constructeur de classe. Il s’agit d’une fonction membre particulière, chargée d’initialiser ses variables.

Parfois, le constructeur va “consommer” des ressources de l’ordinateur (mémoire dynamique, entrées-sorties …), il faut donc prévoir un mécanisme de libération de ses ressources, c’est le rôle du destructeur de classe.

En C++, les constructeurs sont des fonctions membres qui portent le même nom que la classe et ne renvoient aucun paramètre (même pas void), le destructeur est une fonction membre qui portent le même nom que la classe mais précédé du caractère ~ et ne renvoient aucun paramètre.

Le polymorphisme

Implicitement, je viens de dire dans la phrase précédente qu’il peut exister plusieurs constructeurs de classe, c’est vrai, mais cela ne concerne pas que les constructeurs. Toutes les fonctions membres, sauf le destructeur, peuvent avoir plusieurs “versions” ou “formes”, on parle de polymorphisme ou surcharge. on pourra par exemple définir une fonction d’écriture write(...) qui prend un paramètre de type entier et une autre qui porte le même nom et qui prend un paramètre flottant. Comme le compilateur choisira automatiquement la “version” à utiliser, il faut que deux fonctions “surchargées” prennent des paramètres de nature différentes.

Voilà nous sommes arrivés à la fin de ce premier article, dans le prochain nous passerons à la pratique.

Utiliser une librairie Arduino sur carte Pi

Cet article est la suite de l’article Utiliser Hmi4DinBox sur carte Pi. Il faudra donc avoir effectuer les manipulations prévues dans cet article : installation ou mise à jour de piduino, connexion et test de l’interface Hmi4DinBox sur le Nano Pi.

Comme nous l’avons dans un article précédent la librairie piduino permet de compiler un croquis Arduino sur une carte Pi (Nano, Raspberry, Orange …). La librairie piduino peut compiler tous les croquis faisant appel aux fonctions de calcul de base (UC), aux broches d’entrées-sorties numériques (GPIO) ainsi qu’aux liaisons série, I2C et SPI. Il faut préciser que, même si cela peut paraître évident, piduino ne peut pas émuler accéder à des ressources non disponibles sur la carte Pi, comme l’ADC par exemple, mais il dispose aussi de la fonction analogWrite() qui permet de générer un signal PWM sur n’importe quelle broche GPIO…

A certaines conditions, il est possible d’envisager l’utilisation de librairie Arduino sur carte Pi. Posons le problème sous forme de questions réponses :

  • Est-il possible d’utiliser une librairie conçue pour Arduino sur une carte Pi grâce à piduino ?
    En fait, cela dépend de la librairie, si celle-ci ne fait pas d’accès direct aux ressources matérielles du microcontrôleur, cela devrait fonctionner.
  • Comment savoir si une librairie fait un appel direct aux ressources matérielles du microcontrôleur ? Il faut regarder dans les fichiers du code source de la librairie (.cpp), si un ou plusieurs fichiers contient des instructions #include suivies d’un nom de fichier d’entête commençant par <avr/ cela ne sera pas possible (en tout cas pas sans modification du code source de la librairie).

Le but de cet article est de montrer comment utiliser une librairie Arduino, qui répond à la condition précédente, sur une carte Pi. Pour l’utilisation d’une librairie “normale” prévue pour être utiliser sur carte Pi, on se reportera à l’article Utiliser facilement les librairies avec CodeLite.

Nous allons utiliser le Codelite pour créer un espace de travail (un workspace) et un projet piduino, ensuite nous ajouterons les fichiers source de la librairie Hmi4DinBox avant de compiler et exécuter le programme. Cela suppose que vous savez utiliser Codelite dans ses fonctions de base, comme expliqué dans l’article Utilisation de CodeLite.

Création de l’espace de travail

Nous allons créer un espace de travail pour notre tutoriel dans ~/src que nous appellerons hmi-tuto.

Connectez-vous en ssh au Nano Pi à partir d’un PC sous GNU/Linux, on n’oubliera pas l’option -X car nous avons besoin de XWindow 😉

ssh pi@n12.btssn.lan -X

Créer un dossier ~/src s’il n’existe pas :

pi@nanopineocore2:~$ mkdir -p src

Lancer Codelite en tâche de fond (&):

pi@nanopineocore2:~$ codelite &
[1] 9882

Utiliser le menu Workspace > New Workspace... pour créer notre espace de travail.

Sélectionner le dossier /home/pi/src comme chemin et le nom choisi hmi-tuto.

L’espace de travail est créé, nous pouvons le voir dans le panneau latéral de Codelite.

Clonage de la librairie Hmi4DinBox

Il nous faut maintenant le code source de la librairie Hmi4DinBox que nous allons récupérer avec git. Nous mettrons le dossier de la librairie dans /home/pi/src/hmi-tuto :

pi@nanopineocore2:~$ cd src/hmi-tuto/
pi@nanopineocore2:~/src/hmi-tuto$ git clone https://github.com/epsilonrt/Hmi4DinBox.git
Clonage dans 'Hmi4DinBox'...
remote: Enumerating objects: 374, done.
remote: Counting objects: 100% (374/374), done.
remote: Compressing objects: 100% (208/208), done.
remote: Total 374 (delta 178), reused 340 (delta 144), pack-reused 0
Réception d'objets: 100% (374/374), 29.81 MiB | 1.04 MiB/s, fait.
Résolution des deltas: 100% (178/178), fait.

Nous avons maintenant un dossier Hmi4DinBox dans /home/pi/src/hmi-tuto. Le dossier Hmi4DinBox/src contient le code source de la librairie :

pi@nanopineocore2:~/src/hmi-tuto$ ls Hmi4DinBox/src
Hmi4DinBox.cpp  Hmi4DinBox.h  version.h  WireHmi.cpp  WireHmi.h  WireLcd.cpp  WireLcd.h

Création du projet

Nous allons maintenant créé le projet HelloWorld en partant du croquis du même nom fourni dans le dossier Hmi4DinBox/examples/HelloWorld. Ce programme affiche un message sur le LCD de Hmi4DinBox ainsi qu’un comptage.

Faites un clic-droit sur le workspace dans le panneau latéral de Codelite et sélectionner le menu New > New Project....

Dérouler l’item Console, puis choisir le modèle Simple executable (C++ piduino) et cliquer sur Next.

Taper le nom HelloWorld puis cliquer sur Next et Finish :

Le nouveau projet apparaît dans le workspace dans le panneau latéral, vous pouvez voir sa structure, pour l’instant composé d’un “dossier” src contenant le fichier main.cpp . Le fichier main.cpp contient un code source d’exemple (blink) dont nous n’avons pas besoin, supprimer-le en cliquant-droit dessus dans le panneau latéral, puis Remove. Confirmer la suppression du fichier du projet et du disque.

Ouvrir le fichier croquis hmi-tuto/Hmi4DinBox/examples/HelloWorld/HelloWorld.ino à l’aide du menu File > Open > Open File....

Enregistrer ce fichier dans hmi-tuto/HelloWorld/HelloWorld.cpp à l’aide du menu File > Save As.... Attention à bien modifier l’extension en .cpp !! c’est important.

Il ne reste plus qu’à ajouter notre fichier HelloWorld.cpp dans le “dossier” src de notre projet en faisant un clic-droit sur src dans le panneau latéral, puis Add an Existing File.

Vous devriez avoir une fenêtre Codelite qui ressemble à ça :

Avant de passer à la suite, il faut modifier la ligne if (!hmi.begin (24, false)) { dans la fonction setup(). En effet le paramètre 24 correspond au contraste et le false indique que le booster du LCD ne doit pas être activé. Ces paramètres sont corrects quand on alimente l’interface en 5V mais lorsqu’on l’alimente en 3,3V, il faut augmenter le contraste et surtout activer le booster. On modifiera donc cette ligne en conséquence, le programme modifié ressemble à cela :

// Hmi4DinBox Class: LCD Helloworld Example
// by epsilonrt <https://github.com/epsilonrt>

// How to control the LCD ?

// Created 18 may 2018

// This example code is in the public domain.
#ifdef __unix__
#include <Piduino.h>  // All the magic is here ;-)
#else
// Defines the serial port as the console on the Arduino platform
#define Console Serial
#endif

#include <Hmi4DinBox.h>

Hmi4DinBox hmi;

void setup() {

  pinMode ()

  Console.begin (115200);
  if (!hmi.begin (34, true)) {

    Console.println("hmi.begin() failed !");
    exit (1); // HMI failed to start !
  }
}

void loop() {
  static int counter = 0;

  // Write a piece of text on the first line...
  hmi.lcd.setCursor (0, 0); //LINE 1, ADDRESS 0
  hmi.lcd.print ("Hello World");

  // Write the counter on the second line...
  hmi.lcd.setCursor (1, 0);
  hmi.lcd.print (counter / 10, DEC);
  hmi.lcd.write ('.');
  hmi.lcd.print (counter % 10, DEC);
  hmi.lcd.write (' ');
  counter++;
  delay (500);
}

Ajout de la librairie Hmi4DinBox dans le projet

Si nous essayons de compiler notre programme grâce au menu Build > Build Project, nous avons une erreur de compilation :

/home/pi/src/hmi-tuto/HelloWorld/HelloWorld.cpp:16:24: fatal error: Hmi4DinBox.h: No such file or directory

Nous devons paramétrer notre projet pour qu’il trouve le fichier Hmi4DinBox.h.

Faites un clic-droit sur le projet HelloWorld dans le panneau latéral, puis cliquer sur le menu Settings. Dans la fenêtre qui s’ouvre choisir Global Settings, tout en bas, puis cliquer sur les trois petits points à droite de la ligne Additional Include Paths.

Ajouter le dossier ../Hmi4DinBox/src sans supprimer le point de la première ligne, puis valider.

Une nouvelle tentative de compilation nous montre que l’erreur concernant le fichier Hmi4DinBox.h mais plusieurs nouvelle erreurs apparaissent, donc la première est :

/home/pi/src/hmi-tuto/HelloWorld/HelloWorld.cpp:23: undefined reference to `Hmi4DinBox::begin(int, bool)'

Ce n’est pas une erreur de compilation mais une erreur d’édition de liens qui indique que le code exécutable de la fonction Hmi4DinBox::begin(int, bool) n’a pas été trouvé. C’est normal car le code source de cette fonction se trouve dans le fichier Hmi4DinBox.cpp qui n’est pas dans notre projet ! Il faut l’ajouter…

Nous allons commencer par créer un nouveau “dossier virtuel” dans notre projet afin d’y ranger les fichiers source de Hmi4DinBox. Faites un clic-droit sur le projet HelloWorld puis New Virtual Folder. On le nommera très logiquement Hmi4DinBox. Attention à bien décocher la case Create the folder on the file system as well.

Il faut maintenant ajouter les fichiers source dans la dossier virtuel. Faites un clic-droit sur le dossier virtuel Hmi4DinBox, puis Add an Existing File. Allez dans le dossier hmi-tuto/Hmi4DinBox/src, faites un CTRL+A pour sélectionner tous les fichiers, valider par Open.

Vous devriez avoir une fenêtre Codelite qui ressemble à ça :

Compilation du projet

Une nouvelle tentative de compilation grâce au menu Build > Build Project, nous informe :

====0 errors, 0 warnings====

que nous avons compiler notre “croquis” Arduino HelloWorld avec succès ! 😀

Exécution du projet

Nous pouvons lancer l’exécution de notre programme grâce au menu Debugger > Start/Continue Debugger. Si nous le faisons une fenêtre de terminal s’ouvre mais rien ne s’affiche sur le LCD !

Que se passe-t-il ?

En fait, pour accéder au bus I2C sur un système Armbian (accès au module i2cdev), l’utilisateur doit faire partie du groupe i2c. Si ce n’est pas le cas, l’accès est refusé, bizarrement, le système n’affiche pas d’erreur ! (c’est sûrement un bug…).

Nous allons ajouter l’utilisateur pi au groupe i2c après avoir quitter Codelite, puis nous quittons notre session :

pi@nanopineocore2:~$ sudo adduser pi i2c
pi@nanopineocore2:~$ exit

Après s’être reconnecté, nous relançons Codelite :

ssh pi@n12.btssn.lan -X
pi@nanopineocore2:~$ codelite &

On peut alors lancer lancer l’exécution de notre programme grâce au menu Debugger > Start/Continue Debugger, voilà le résultat :

Pour aller plus loin

Pour aller plus loin, vous pouvez évidement répéter cette procédure avec les différents croquis disponibles dans hmi-tuto/Hmi4DinBox/examples en commençant par KeyboardDemo.

ATTENTION, pour tous les programmes utilisant le bouton de navigation et donc la broche HIRQ, il faudra lancer Codelite en sudo conformément à l’explication de l’article Utiliser CodeLite en sudo.

Utiliser Hmi4DinBox sur carte Pi

Dans cet article nous allons utiliser l’interface homme-machine Hmi4DinBox sur une carte Pi.

Après une installation ou une mise à jour de piduino, nous connecterons l’interface Hmi4DinBox à un carte Pi avant d’en effectuer le test à partir de la ligne de commande avec les outils du paquet i2c-tools.

Comme nous l’avons vu dans un article précédent cette interface est prévue pour être connectée à une carte Arduino ou Pi par un bus I2C. Elle est fournie avec une librairie Arduino que nous utiliserons dans un deuxième article pour réaliser des programmes accédant à l’interface Hmi4DinBox sur notre carte Nano Pi.

La carte Nano Pi utilisée est une carte Nano Pi Neo Core implantée sur un Mini Shield, commercialisée en France par GoTronic, mais on pourra utiliser n’importe quelle carte Pi compatible avec piduino.

hmi4dinbox-nanopi

Installer ou mettre à jour piduino

Si ce n’est pas encore fait, installer les paquets libpiduino-dev et piduino-utils comme expliqué dans l’article sur piduino.

Si vous avez déjà installer piduino, effectuez une mise à jour des paquets :

sudo apt update
sudo apt upgrade

Afin que pido affiche correctement le connecteur GPIO, il est nécessaire de modifier le fichier /etc/piduino.conf afin d’indiquer que la carte est implantée sur le Mini Shield:

sudo nano /etc/piduino.conf

Il faut retirer le # devant la ligne tag et mettre nanopineocoreshield entre les guillemets. Le fichier doit ressembler à ceci:

# PiDuino configuration file
connection_info="sqlite3:db=/usr/share/piduino/piduino.db"

# Allows you to force the board tag (Armbian)
# !! Be careful, this is at your own risk !!
# !! Forcing an incorrect value may destroy GPIO pins !!
tag="nanopineocoreshield"

# Allows forcing the revision of the board (Raspbian)
# !! Be careful, this is at your own risk !!
# !! Forcing an incorrect value may destroy GPIO pins !!
#revision=0xa02082

Relier l’interface Hmi4DinBox au Nano Pi

Fil de câblageAprès avoir arrêté la carte Nano Pi, nous allons relier l’interface Hmi4DinBox au connecteur GPIO grâce à des fils équipés de petits connecteurs (femelle-femelle) comme ci-contre.

Le brochage du connecteur J1 de l’interface Hmi4dinBox est le suivant:

# Nom Description
1 VCC Alimentation 5V ou 3,3V, protégée contre les surtensions et les inversions de polarité
2 SCL Horloge I2C, pas de résistance au pull-up sur cette ligne! mettez-en une sur la carte principale !
3 SDA Données I2C, pas de résistance au pull-up sur cette ligne! mettez-en une sur la carte principale !
4 HIRQ Indique qu’une ou plusieurs actions ont été effectuées sur le bouton de navigation
5 GND Masse

Nous pouvons voir le brochage du connecteur GPIO CON1 du Nano Pi grâce à pido :

pido readall 1
                                          CON1 (#1)
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
|     |     |     3.3V |      |      |   |  1 || 2  |   |      |      | 5V       |     |     |
|  12 |   8 |  I2C0SDA | ALT2 |  OFF |   |  3 || 4  |   |      |      | 5V       |     |     |
|  11 |   9 |  I2C0SCK | ALT2 |  OFF |   |  5 || 6  |   |      |      | GND      |     |     |
|  91 |   7 |  GPIOG11 |  OFF |  OFF |   |  7 || 8  |   | OFF  | ALT2 | UART1TX  | 15  | 86  |
|     |     |      GND |      |      |   |  9 || 10 |   | OFF  | ALT2 | UART1RX  | 16  | 87  |
|   0 |   0 |   GPIOA0 |  OFF |  OFF |   | 11 || 12 |   | OFF  | OFF  | GPIOA6   | 1   | 6   |
|   2 |   2 |   GPIOA2 |  OFF |  OFF |   | 13 || 14 |   |      |      | GND      |     |     |
|   3 |   3 |   GPIOA3 |  OFF |  OFF |   | 15 || 16 |   | OFF  | OFF  | GPIOG8   | 4   | 88  |
|     |     |     3.3V |      |      |   | 17 || 18 |   | OFF  | OFF  | GPIOG9   | 5   | 89  |
|  15 |  28 | SPI1MOSI | ALT2 |  OFF |   | 19 || 20 |   |      |      | GND      |     |     |
|  16 |  24 | SPI1MISO | ALT2 |  OFF |   | 21 || 22 |   | OFF  | OFF  | GPIOA1   | 6   | 1   |
|  14 |  29 |  SPI1CLK | ALT2 |  OFF |   | 23 || 24 |   | OFF  | ALT2 | SPI1CS   | 27  | 13  |
|     |     |      GND |      |      |   | 25 || 26 |   | OFF  | OFF  | GPIOA17  | 11  | 17  |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+

Voilà les connexions que nous devons réaliser :

J1NomCON1NomiNo
1VCC13.3V
2SCL5I2C0SCK9
3SDA3I2C0SDA8
4HIRQ15GPIOA33
5GND6GND

Après avoir vérifié, puis revérifié encore les 5 fils, démarrer le Nano Pi.

Tester l’interface Hmi4DinBox

Avant d’utiliser l’interface dans un programme, il est préférable d’en effectuer un test directement à partir de la ligne de commande.

Nous allons utiliser pour ce faire les programmes du paquet i2c-tools (à installer si nécessaire).

sudo apt install i2c-tools

Avant de “parler” à notre interface, il ne faut oublier que les lignes SDA et SCL sur un Nano Pi ne disposent pas de résistances de pull-up (ce n’est pas le cas sur Raspberry Pi). Il faut donc, soit ajouter une résistance de 1,8k reliée au 3,3V sur chaque signal, soit activer les résistances de pull-up sur les lignes iNo 8 et 9. C’est cette dernière solution que nous allons utiliser car elle est plus simple (mais la première solution avec des résistances externes devra être retenue dans un cadre industriel).

pido nous permet d’activer les résistances de pull-up :

pido pull 8 up
pido pull 9 up

Si vous ne savez pas pourquoi il faut des résistances de pull-up sur les lignes I2C, il est tant de se renseigner sur Wikipedia

Pour comprendre ce que nous allons faire, il faut regarder le diagramme d’exigences de l’interface Hmi4DinBox qui a été présenté dans un article précédent.

L’exigence Affichage LED (id=1.10.2) nous dit la chose suivante :

  • L’interface dispose de 5 leds T1-3mm.
  • L’accès à la fonction par le module hôte se fera par bus I2C à l’adresse 0111111.
  • Le registre 0 à cette adresse permettra de lire et modifier l’état des leds (1 bit par led). Le bit 0 correspond à la led rouge, le bit 1 à la verte1, le bit 2 à la verte2, le 3 à jaune1 et le 4 à jaune2).

L’exigence Affichage LCD (id=1.10.1) nous informe que l’afficheur de modèle ST7032i se trouve à l’adresse 0111110.

Utilisons i2cdetect sur le bus I2C 0 pour savoir si les 2 adresses, celle du LCD et l’autre utilisée pour les leds (et les autres fonctions) sont détectées :

sudo i2cdetect  -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- 3e 3f 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --                         

Bingo ! nous détectons 2 circuits sur le bus I2c :

  • un circuit à l’adresse hexadécimale 0x3e, 00111110 en binaire, qui correspond au LCD.
  • un circuit à l’adresse hexadécimale 0x3f, 00111111 en binaire, qui correspond aux leds (et les autres fonctions).

Nous allons écrire 0x01 dans le registre 0 à l’adresse 0x3f grâce à i2cset :

sudo i2cset -y 0 0x3f 0 0x01

Normalement cela allume la LED1, c’est à dire la rouge, pour allumer la LED2 on écrira 0x02, la LED3 0x04, la LED4 0x08, la LED5 0x10. On allumer plusieurs leds en effectuant une somme des valeurs, par exemple, 0x12 allume la LED2 et la LED5, 0x1F toutes les leds…

Pour éteindre les leds :

sudo i2cset -y 0 0x3f 0 0x00

Pour lire l’états des leds :

sudo i2cget -y 0 0x3f 0
0x12

L’exigence id=1.10.1.1 nous permet de savoir que le registre 1 à l’adresse 0x3f correspond au rétroéclairage, pour l’allumer à mi-puissance soit 128 :

sudo i2cset -y 0 0x3f 1 128

L’exigence id=1.10.3 nous permet de savoir que les actions effectuées par l’utilisateur sont accessibles par le registre 2 à l’adresse 0x3f et que le signal HIRQ, broche iNo 3 dans notre cas, est à l’état haut lorsque des actions sont disponibles. Commençons par passer la broche en entrée et lisons sa valeur avec pido :

pido mode 3 in
pido read 3
0

Cela veut dire qu’aucune action n’est mémorisé, effectuons un appui sur le bouton, puis relisons HIRQ :

pido read 3
1

Nous pouvons effectuer maintenant la lecture des actions avec i2cget en lisant le registre 2 à l’adresse 0x3f :

sudo i2cget -y 0 0x3f 2
0x05

D’après l’exigence id=1.10.3, cela correspond à un appui (bit 7 à 0) au centre.

Une nouvelle lecture de HIRQ, nous apprend qu’il y a encore des actions disponibles :

pido read 3
1

Qu’on peut lire :

sudo i2cget -y 0 0x3f 2
0x85

Cela correspond à un relâchement (bit 7 à 1 0x80) du centre (0x05). Une nouvelle lecture de HIRQ, nous apprend qu’il n’y a plus d’action disponible.

pido read 3
0

Le test en ligne de commande du LCD à l’adresse 0x3e est un peu plus complexe car il nécessite de se pencher sur le datasheet du ST7032i… Nous verrons sa programmation dans le prochain article… 🙂

piduino

Arduino sur cartes Pi, le meilleur des deux mondes !

piduino est une bibliothèque C++ pour cartes Pi qui permet d’utiliser les entrées-sorties comme GPIO, I2C, SPI, UART… avec une API aussi proche que possible du langage Arduino.
La description des cartes Pi utilise un modèle “Objet” stocké dans une base de données qui permet d’ajouter facilement de nouveaux modèles de cartes.

Actuellement, les modèles SoC pris en charge sont AllWinner H-Series et Broadcom BCM2708 à 2710 qui lui permet d’être utilisé sur Raspberry Pi et la plupart des Nano Pi, Orange Pi et Banana Pi.

Pour en savoir plus sur piduino, vous pouvez suivre la Wiki, mais si vous êtes pressé, passons à la version de démarrage rapide …

Guide de démarrage rapide

Le moyen le plus rapide et le plus sûr d’installer piduino sur Armbian est d’utiliser le dépôt APT de piduino.org, vous devriez 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 libpiduino-dev piduino-utils

Ce dépôt fournit les packages piduino pour les architectures armhf etarm64 (et la plupart des librairies et programmes disponibles sur le github de epsilonrt).
Dans les commandes ci-dessus, le dépôt est une distribution Debian Stretch, mais vous pouvez également choisir Ubuntu Xenial ou Bionic en remplaçant stretch par xenial ou bionic. Il peut être nécessaire d’installer le logiciel software-properties-common paquet pour add-apt-repository.

Pour Raspbian, vous devez faire un peu différent:

wget -O- http://www.piduino.org/piduino-key.asc | sudo apt-key add -
echo 'deb http://raspbian.piduino.org stretch piduino' | sudo tee /etc/apt/sources.list.d/piduino.list
sudo apt update
sudo apt installer libpiduino-dev piduino-utils

Le dépôt Raspbian fournit des packages piduino Stretch pour l’architecture armhf uniquement .

Si vous voulez construire piduino à partir de sources, vous pouvez suivre la Wiki.

Utilitaires

Une fois installé, vous devez exécuter ce qui suit sur la ligne de commande:

$ pinfo
Nom: NanoPi Core2 Mini Shield
Famille: NanoPi
ID de base de données: 40
Fabricant: Friendly ARM
Tag Conseil: nanopineocore2shield
SoC: H5 (Allwinner)
Mémoire: 1024 Mo
Identifiant GPIO: 9
Bus I2C: / dev / i2c-0
Bus SPI: /dev/spidev1.0
Ports série: / dev / ttyS1

Comme nous pouvons l’imaginer, dans l’exemple ci-dessus, nous sommes sur une NanoPi Neo Core2 connecté à un Mini Shield.

Pour lire l’état des broches du connecteur 1, exécutez la commande suivante sur la ligne de commande:

$ pido readall 1
                                          CON1 (#1)
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
|     |     |     3.3V |      |      |   |  1 || 2  |   |      |      | 5V       |     |     |
|  12 |   8 |  I2C0SDA | ALT2 |  OFF |   |  3 || 4  |   |      |      | 5V       |     |     |
|  11 |   9 |  I2C0SCK | ALT2 |  OFF |   |  5 || 6  |   |      |      | GND      |     |     |
|  91 |   7 |  GPIOG11 |  OFF |  OFF |   |  7 || 8  |   | OFF  | ALT2 | UART1TX  | 15  | 86  |
|     |     |      GND |      |      |   |  9 || 10 |   | OFF  | ALT2 | UART1RX  | 16  | 87  |
|   0 |   0 |   GPIOA0 |  OFF |  OFF |   | 11 || 12 |   | OFF  | OFF  | GPIOA6   | 1   | 6   |
|   2 |   2 |   GPIOA2 |  OFF |  OFF |   | 13 || 14 |   |      |      | GND      |     |     |
|   3 |   3 |   GPIOA3 |  OFF |  OFF |   | 15 || 16 |   | OFF  | ALT2 | UART1RTS | 4   | 88  |
|     |     |     3.3V |      |      |   | 17 || 18 |   | OFF  | ALT2 | UART1CTS | 5   | 89  |
|  15 |  28 | SPI1MOSI | ALT2 |  OFF |   | 19 || 20 |   |      |      | GND      |     |     |
|  16 |  24 | SPI1MISO | ALT2 |  OFF |   | 21 || 22 |   | OFF  | OFF  | GPIOA1   | 6   | 1   |
|  14 |  29 |  SPI1CLK | ALT2 |  OFF |   | 23 || 24 |   | OFF  | ALT2 | SPI1CS   | 27  | 13  |
|     |     |      GND |      |      |   | 25 || 26 |   | OFF  | OFF  | GPIOA17  | 11  | 17  |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+

pido etpinfo disposent de pages de man…, donc vous pouvez en savoir plus sur ces commandes grâce à :

$ man pido

Exemple Blink

Vous êtes prêt à faire de l’Arduino sur carte Pi ? Ok, allons-y !

Nous allons faire clignoter une led connectée avec une résistance à une broche GPIO. Voilà le code source de l’exemple, qui, à l’exception de la première ligne est identique à celui du tutoriel Arduino :

#include <Piduino.h> // all the magic is here ;-)

const int ledPin = 0; // Header Pin 11: GPIO17 for RPi, GPIOA0 for NanoPi

void setup() {
  // initialize digital pin ledPin as an output.
  pinMode (ledPin, OUTPUT);
}

void loop () {
  // Press Ctrl+C to abort ...
  digitalWrite (ledPin, HIGH);  // turn the LED on (HIGH is the voltage level)
  delay (1000);                 // wait for a second
  digitalWrite (ledPin, LOW);   // turn the LED off by making the voltage LOW
  delay (1000);                 // wait for a second
}

Évidement, vous devez connaître le numéro de broche où vous avez connecté à la led ! Pour ce faire :

$ pido readall 1
                                          CON1 (#1)
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
|     |     |     3.3V |      |      |   |  1 || 2  |   |      |      | 5V       |     |     |
|  12 |   8 |  I2C0SDA | ALT2 |  OFF |   |  3 || 4  |   |      |      | 5V       |     |     |
|  11 |   9 |  I2C0SCK | ALT2 |  OFF |   |  5 || 6  |   |      |      | GND      |     |     |
|  91 |   7 |  GPIOG11 |  OFF |  OFF |   |  7 || 8  |   | OFF  | ALT2 | UART1TX  | 15  | 86  |
|     |     |      GND |      |      |   |  9 || 10 |   | OFF  | ALT2 | UART1RX  | 16  | 87  |
|   0 |   0 |   GPIOA0 |  OFF |  OFF |   | 11 || 12 |   | OFF  | OFF  | GPIOA6   | 1   | 6   |
|   2 |   2 |   GPIOA2 |  OFF |  OFF |   | 13 || 14 |   |      |      | GND      |     |     |
|   3 |   3 |   GPIOA3 |  OFF |  OFF |   | 15 || 16 |   | OFF  | OFF  | GPIOG8   | 4   | 88  |
|     |     |     3.3V |      |      |   | 17 || 18 |   | OFF  | OFF  | GPIOG9   | 5   | 89  |
|  22 |  12 |   GPIOC0 |  OFF |  OFF |   | 19 || 20 |   |      |      | GND      |     |     |
|  23 |  13 |   GPIOC1 |  OFF |  OFF |   | 21 || 22 |   | OFF  | OFF  | GPIOA1   | 6   | 1   |
|  24 |  14 |   GPIOC2 |  OFF |  OFF |   | 23 || 24 |   | UP   | OFF  | GPIOC3   | 10  | 25  |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+
| sOc | iNo |   Name   | Mode | Pull | V | Ph || Ph | V | Pull | Mode |   Name   | iNo | sOc |
+-----+-----+----------+------+------+---+----++----+---+------+------+----------+-----+-----+

La colonne iNo de ce tableau correspond au numéro ‘Arduino’, le numéro 0 pin correspond donc à la broche 11 du connecteur GPIO (GPIOA0 pour un Nano Pi).

Une fois le code source du programme, enregistré dans le fichier blink.cpp (pas d’extension .ino sous Pi !), vous pouvez construire, vous devez taper la commande:

$ g++ -o blink blink.cpp $(pkg-config --cflags --libs piduino)

Vous pouvez ensuite exécuter le programme:

$ sudo ./blink

sudo est nécessaire pour accéder à la zone mémoire du GPIO. Vous pouvez activer le bit setuid pour éviter sudo à l’avenir:

$ sudo chmod u+s blink
$ ./blink

Avec Codelite c’est plus facile et amusant, non ?

Débogage avec Codelite

Vous devriez lire le wiki sur les exemples pour en savoir plus …

Genèse du projet

piduino est né d’une question d’un de mes étudiants qui m’a demandé pourquoi la programmation des entrées-sorties sur NanoPi n’était pas aussi simple que sur Arduino.

piduino vise donc à répondre à ce besoin:

Une interface de programmation d’application (API) sur les cartes Pi aussi proche que possible de celle d’Arduino.

Cette API doit permettre l’utilisation de GPIO, port série, bus I2C et SPI… sur Raspberry Pi, Nano Pi, Orange Pi, Banana Pi, Beagle Board… comme sur une carte Arduino.

Que propose piduino ?

  • Une interface de programmation API identique à Arduino, à l’exeception du #include <Piduino.h> est ajouté au début du programme. Cela n’interdit pas d’offrir des extensions de l’API mais à condition de rester indépendant de la plate-forme et de ne pas rendre le code incompatible avec Arduino. Il est logique de penser que les utilisateurs qui souhaitent rester dans le monde Arduino utilisent le C++, piduino est destiné à ce cas d’utilisation. Néanmoins, certaines fonctions peuvent être utilisées en langage C (pinMode(), digitalWrite(), …).
  • La description des cartes Pi basée sur un modèle “Objet” stocké dans une base de données (SQLite par défaut), permettant à un utilisateur qui n’est pas forcément un hacker d’ajouter une nouvelle variante de carte Pi SANS programmation. Une variante désigne une carte équipée du même modèle de SoC avec une partie matérielle différente (connecteurs…).
  • Une conception objet en C++ avec une séparation claire de la partie spécifique à la plate-forme. La prise en charge de nouveaux SoC se résume à ajouter une partie “HAL” dans le répertoire src/arch. Les HAL actuellement disponibles sont les Soc AllWinner de la série H (cartes Nano Pi, Banana Pi, Orange Pi …) et les Broadcom de la famille BCM2835 à 37 (cartes Raspberry Pi).
  • Un utilitaire en ligne de commande de manipulation des signaux GPIO : pido
  • Un utilitaire en ligne de commande qui récupère les informations de la carte et le la base de données : pinfo
  • Un programme de gestion de la base de données de cartes Pi: pidbm (en développement…).

Remarque

Il existe déjà quelques projets qui permettent la programmation des entrées-sorties sur cartes embarquées, mais pour un seul modèle de carte Pi.

Le plus connu est probablement wiringPi. wiringPi est une solution prévue pour Raspberry Pi et même s’il y a versions dérivées pour d’autres cartes Pi, ces versions sont des fork “boiteux” de la version originale, d’un point de vue du génie logiciel.

Les raisons qui m’ont amené à ne pas choisir wiringPi sont les suivantes :

  • Même s’il y a une similitude avec la programmation Arduino, il y a des différences qui augmentent avec le temps (et qui perturbe les débutants).
  • wiringPi a été conçu en C pur, ce qui freine l’évolutivité et n’est pas très compatible avec Arduino (le langage Arduino est du C++ !). Il est impossible par exemple de compiler un programme Arduino faisant appel à la liaison série (HardwareSerial) ou aux bus I2c (Wire) et SPI…
  • wiringPi a été conçu pour le Raspberry Pi et son adaptation à d’autres cartes Pi est de plus en plus ingérable, au fur et à mesure de l’arrivée de nouveaux modèles de cartes Pi. Il suffit d’aller sur le site de ArmBian pour voir la multitude de modèles Pi !

Installer Armbian dans la mémoire eMMC d’une carte Nano Pi

Certaines cartes Nano Pi disposent de mémoire eMMC. C’est le cas des Nano Pi Neo Core et Neo Core2, mais aussi de la Neo Plus2.
Cette mémoire change radicalement l’utilisation possible de ces cartes Nano Pi, en autorisant leur utilisation dans un contexte industriel. En effet, la mémoire MMC (cartes SD ou microSD) dont sont équipées la plupart des cartes concurrentes, comme les cartes Raspberry Pi, a la fâcheuse tendance à rendre l’âme sans prévenir au bout de quelques mois d’utilisation. Il suffit de lancer une recherche sur un moteur de recherche avec les mots clés { sd card corrupted raspberry pi } pour comprendre le problème.

Les cartes Nano Pi équipées de mémoire de mémoire eMMC sont livrées avec le système d’exploitation “maison” qui est une version modifiée de ArmBian basé sur Ubuntu Xenial. Bien que ce choix soit celui de la simplicité, il amène à se pencher, dans un contexte industriel, sur la pérennité, l’évolutivité et la sécurité de cette solution.

La distribution ArmBian prenant en charge les cartes Nano Pi, il apparaît bien plus intéressant de se tourner vers cette solution. Cette solution permettra, par exemple, de recompiler “rapidement” les paquets du noyau (BSP) afin d’y intégrer des drivers absents de l’image par défaut (IIO par exemple). ArmBian fournit le script compile.sh qui permet de faire cela très simplement.

Dans cet article, nous allons voir comment installer ArmBian dans la mémoire eMMC d’une carte Nano Pi Core LTS. Cette carte sera implantée dans son mini-shield afin de pouvoir effectuer le prototypage.

Nous utiliserons comme environnement de travail un PC sous GNU Linux, car c’est beaucoup plus simple de travailler sur une carte Nano Pi à partir d’un PC sous GNU Linux. Pour ceux qui en doute, il suffit de se reporter à mon article sur Codelite.

Téléchargement de l’image ArmBian

L’image que nous avons choisi est la version Stretch basée sur Debian. Elle peut être téléchargée sur la page de téléchargement de ArmBian consacrée à la carte NanoPi Neo (et Core).

Une fois le fichier archive 7z téléchargé, puis décompressé on oubliera pas de vérifier la signature SHA256 du fichier afin de vérifier son intégrité…

$ 7za e Armbian_5.69_Nanopineo_Debian_stretch_next_4.19.13.7z 

7-Zip (A) [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=fr_FR.UTF-8,Utf16=on,HugeFiles=on,8 CPUs)

Processing archive: Armbian_5.69_Nanopineo_Debian_stretch_next_4.19.13.7z

Extracting  Armbian_5.69_Nanopineo_Debian_stretch_next_4.19.13.img
Extracting  Armbian_5.69_Nanopineo_Debian_stretch_next_4.19.13.img.asc
Extracting  armbian.txt
Extracting  sha256sum.sha

Everything is Ok

Files: 4
Size:       1103121482
Compressed: 266049577
$ sha256sum -c sha256sum.sha 
Armbian_5.69_Nanopineo_Debian_stretch_next_4.19.13.img: Réussi

Écriture de l’image sur carte microSD

Pour écrire l’image dans la mémoire eMMC, il faudra passer par une carte microSD. Une fois le système transféré en eMMC, cette carte SD pourra être retirée. La solution la plus simpliste pour écrire une image sur une carte SD est dd, mais il existe une solution beaucoup plus confortable et “sécurisée”, elle s’appelle Etcher.

Balena Etcher en action

Je vous conseille donc d’utiliser Etcher pour écrire votre image sur la carte microSD. Pour l’installer proprement, il est préférable d’ajouter le dépôt de paquets :

echo "deb http://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 379CE192D401AB61
sudo apt-get update
sudo apt-get install balena-etcher-electron

Démarrage de la carte Nano Pi sur la carte microSD

Une fois l’image écrite sur la carte microSD, il suffit de l’insérer dans la carte Nano Pi et de l’alimenter.

La première connexion se fera sur la liaison série de débogage (UART0). On utilisera pour ce faire, un adaptateur série-USB connecté au connecteur DBG_UART du mini-shield et un logiciel d’émulation de terminal comme gtkTerm ou minicom.

Lors de la première connexion, le mot de passe root est ‘1234’. Il devra être modifié pour un plus sûr ;-| et vous aurez la possibilité de créer un utilisateur. Je vous conseille de créer un utilisateur pi dans un premier temps.

Transfert du système en mémoire eMMC

Pour transférer le système de la microSD vers la mémoire eMMC, il suffit d’utiliser la commande :

sudo nand-sata-install 

On choisira ensuite le menu system on eMMc puis le type ext4.

Après quelques minutes de patience… On arrêtera proprement le système. Après coupure de l’alimentation, on retirera la carte microSD avant de redémarrer sur la mémoire eMMC… 🙂

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>.