Deux livres Elektor sur le langage C et l’embarqué

Dans les publications d’Elektor, je me suis laissé tenter par la proposition de deux ouvrages:

  • C programming with Arduino, de  Warwick A. Smith, 343 pages
  • Embedded Linux Control Center, de Hans Henrik Skovgaard, 450 pages

Les deux sont dans la catégorie « learn » (il y a « design » et « share » ). Et vous l’aurez compris, ils sont en anglais. Petite revue et opinion.

1 – C programming with Arduino

Avec Arduino, vraiment?

C‘est la question qu’on peut se poser avec le premier livre; mais le préambule du titre est « AVR Microcontrollers and ATMEL Studio for… »; et c’est bien le cas. Juste pour voir si je vais apprendre des ficelles, des trucs et des astuces pour programmer en C des Arduino, je l’ouvre au milieu, et je tombe sur (p. 174) les spécification du format pour la fonction printf(), et la largeur des champs comme printf(« %5d\n », 123); // padded with 0

Et a peine plus loin, les boucles imbriquées. En fait, non, c’est un livre pour :

  1. Apprendre le C
  2.  programmer des CPU AVR

L’auteur décrit bien qu’il utilise l’Atmel AVR Dragon ou l’AVRISP mkII, soit une sonde qui permet la cross-compilation et le chargement du programme sur la cible par un câble ICSP (voir: https://fr.wikipedia.org/wiki/Joint_Test_Action_Group).

L’environnement de développement n’est donc pas l’IDE Arduino classique (que l’on peut télécharger ici: https://www.arduino.cc/en/Main/Software ), mais l’environnement Atmel Studio. Donc bien plus général. Le début du livre montre bien comment connecter la cible et mettre en place cette méthode de développement. Les points forts sont que l’on a accès à une bonne connaissance des registres et de l’utilisation native de ceux-ci. Par contre, on peut se demander si c’est un moyen rapide et efficace d’apprendre le C?

Pour ma part, je préfère que mes apprentis se fassent les dents sur un IDE de PC. Une fois les bases du langage aquises plus rapidement et plus facilement, on passe à une cible plus… exotique comme un Arduino. Avec ses spécificités et ses limites. Et les difficultés propres à la cross-compilation, et la non-possibilité de debbuguer apparaissent et viennent charger le bateau de la connaissance.

Apprendre le C efficacement

En effet, a quoi bon apprendre au stade de débutant qu’un cross-compilateur va râler à l’étape de l’édition de liens (linker) si une fonction de la bibliothèque est mal orthographiée? Les mêmes expériences seront plus efficacement  traitées sur un IDE natif, prévu pour Windows 32 ou 64 par exemple, même s’il cache une (grande) partie du déroulement de la création de l’exécutable. le même étudiant va remarquer rapidement que printf() n’est pas vraiment disponible pour de l’embarqué et que l’interface est du texte en mode série.

Bref, on peut garder le livre pour apprendre le C, et obtenir des infos détaillées sur les registres des CPU ATMEGA 328 et 2560, ainsi que leurs connexions sur les ports I/O du print Arduino. Et bien sûr, comment programmer timers, AD et watchdog!

2 – Embedded Linux Control Center

Dont le titre complet est « Design our own Embedded Linux Control Centre on PC and RaspBery Pi ». Ce livre touche à de nombreux programmes et concepts, pourtant il reste accessible à un (relativement) débutant grâce aux explications claires dans un anglais simple – aussi certainement parce que l’auteur est Danois. Au cours des explications, il y a de fréquents renvois par des URL sur des sites spécifiques qui permettent d’approfondir un sujet, ou de télécharger des utilitaires et des compléments.

Le livre s’adresse à celui qui aimerait faire de la « home automation » (de la maison intelligente?)

Allez, une jolie image pour illustrer cet anglicisme:

piqué à Protex.me

Exhaustivité

L’auteur est exhaustif dans son développement et la mise en oeuvre, sans tartiner avec des banalités, ni se répéter. Par exemple, pour la sélection de l’OS à monter sur le RaspBerry, les avantages et inconvénients pour le but proposé sont discutés. Toutefois, il faut ensuite se rappeler au cours de la lecture que ELCC signifie Embedded Linux Controle Centre – c’est ce programme et sa mise en œuvre qui est l’objet du livre. Une table des abréviations facilite la recherche d’acronymes.

Où j’ai plus de peine à suivre l’auteur c’est lorsqu’il recommande de lire tout le livre avant d’attaquer un projet… Alors que je ne résiste pas à sauter directement au sujet qui m’intéresse, qui à revenir en arrière pour plus en détail ou un élément qui m’aurait échappé?

Vivre avec la modernité

L’auteur montre a plusieurs reprise la difficulté de vivre dans le soft avec des changements et des mises à jour incessantes… Par exemple pour lire une sonde de température USB, il faut installer une librairie LIBUSB … version 1.12 et pas plus récente. Comme dit l’auteur, d’habitude je recommande la dernière version, mais celle-ci n’est pas compatible avec la librairie LIBHID! (lib pour les périphériques tels claviers, souris, manettes).

WEB Config et connectivité d’accès

L’auteur précise comment monter un service XAMPP et le sécuriser de manière à ce qu’il fonctionne encore tout en rendant le système fermé pour un accès indésirable. Les autres moyens de connexion, tels que SSH (secure shell), VNC (Virtula Network Computing), le X-server Xming et même le remote desktop xrdp sur RaspBerry sont exposés de manière claire.

Et oui, via l’application de connexion de bureau à distance MSTSC de Windows, on peut se logguer sur le RaspBerry. Attention, le clavier reste QWERTY. Vous voyez ci-dessous les écrans de VNC et MSTSC sur le même RaspBerry.

VNC-MSTSC

Old fashioned et … Arduino

Par certains aspects, des détails sont quelque peu de style ancien. L’auteur a depuis longtemps travaillé sur son projet, et certains renseignements le montrent. Par exemple, sur un (vieux) PC, comment réaliser une entrée-sortie sur un port parallèle.. Heureusement, on trouve toujours dans le commerce de tels adaptateurs; mais il y a belle lurette que les imprimantes sont connectées en USB ou sur le LAN.

Cependant, l’auteur précise que si l’on ne possède pas de port parallèle, on peut utiliser… un Arduino. Et là, les indications pour trouver comment connecter le hardware : LED, résistance; puis programmer l’Arduino, connecter l’USB à son fameux ELCC – tout y est! Il est également précisé que de couper et replanter l’USB provoque le reset de l’Arduino et rend les I/O instables un instant; ainsi que le moyen d’y palier. Et de se méfier de la limitation de courant si le device est alimenté par l’USB.

L’amour des choses anciennes fait un peu souci quand l’auteur présente son Linux chouchou: le DamnSmallLinux, qui n’a pas été mis à jour depuis 2012. Bon, il fait 50 Mo, mais quand on sait que la sécurité dépend de la fraicheur de l’OS…

Ou sont les pins du RaspBerry? – WiringPi

Là aussi, fidèle à son habitude, l’auteur fait une photo et met à disposition un tableau pour se repérer sur le connecteur, que ce soit un Raspberry rev 1 ou 2, modèle B ou B+. Il met en garde à propos de la tension admissible en entrée… Oui, le RaspBerry est en 3,3 V, et non en 5V.

Les commandes de ELCC se font par l’utilisation de WiringPi, une bibliothèque générale pour les I/O d’un RaspBerry, écrite et maintenue par Henderson Gordon. C’est un code sur GIT, facile d’accès et d’utilisation. C’est une excellente chose que d’utiliser cette référence qui a une large audience plutôt que de ré-écrire une lib GPIO.

En bref, c’est un excellent livre qui peut rendre de grands services à ceux qui se lanceraient dans l’automatisation domestique.

Yves Masur (4/2016)

 

Sources de temps et cycle pour Arduino (ou autre)

Dans cet article, on va examiner les moyens d’obtenir une base de temps sur la plateforme Arduino (Diduino, Seeeduino Stalker et Arduino Yun). Bien sûr, c’est transposable sur d’autres systèmes, qui tous ont besoin de références de temps. Que ce soit la date/heure exacte pour horodater un événement, ou attendre une certaine durée, qu’elle soit courte ou longue. Parfois, un timer local doit être synchronisé sur une horloge de précision : il y a plusieurs techniques pour ce faire.

clock_cff

Pour illustrer la complexité du sujet, il faut savoir que même notre planète ralentit et demande d’ajouter une seconde intercalaire. Sauter dans le temps peut être mortel pour la cohérence de données injectées dans une base de données ! Pour y palier, Google a mis au point une technique pour la gérer (http://www.clubic.com/internet/actualite-748601-google-rajoute-seconde-intercalaire-eviter-plantage.html )

Un bon exemple de la complexité du maintien d’une référence de temps cohérente se trouve dans les contrôleurs de trafic. Ces machines doivent d’une part donner des durées précises aux enclenchements des sources lumineuses (rouge – jaune – vert), souvent basées sur une résolution de 20 ou 100 millisecondes ; d’autre part maintenir un journal d’événements et commuter de plan de feux. Les plans de feux d’un cycle de – par exemple – 90 secondes doivent être synchronisés entre carrefours d’un même axe à la seconde. La référence utilisée est le Tx. Pour le calculer, on part du nombre de secondes écoulées depuis une origine comme le 1er janvier de l’année en cours, dont on prend le modulo (dans ce cas : 90). Le programme doit donc pouvoir compter sur des sources de temps fiables, précises. Elles sont aussi cascadées, c’est-à-dire que si l’une ne répond pas, on se rabat sur la suivante. On a par exemple :

  • Le temps réseau LAN NTP
  • Un signal GPS
  • L’horloge radio DCF
  • La centrale de trafic (qui donne le temps par télégramme)
  • Le secteur 50 Hz (variations en cours de journée rattrapées)
  • L’horloge interne (mais susceptible de dériver)
  • Un timer du CPU (peu précis)

Programmer un rythme : le tick

Une approche simple, qui consiste à écrire des routines dont l’exécution est rapide et de rythmer le cycle par une tempo ajustée. Ceci ne fonctionne que très approximativement. C’est pourtant ce qui est montré dans la plupart des exemples de programmation, comme faire clignoter une LED (https://www.arduino.cc/en/Tutorial/Blink ) à 0,5 Hz avec deux délais de 1000 ms entre les états allumé et éteint. Pour faire fonctionner un système en temps réel, il faut un « tick » d’horloge qui lance un appel régulier à des routines, qui dureront plus ou moins longtemps ; mais moins que la période de récurrence.

Une approche précise consiste à faire tourner un timer hardware, et d’attendre qu’il ait atteint ou dépassé une valeur pré calculée pour appeler les routines à rythmer. A ce moment, on peut soit agir par interruption, soit par polling. L’utilisation de l’interruption, si elle semble séduisante de prime abord, est plus complexe à mettre en œuvre et présente un danger : la réentrance. Soit l’apparition d’une nouvelle interruption, alors que les fonctions appelées n’ont pas terminé leur tâche. Si elle est masquée (l’interruption attend d’être servie), un tick sera raté sans qu’on s’en rend compte ; si elle ne l’est pas, le programme plante.

Voici un exemple de cycle de 100 millisecondes, basé sur la fonction native de Arduino, millis(). Elle donne le nombre de millisecondes écoulées depuis l’enclenchement. Ce compteur de 32 bits déborde (revient à 0) au bout de 2^32 / 1000 / 60 / 60 / 24 = 49.71 jours.

/* Main loop.
Run at 100 ms, commute the LED13 and call pulse_01Sec()
call reader() continously
*/
void loop()
{
  unsigned long currentMillis = millis(); // read time passed
  if (currentMillis - previousMillis > cycleTime) //Q: cycle reached?
  {
    previousMillis = currentMillis; //A: yes, set next point
    pulse_01Sec();
    etat_LED13 = !etat_LED13;     // pulse LED13 at 0.1 sec
    digitalWrite(LED13, etat_LED13);
  }
  reader();
}//loop

Désolé pour les commentaires. Selon mon inspiration, ils sont en anglais ou en français.

La variable locale currentMillis contient la valeur actuelle du compteur. Sil elle a dépassé la borne définie par constante cycleTime (ici 100 ms), on mémorise sa valeur dans la variable globale ou statique previousMillis. On évite ainsi les petites variations entre appels de fonctions qui conduiraient inévitablement à des imprécisions : il n’y a pas de flottement.

Quelle est la précision de cette technique ?

Au point de vue du rythme, c’est imparable, il n’y a pas d’écart. Si le temps pris par les fonctions appelées dépasse parfois 100 ms, les appels seront consécutifs jusqu’à ce que la synchronisation soit retrouvée. En observant le rythme de la LED, on peut ainsi se rendre compte qu’il y a un problème.

Mais sur la durée, quelle est la précision absolue de cette technique ? Afin de m’en rendre compte, j’ai ajouté à l’Arduino une horloge RTC, un programme qui permet sa mise à l’heure et de l’afficher. De plus, je l’ai doté d’une fonction qui affiche l’heure sur la ligne série théoriquement toute les minutes, soit tous les 600 appels par la fonction pulse_01Sec().

void dispTimeMinutes()
{
  static int td;
  td++;
  if (td == 600)
  {
    td = 0;
    dispTime();
  }
}

Résultat : anormalement faux !

Le board est un Diduino, la RTC une Grove RTC (v. ref en fin d’article ) dotée d’une puce horloge DS1307. Un enregistrement de 10 minutes montre que la précision n’est pas au rendez-vous.

2015/12/31 THU 15:37:16
2015/12/31 THU 15:38:16
2015/12/31 THU 15:39:17
2015/12/31 THU 15:40:18
2015/12/31 THU 15:41:18
2015/12/31 THU 15:42:19
2015/12/31 THU 15:43:19
2015/12/31 THU 15:44:20
2015/12/31 THU 15:45:21
2015/12/31 THU 15:46:21
2015/12/31 THU 15:47:22

Soit une dérive de 6 secondes pour 10 minutes. Un essai avec le même programme chargé sur un Seeduino Stalker, dont le board comporte une RTC compatible (chip DS3231) montre une dérive de 10 secondes pour 10 minutes. On conçoit certes qu’une dérive est possible, mais 1/60 d’erreur entre deux quartz, dont la précision attendue ne devrait pas dépasser de quelques secondes par jour, ce n’est pas soutenable.

Sources d’erreur

Une recherche sur l’Internet concernant l’utilisation de la fonction millis() m’indique que sa précision dépend :

  • Du quartz… ou du résonateur (bien moins précis !!)
  • De la fonction millis(), qui fait… 1024 microsecondes au lieu de 1000 us

On aura donc 2,4 % de retard à cause de cette définition. Pour en tenir compte dans ma « minute », je recalcule la constante : 600 / 1.024 = 585.9375 -> 586

Résultat, sur le board Stalker : dérive de -5 secondes sur 10 minutes ; sur le Diduino -8 secondes sur 10 minutes. C’est mieux, mais loin d’une précision suisse. Très probablement, le « quartz » du CPU est en fait un résonateur, moins cher. Et bien moins précis.

Pour des durées relativement longues, il vaut mieux interroger une RTC. Contrairement au timer qui est lu directement en quelques instructions, la RTC est connectée par le biais d’un bus I2C. Elle est lue via un pilote software, ce qui est bien plus lent. Mais combien de temps cela prend-il ?Yun_et_RTC

Interroger la RTC

La fonction micros(), qui retourne un nombre de microsecondes écoulées permet des mesures avec une résolution de 4 us. J’ajoute cette mesure dans mon code. Pour éviter le biais de l’appel à micros(), je l’affiche et le soustrait de l’appel à getTime(), qui se charge de lire la RTC.

case 'm':
{
  r_time t;
  long t1 = micros();
  long t2 = micros();
  long delta = t2-t1;
  Serial.print(delta);
  t1 = micros();
  getTime(&t);
  t2 = micros();
  delta = t2-t1-delta;
  Serial.print(" Lecture RTC: ");
  Serial.print(delta);
  Serial.print(" us\n");
}
break;

Résultats (ici, sur le Stalker) :

m
8 Lecture RTC: 1248 us
m
8 Lecture RTC: 1232 us
m
8 Lecture RTC: 1232 us
m
8 Lecture RTC: 1240 us

L’appel de micros() est insignifiant ; il prend 0 à 4 us, soit la résolution du timer (8 us sur Stalker).

Diduino : 1080 à 1100 us
Stalker : 1232 à 1248 us
Yun : 1080 à 1104 us

Horloge sur le Yun

On peut certes y ajouter une horloge RTC comme présenté dans le test ci-dessus.

Le Yun est équipé d’un Linux, qui lui sait se servir du temps sur l’Internet par NTP (Network Time Protocole) et maintenir une horloge interne. Pour cela, il faut aller dans Lucy, le panneau d’administration ; onglet system : Enclencher le service client NTP, et indiquer des serveurs de temps.

Yun_NTP

On met la précision suisse en premier ! Il s’agit ensuite de demander, depuis la partie Arduino à Linux de nous donner le temps, à la résolution de 1 seconde. On utilise un tableau de char global, qui sera mis à jour avec le temps actuel et dont on interroge la valeur pour lancer des processus (forcément rythmés à une période de plus d’une seconde). L’exemple ci-dessous est issu d’un système qui enregistre des températures toute les 10 minutes.

// time given by Unix cmd                  1
//                                    01234567890123456
char dateTime[20]; // asci format, as 14/08/29 22:10:42
// char position for second
#define TIME_S 16

La fonction getTimeStamp( char *p) qui met à jour le temps au format ASCII dans le tableau pointé par p, est appelée au rythme de la boucle principale :

/* getTimeStamp(char *p)
---------------------
This function fill a string with the time stamp
Vars used:
- *p pointer to the destination str
returned value:
- number of chr written
*/
int getTimeStamp(char *p)
{
int i = 0;
char c;
Process time;
// date is a command line utility to get the date and the time
// in different formats depending on the additional parameter
time.begin("date");
// parameters: for the complete date yy/mm/dd hh:mm:ss
time.addParameter("+%y/%m/%d\t%T");
time.run(); // run the command
// read the output of the command
while(time.available() > 0)
{
  c = time.read();
  if (c != '\n')
  p[i] = c;
  i++;
}
p[i] = '\0'; // end of the collected string
return i;
}

Voici maintenant un prédicat simple pour déclencher une action toute les 10 minutes. Il teste que l’unité des minutes soit à 0, ainsi que la dizaine et l’unité des secondes.

/*
IsSyncTime000(char *s)
----------------------
Prédicat permettant une synchro toute les 10 minutes
s: String au format date comme: 06/10/14-23:02:21
avec l'alignement               01234567890123456
Retour: 1 si synchronisation effective, 0 sinon
*/
bool IsSyncTime000(char s[])
{
if (s[16] == '0' && s[15] == '0' && s[13] =='0')
  return true;
else
  return false;
}

Dans la boucle principale, nous avons la lecture du temps, un test de synchronisation et l’enregistrement :

void loop()
{
// maintain state while sync is active
static byte stored;
// get the time from the server:
getTimeStamp(dateTime);
// Toggle LED 3 a the second pulse
digitalWrite(LED3, (dateTime[TIME_S] & 1) ? 1:0);
//store values only at second 00
if (IsSyncTime000(dateTime))//Q: sync windows reached?
{
  if (stored == false)   //Q: not already done?
  {
    storeTemp();         //A: yes, write on file
    stored = true;
  }
}
else //A: no, out of sync windows, reset flag
{
  stored = false;
}
… (autre process)
}//fin de loop()

Remarquez la méthode pour la LED de vie qui clignote selon que le digit des secondes, indexé par TIME_S, est paire ou impaire. Lorsque la fenêtre de synchronisation est présente, soit pendant une seconde toute les 10 minutes, l’enregistrement de températures est réalisé par storeTemp(). Des flags évitent qu’on enregistre plusieurs fois pendant la même seconde, car la boucle tourne à 5..10 fois par seconde.

Résultats de la technique Yun avec NTP

L’enregistreur utilisant cette technique est fiable et précis… tant que l’Access Point (AP) WiFi est présent et que le Yun n’est pas redémarré. En effet, j’ai testé que, après une coupure du courant du Yun, il se reconnecte sans problème au bout d’une minute… pour autant que le WiFi de l’AP soit présent. Mais voilà, lorsqu’une coupure du réseau (c’est rare, mais…) le Yun démarre en 1 minute, alors que l’AP WiFi met facilement 2 minutes pour obtenir la connexion sur le NET.

Sans le WiFi, et donc sans une source de temps NTP, ça ne se passe pas bien. L’horloge Linux va se trouver au 09/09/2011 ; et le Linux va mettre l’Arduino en mode Access Point ! Pour éviter ceci, il Faut modifier le script de démarrage.

Pour cela, aller dans le Web Panel avancé, onglet Administration, Sytem, Startup. Au bas de la page se trouve la fenêtre qui permet l’édition. Commenter la ligne « Wifi-live-or-reset » par un ‘#’ :
# wifi-live-or-reset
boot-complete-notify

Et enregistrer la modification.

En plus de cette précaution, il faut certes prévoir dans le code Arduino d’initialisation de lancer une première lecture du temps Linux ; car ce dernier mets bien plus de temps que la partie Arduino à démarrer.

Conclusion

Pour une utilisation de tick d’horloge en dessous de la seconde, l’utilisation précautionneuse de la fonction millis() convient parfaitement ; en se rappelant toutefois qu’elle dure 1024 us. Pour des durées plus longue, ou simplement pour maintenir une référence de date/temps complète, il faut disposer d’une RTC. L’idéal est d’avoir une connexion LAN, et de lire l’heure sur une source de temps NTP. Encore mieux, une RTC qui serait régulièrement mise à l’heure par du NTP, faisant office de « backup » en cas d’indisponibilité du réseau.

Yves Masur (1/2016)

 

Références:

Module « Grove RTC » : http://www.seeedstudio.com/wiki/Grove_-_RTC

Board « Seeduino Stalker» : http://seeedstudio.com/wiki/Seeeduino_Stalker

Seconde intercalaire : https://fr.wikipedia.org/wiki/Seconde_intercalaire

Utilisation de millis() et de micros() : http://www.gammon.com.au/millis

Forum : How accurate is millis()? http://forum.arduino.cc/index.php?topic=294542.0