mercredi 1 janvier 2020

Cartes d'alimentation par batterie & POWER-BANK



Cartes d'alimentation par batterie

 & POWER-BANK



Aujourd'hui nous allons parler de cartes censées être utilisées comme source d'énergie fonctionnant avec une ou plusieurs batteries LITHIUM-ION ou LIPO.

Certaines cartes ont une utilisation plutôt orientée power-bank, c'est à dire qu'après recharge elles alimenteront un appareil électronique portatif.

D'autres sont plutôt destinées à être utilisées comme alimentation de secours UPS, en complément d'une alimentation secteur permanente.

Elles peuvent alimenter un montage électronique 5V :
  • un ARDUINO
  • un ESP8266 ou ESP32
  • un STM32 
  • etc.
Certaines peuvent servir d'alimentation de secours pour un nano-ordinateur :
  • RASPBERRY PI
  • BANANA PI 
  • BeagleBoard
  • ODROID
  • etc. 
Ou tout simplement recharger un appareil électronique :
  • un téléphone
  • une vapoteuse
  • etc.
Le but est de passer en revue leurs caractéristiques et leur équipement :
  • courant de sortie
  • courant de charge
  • supports de batterie
  • connecteurs
  • LEDs
  • boutons
  • électronique
Mais il est aussi d'examiner leur face cachée, ce qui n'est pas souvent dit sur les sites marchands :
  • consommation à vide, c'est à dire sans appareil branché sur la sortie
  • courant minimal en sortie
  • comportement général
Je présente ici une bonne douzaine de cartes. Certaines sont minimalistes, d'autres beaucoup moins.
Carte simple, chargeur + convertisseur élévateur
Chargeur UPS LANTIANRC

1. Un peu de théorie

Les cartes power-bank sont composées de deux éléments principaux :
  • un module chargeur qui permet de charger la ou les batteries
  • un module élévateur de tension (step-up) qui permet d'élever la tension batterie à 5V pour alimenter un appareil
Pas mal de ces cartes sont équipées de circuits utilisés sur les convertisseurs élévateurs chinois pour lequel il est difficile de trouver de la documentation ou alors celle-ci est écrite en chinois, d'où la nécessité de tester pour se faire une idée.

1.1. Équipement

Certaines cartes sont équipées de supports (holders) et peuvent embarquer 1, 2 ou 4 batteries LITHIUM-ION.
D'autres cartes possèdent simplement deux plots où l'on doit souder des fils de raccordement à la batterie.

La sortie est souvent constituée d'un ou deux connecteurs USB A.

Une ou plusieurs LEDs et un bouton permettent la plupart du temps de consulter l'état de charge de la batterie. Dans certains cas, un afficheur LCD est présent.

1.2. Consommation à vide

Ce courant est celui qui est gaspillé en pure perte par la carte. Il peut être suffisamment important pour vider la batterie en quelques jours, sans que la carte soit raccordée à un quelconque appareil.

Cette notion est importante si l'on désire laisser la carte avec la batterie branchée pendant un certain temps.

Si la consommation à vide est très faible, elle pourra rester chargée sans être utilisée pendant une longue période.

Si elle est élevée il vaudra mieux l'utiliser après l'avoir chargée et ne pas laisser les batteries raccordées lorsque la carte est stockée.

Une consommation à vide de 10mA entraînera une décharge complète d'une batterie de 2500mAH en 250 heures, c'est à dire 10 jours.

Pour chaque carte je donne une indication de l'autonomie de la batterie sans charge sur la sortie. Lorsque cette valeur est élevée (2000 jours par exemple) il est bien évident que je ne tiens pas compte du courant d'auto-décharge de la batterie, et qu'une batterie de bonne qualité aura une autonomie propre de maximum 2 à 3 années.

Lorsque la batterie est insérée dans un support, il est assez malaisé de mesurer le courant débité. J'ai utilisé un petit morceau de PCB double face sur lequel j'ai soudé deux pattes de composant : 


Il suffit d'insérer ce dispositif entre le pôle + de la batterie et le contact du support, et de brancher le multimètre réglé sur le calibre mA, sur les deux pattes, à l'aide de grip fils.

1.3. Courant minimal en sortie

Dans la majeure partie des cas, les cartes power-banks coupent leur sortie lorsque l'appareil raccordé ne consomme pas assez de courant. S'il s'agit de recharger un téléphone ou une vapoteuse, cela n'est pas gênant, mais si l'on cherche à alimenter un montage électronique à faible consommation, cela peut être très problématique.
Ces cartes ne permettent pas l'alimentation permanente d'un ARDUINO, sauf MEGA ou conception particulière du logiciel (voir 1.3.).D'autre part le fait que les LEDs soient allumées pendant l'utilisation réduit l'autonomie.

Souvent la sortie est coupée au bout d'un certain temps (8 secondes par exemple).
 Dans la pratique si l'appareil branché sur la sortie consomme peu (un ARDUINO par exemple) son logiciel devra être conçu pour consommer régulièrement (une période inférieure au temps de coupure) un courant supérieur au courant minimal, pendant une seconde environ, à l'aide d'une résistance reliée à une sortie GPIO.
Si la sortie GPIO n'est pas capable de fournir le courant nécessaire on pourra ajouter un transistor.

J'ai testé ce courant minimal avec plusieurs valeurs de charge : 50mA, 100mA, 150mA, 200mA. Pour une carte qui maintient sa sortie active à 100mA mais ne la maintient pas à 50mA, le courant minimal sera donc situé entre les deux valeurs.

1.4. Comportement général

Ici il s'agit d'examiner le comportement général de la carte :
  • raccordement d'un appareil sur la sortie
  • raccordement du chargeur
  • débranchement du chargeur
  • etc.
Une carte qui ne coupe pas sa sortie lorsque le câble de charge est débranché pourra servir d'alimentation de secours UPS par exemple.

Cette notion est importante si l'on désire alimenter une RASPBERRY PI ou toute autre carte du même type, car lorsque l'on coupe la tension il peut arriver que l'on détériore le système de fichiers de la µSD, lors d'un accès en écriture notamment.

2. Les cartes chargeur / élévateur

Les cartes ont souvent certaines caractéristiques communes.

Si elles sont équipées de LEDs (en général 4) et d'un bouton :
  • le nombre de LEDs allumées indique la capacité restante
  • si l'on appuie sur le bouton, les LEDs s'allument pendant quelques secondes
  • 1 LED clignote pendant la recharge
    • la première si la batterie est vide 
    • la dernière si la batterie est presque pleine
  • les 4 LEDs sont allumées de manière fixe en fin de recharge
  • une ou plusieurs LEDs sont allumées (en fonction la capacité batterie) de manière fixe lorsqu'un appareil est raccordé sur la sortie
Certaines cartes sont équiées d'une LED 5mm blanche réservée à l'éclairage, activable par une pression longue sur un bouton, ou une double pression courte.

Pour chaque carte, si le comportement est différent de celui-ci une indication le mentionne.

2.1. La carte J5019

Courant maximal en sortie 2A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Pastilles à souder (4.5V - 8V)
Connecteur de batterie Pastilles à souder
Connecteur de sortie Pastilles à souder (4.3V - 27V)
Bouton Aucun
LEDs 1 LED rouge
1 LED verte
Consommation à vide 160µA
Autonomie à vide (batterie 2500mAH) 650 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications Power-bank
Alimentation basse consommation
UPS

Cette carte est équipée d'un chargeur TC4056 et d'un convertisseur élévateur SX1308 (marquage B6289K).

Elle possède une sortie réglable en tension.

Grâce à l'absence de coupure pendant le branchement / débranchement du chargeur elle pourra servir de bonne base à une alimentation de secours UPS.

2.2. La carte DD06CVSA

Courant maximal en sortie 2A
Courant de charge 1A
Connecteur de recharge batterie Pastilles à souder (4.5V - 5.2V)
Connecteur de batterie Pastilles à souder
Connecteur de sortie Pastilles à souder (5V 6V 9V ou 12V)
Bouton Aucun
LEDs 4 LEDs rouges
Consommation à vide 60µA
Autonomie à vide (batterie 2500mAH) 1700 jours
Courant minimal en sortie 40mA
Coupure au bout de 30s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie est coupée pendant 1 seconde
Débranchement du chargeur La sortie est coupée
Applications Power-bank

Cette carte est équipée d'un circuit FM3209F.
Elle existe en versions 5V, 6V, 9V ou 12V.

Sa sortie est activable / désactivable par une broche spéciale sur laquelle il faudra brancher un bouton (entre la broche KEY et GND).

Un petit défaut de cette carte : si l'on raccorde la batterie alors que la charge est déjà branchée, la sortie est inactive. Il faut donc raccorder la batterie en premier, puis la charge.
Les 4 LEds indiquent la charge de la batterie en permanence.

Il sera difficile d'en faire autre chose qu'un power-bank.

2.3. La carte T6864-C


Courant maximal en sortie 2A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Pastilles à souder
Connecteur de sortie USB A
Bouton 1
LEDs 4 LEDs blanches
Consommation à vide 15µA
Autonomie à vide (batterie 2500mAH) 7000 jours
Courant minimal en sortie 40mA
Coupure au bout de 10s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie est coupée pendant 1 seconde
Applications Power-bank

Cette carte est équipée d'un circuit HT4938H.

2.4. La carte 864-KC

Courant maximal en sortie 2A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Pastilles à souder
Connecteur de sortie USB A
Bouton 1
LEDs 4 LEDs blanches
Consommation à vide 15µA
Autonomie à vide (batterie 2500mAH) 7000 jours
Courant minimal en sortie 40mA
Coupure au bout de 10s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie est coupée pendant 1 seconde
Applications Power-bank

Cette carte est équipée d'un circuit à 8 pattes non sérigraphié.

2.5. La carte double USB JX-887Y

Courant maximal en sortie 2A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Pastilles à souder
Connecteur de sortie USB A
Bouton 1
LEDs 4 LEDs bleues
1 LED blanche (éclairage)
Consommation à vide 50µA
Autonomie à vide (batterie 2500mAH) 2000 jours
Courant minimal en sortie 150mA
Coupure au bout de 40s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie est coupée pendant 1 seconde
Applications Power-bank

Cette carte est équipée d'un circuit FM5324B et de deux doubles MOSFETs STT8205S.

2.6. La carte double USB et LCD BQ-U5



Courant maximal en sortie 2.1A ou 2 x 1A
Courant de charge 2.1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Pastilles à souder
Connecteur de sortie 2 x USB A
Bouton 1
LEDs Ecran LCD
1 LED blanche (éclairage)
Consommation à vide 50µA
Autonomie à vide (batterie 2500mAH) 2000 jours
Courant minimal en sortie 200mA
Coupure au bout de 20s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie est coupée
Débranchement du chargeur La sortie reste coupée
Applications Power-bank

Cette carte est équipée d'un circuit R358 (marquage inconnu) et de deux doubles MOSFETs AO4606 et AM4953.

Cette carte possède un petit écran LCD permettant d'afficher la capacité de la batterie.

2.7. La carte double USB et LCD H913-A


Courant maximal en sortie 2.1A ou 2 x 1A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Pastilles à souder
Connecteur de sortie 2 x USB A
Bouton 1
Appui long : activation sortie
2 appuis courts : éclairage
LEDs Ecran LCD
Consommation à vide 80µA
Autonomie à vide (batterie 2500mAH) 1300 jours
Courant minimal en sortie 50mA
Coupure au bout de 60s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications Power-bank
Alimentation basse consommation
UPS

Cette carte est équipée d'un circuit à 24 pattes non sérigraphié.

Cette carte possède un petit écran LCD permettant d'afficher la capacité de la batterie.

Sa sortie reste activée dans tous les cas, même si le courant de sortie est faible. Une bonne surprise.

Comme on le voit sur ce graphique, en alimentant une RASPBERRY PI 1B et en recharge permanente, la période de recharge des batteries est d'environ 1/4 d'heure à 1 heure. La tension chute à 4.14V et remonte à 4.19V après une recharge qui dure très peu de temps.

2.8. Dual 18650 battery shield 

Attention, il y a deux versions de cette carte :
  • version avec bouton-poussoir et 4 LEDs
  • version avec interrupteur ON/OFF et 2 LEDs verte et rouge
La version testée ici est la première. La seconde attendra que je la reçoive.


Courant maximal en sortie 5V 2.2A, 3V 1A
Courant de charge 600-800mA
Connecteur de recharge batterie micro USB + USB C
Connecteur de batterie 2 supports 18650
Connecteur de sortie USB A
5V : 3 pastilles à souder
3V : 3 pastilles à souder
Bouton 1
LEDs 4 LEDs rouges
Consommation à vide 30mA par batterie
Autonomie à vide (batterie 2500mAH) 3 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie est coupée pendant 2 secondes
Applications Power-bank (sans laissser la batterie hors utilisation)

Cette carte est équipée d'un circuit TP5602, d'un double MOSFET 82051A et d'un régulateur AMS1117.

Ce n'est pas une merveille. Sa consommation à vide est élevée.
Cela entraînera une décharge complète de deux batteries de 2500mAH en 80 heures, c'est à dire 3 jours.

Lors de la première utilisation, après insertion de batteries chargées, le branchement d'un appareil ne déclenche aucune réaction. Il faut brancher le chargeur quelques secondes et le débrancher pour que l'appareil soit alimenté !

Bref : à réserver pour du bricolage sur établi si l'on a rien d'autre sous la main.
Ne jamais laisser les batteries dans les supports sans quoi elles seront vides en peu de temps.

Note : après examen de la carte je note que deux paires de pattes du circuit à 24 broches sont reliées par des ponts de soudure. Est-ce la source des problèmes ?
Certaines photos du produit sur les sites marchands présentent le même défaut, d'autres non. A fuir !

2.9. Quad 18650 battery shield


Courant maximal en sortie 5V 2.2A
Courant de charge 600-800mA
Connecteur de recharge batterie micro USB + USB C
Connecteur de batterie 2 supports 18650
Connecteur de sortie USB A
5V : 3 pastilles à souder
3V : 3 pastilles à souder
Bouton 1
LEDs 4 LEDs rouges
Consommation à vide 30mA par batterie
Autonomie à vide (batterie 2500mAH) 3 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie est coupée pendant 2 secondes
Applications Power-bank (sans laissser la batterie hors utilisation)

Cette carte est une version 4 batteries de la carte testée en 2.8. (version avec bouton-poussoir). Elle a exactement les mêmes défauts.

2.10. DyMore 1x18650

Courant maximal en sortie 5V 2A, 3V 1A
Courant de charge 500mA
Connecteur de recharge batterie micro USB
Connecteur de batterie 2 supports 18650
Connecteur de sortie USB A
5V : 3 pastilles à souder
3V : 3 pastilles à souder
Bouton 1 interrupteur ON/OFF
LEDs 1 LED rouge
1 LED verte
Consommation à vide 300µA
Autonomie à vide (batterie 2500mAH) 350 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications Power-bank
Alimentation basse consommation
UPS

Cette carte est équipée d'un chargeur TC4056 et d'un convertisseur élévateur FP6298.

Apparemment l'interrupteur ON/OFF ne coupe pas le convertisseur élévateur, ce qui explique la consommation de 300µA même en position OFF.

Sa sortie reste activée dans tous les cas, même si le courant de sortie est faible, ce qui en fait une bonne solution d'alimentation de secours UPS, malgré que cela ne soit pas très clairement exprimé dans les descriptions.

3. Les cartes UPS

3.1. Chargeur UPS LI-M-1P-D


Courant maximal en sortie 1A
Courant de charge 800mA
Connecteur de recharge batterie micro USB
Connecteur de batterie 1 support 18650
Connecteur de sortie 5V : 2 pastilles à souder
Bouton Interrupteur ON / OFF (non fourni)
LEDs 1 LED bicolore rouge / verte
Rouge pendant la recharge
Verte en fin de recharge
1 LED verte allumée en mode UPS
Consommation à vide 720µA UPS activé
4µA sinon
Autonomie à vide (batterie 2500mAH) 140 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications Power-bank
UPS

Cette carte est équipée d'un chargeur TC4056 et d'un convertisseur élévateur SX1308 (marquage B6289K).

J'ai été surpris au départ de mesurer ZÉRO volts sur la sortie. Il faut chercher un peu. Voici ce que la description sur les sites marchands expliquent (en français approximatif) :
La section boost utilise des commutateurs pour contrôler son fonctionnement et son arrêt, et peut également être directement court-circuitée par des tampons de cavalier. (L'interrupteur doit être équipé séparément)
Pour autoriser le fonctionnement en mode UPS, un pont de soudure doit être mis en place sur le plots marqués UPS au centre de la carte, ou un interrupteur doit être soudé sur les pastilles prévues :


Je l'ai équipée d'un mini-switch, après lui avoir coupé une patte :

Cette carte a une consommation à vide très faible.

Si l'on n'active pas l'UPS (interrupteur en position OFF) elle consomme 4µA, c'est à dire certainement moins que le courant d'auto-décharge de la majorité des batteries LITHIUM-ION.
Le chargeur reste toujours en service. La consommation très faible du TC4056 est connue.

Sinon, UPS activé,  elle consomme 720µA.
Comme on le voit sur la photo ci-dessus, la LED verte UPS reste allumée en permanence, ce qui est probablement la cause principale. Il est probable qu'en retirant cette LED on puisse descendre  à environ 170µA, la consommation propre du SX1308.

En utilisation avec une RAPBERRY PI, je remarque que la LED de charge est allumée en permanence, ce qui veut dire que la batterie est perpétuellement en recharge dite "floating", sous 4.2V.
Ce graphique confirme que la tension de la batterie reste figée à 4.23V - 4.24V.
Cela m'étonnerait que ce soit très recommandé pour sa durée de vie.

3.2. La carte UPS LX-28UPS

Cette carte récente n'a pas été testée par mes soins, je me contente de déduire certaines caractéristiques des datasheets des composants utilisés.
Courant maximal en sortie2A
Courant de charge1A
Connecteur de recharge batterieUSB C
Connecteur de batterie2 supports 18650
Connecteur de sortie5V, 9V, 12V : 2 pastilles à souder
BoutonAucun
LEDs1 LED bicolore rouge / verte
Rouge pendant la recharge
Verte en fin de recharge
Consommation à vide150µA
Autonomie à vide (batterie 2500mAH)18 mois
Courant minimal en sortieAucun
Raccordement d'un appareilLa sortie est activée
Raccordement du chargeurLa sortie reste activée
Débranchement du chargeurLa sortie reste activée
ApplicationsPower-bank
UPS

Cette carte est équipée d'un chargeur TP4056 et d'un convertisseur élévateur XR2981.

Comme elle est peu consommatrice, il y a de fortes chances que le courant d'auto-décharge des batteries soit supérieur à la consommation à vide de la carte.

3.3. Chargeur UPS LANTIANRC


Courant maximal en sortie 2A
Courant de charge 2.1A
Connecteur de recharge batterie micro USB
Connecteur de batterie 2 supports 18650
Connecteur de sortie USB A
Pastilles à souder
Bouton 1
LEDs 4 LEDs rouges
Consommation à vide < 10µA par batterie
Autonomie à vide (batterie 2500mAH) 10000 jours
Courant minimal en sortie 100mA
Coupure au bout de 40s
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications Power-bank
UPS

Cette carte existe en versions 5V, 5V+9V ou 5V+9V+12V.

Elle est équipée d'un chargeur IP5306 et d'un convertisseur élévateur SX1308 (marquage B6289K).

Son courant de sortie minimal est élevé (100mA). Elle ne permet pas l'alimentation permanente d'un ARDUINO, y compris d'une MEGA (voir 1.3.).

Par contre elle constitue une solution assez intéressante pour une RASPBERRY PI.
Comme on le voit sur ce graphique la période de recharge des batteries est d'environ 2 heures. La tension chute à 4.1V et remonte à 4.2V après une recharge qui dure 2 minutes.
Je précise que la batterie n'est pas en cause, car elle reste chargée à 4.19V pendant plusieurs jours si elle est non utilisée.
Cette carte n'est pas idéale d'un point de vue durée de vie des batteries mais elle fait tout de même mieux que la carte précédente.

3.4. La carte UPS RPI PowerPack


Courant maximal en sortie 1.8A
Courant de charge 1A
Connecteur de recharge batterie micro USB
Connecteur de batterie Connecteur JST PH
Connecteur de sortie 2 x USB A
Bouton Interrupteur ON/OFF
LEDs 1 LED rouge pendant la recharge
1 LED verte (sortie activée)
Consommation à vide 3.6mA
130µA (sortie désactivée)
Autonomie à vide (batterie 2500mAH) 29 jours
830 jours (sortie désactivée)
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications UPS

Cette carte UPS peut être fixée sur une RASPBERRY PI mais elle peut également servir à alimenter d'autres nano-ordinateurs.

L'interrupteur ON/OFF est inopérant lorsque le chargeur est branché. La sortie reste activée, ce qui n'est pas le cas lorsque le chargeur est débranché.
Cela semble indiquer que la sortie est alimentée directement par le chargeur si le secteur est présent, ce qui est une bonne chose.
La LED rouge reste éteinte mais la tension de la batterie est stable, quoique un peu élevée : 4.3V.
Est-ce le résultat d'un léger courant de charge permanent ? Une mesure au multimètre permet d'éliminer le doute : aucun courant ne circule.

Lors d'un essai avec une RASPBERRY PI 1B je constate que lorsque le chargeur est débranché la carte reboote. Un peu gênant pour une alimentation UPS !

Il s'agit probablement d'une microcoupure. Un condensateur de 3300µF soudé entre masse et +5V sur le connecteur USB permet d'éliminer celle-ci.

Lorsque la carte fonctionne chargeur débranché le fait de rebrancher celui-ci ne déclenche pas la recharge de la batterie. La carte attend que la tension de celle-ci tombe en dessous de 3.7V (50% de capacité environ) pour démarrer la recharge.

Pendant la recharge de la batterie, quand le courant tombe en dessous de 250mA la LED de charge s'éteint, mais la charge continue. Ce chargeur a un comportement assez curieux.

3.5. La carte UPS DFRobot DFR0494



Courant maximal en sortie 2A
Courant de charge 1.6A
Connecteur de recharge batterie micro USB
Connecteur de batterie Connecteur JST PH
Pastilles à souder
Connecteur de sortie 2 x USB A
Bouton 1
LEDs 5 LEDs blanches
Consommation à vide 5mA
Autonomie à vide (batterie 2500mAH) 20 jours
Courant minimal en sortie Aucun
Raccordement d'un appareil La sortie est activée
Raccordement du chargeur La sortie reste activée
Débranchement du chargeur La sortie reste activée
Applications UPS

Cette carte spécifique RASPBERRY PI est déjà présentée ici :
https://riton-duino.blogspot.com/2019/10/raspberry-pi-ups-dfrobot-dfr0494.html

Elle a été testée ici :

https://riton-duino.blogspot.com/2020/02/raspberry-pi-ups-dfrobot-dfr0494-en-test.html

4. Conclusion

On peut constater que les offres alléchantes de certains vendeurs sont loin de tenir leurs promesses.
Certaines cartes sortent franchement du lot et permettent de réaliser des alimentations très performantes.


Cordialement
Henri

samedi 21 décembre 2019

Commander un ARDUINO par la ligne série ou BLUETOOTH


Commander un ARDUINO par la ligne série

ou BLUETOOTH



Après mon article sur l'alimentation numérique il m'a semblé intéressant d'isoler la partie commande par la ligne série.

En effet cette partie peut être reprise facilement si l'on désire envoyer des requêtes à un ARDUINO, que ce soit par une liaison série hardware, software, BLUETOOTH, ou autre.

1. Principe

Dans tous les cas, le principe est le même. Il s'agit de :
  • recevoir une chaîne de caractères provenant de la ligne série
  • interpréter la commande
  • exécuter la commande
  • renvoyer une réponse (éventuellement) 
Examinons chacun de ces points.

1.1. Réception

Recevoir une chaîne de caractères consiste à stocker les caractères reçus dans un buffer (une suite d'octets).

Pour que la lecture sur la ligne série soit efficace, il convient de convenir d'un caractère terminateur. La lecture s'arrêtera donc lorsque ce caractère terminateur sera lu.

On peut choisir par exemple un caractère retour à la ligne '\n' ou '\x0A', ou retour chariot '\r' ou '\x0D', ce qui sera pratique pour essayer les commandes à l'aide d'un terminal (moniteur série de l'IDE ARDUINO).

Pour ceux qui désireraient transférer des données de manière plus sécurisé qu'avec un simple terminateur je renvoie à cet article :
https://riton-duino.blogspot.com/2019/04/arduino-un-protocole-serie.html
Ce protocole implémente un CRC et des répétitions multiples.

1.2. Interprétation

Interpréter la commande revient à la découper en morceaux :
  • l'identifiant de la commande (un mot-clé, un entier, etc.)
  • les éventuels arguments
1.2.1. binaire, hexadécimal et ASCII
Très souvent les débutants se prennent les pieds dans le tapis avec ces notions.

La notation binaire est la représentation d'un nombre de un ou plusieurs octets sous forme de 1 et de 0. Le bit de poids faible (à droite) vaut 1 point, le deuxième vaut 2 points, le troisième vaut 4 points, le quatrième vaut 8 points, etc. En C on la représente comme ceci (avec un 0B) :

Sur 1 octet :
0B00000010 : 2
0B00000110 : 6
0B11111111 : 255

Sur 2 octets :
0B0000000000000110 : 6
0B1111111111111111 : 65535

L'hexadécimal est une représentation binaire compacte d'un ou plusieurs octets : 
0x7F : 0B01111111 ou 127 en décimal
0x68 : 0B1101000 ou 104 en décimal
0x7F68 : 0B0111111101101000 ou 32616 en décimal

En informatique, ce n'est qu'une représentation visuelle plus facile à lire que le binaire :

0x7F68 est plus simple à écrire que 0B0111111101101000 mais c'est la même chose.

L'ASCII est une norme d'encodage informatique des caractères alphanumériques.

Chaque caractère est représenté sur un seul octet :
'A' : 65 (0x41 en hexa)
'B' : 66 (0x42 en hexa)
'0' : 48 (0x30 en hexa)
'1' : 49 (0x31 en hexa) 

Il n'y a aucun besoin de conversion si l'on veut obtenir la valeur décimale ou hexadécimale d'un caractère ASCII :

'A' ou 65 ou 0x41 sont équivalents.

Le seul problème réside au niveau des types de variables employés :

void setup()
{
  Serial.begin(115200);
  Serial.println('A');
  Serial.println(0x41);
  Serial.println(65);
  Serial.println((char)0x41);
  Serial.println((char)65);
}

void loop()
{

}


L'opération (char)0x41 n'est pas une conversion, c'est un cast qui vise à faire croire au compilateur que le nombre passé en paramètre est un caractère.

Le compilateur utilisera donc une méthode Serial::println(char) à la place de Serial::println(int), et c'est tout.

Ce sketch affichera :

A
65
65
A
A


1.2.2. Chaîne de caractères
En C une chaîne de caractère peut être représentée de plusieurs manières :

"A56T" est équivalent à "\x65\x35\x36\x54".
"\x65\x66\x67\x0A" est équivalent à "ABC\x0A".

 '\x0A' est le caractère NL ou NewLine (retour à la ligne). Il ne peut être représenté de manière visuelle comme une lettre de l'alphabet. C'est un caractère non imprimable. Dans une chaîne de caractères on peut le remplacer par '\n' : "ABC\n".

Il faut bien distinguer les chiffres d'une chaîne de caractères ou chaque chiffre ou digit est un caractère imprimable, des chiffres d'un nombre entier :

"1234" est équivalent à "\x31\x32\x33\x34".
Tandis que le nombre 1234 est équivalent à 0x04D2.
"1234" occupe 4 octets en mémoire, 1234 n'en occupe que deux.

Si l'on veut transmettre un nombre sur une ligne série, on peut adopter les deux formats, mais le format chaîne de caractères impliquera une conversion :

Pour transmettre un nombre en clair, on utilisera la méthode Serial.print(), qui effectue elle-même la conversion :

Serial.print(10);

Ce qui aura pour conséquence de transmettre deux caractères '1' et '0', donc 0x31 et 0x30, ou la chaîne "\x31\x30" et donc "10". 

Serial.write(10);


Cet appel à la méthode write transmettra un unique caractère 10 ou 0x0A ou '\x0A'.

Dans le cas où l'on transmet en clair, lorsque l'on recevra "1234", on pourra convertir cette chaîne en nombre :

int n = atoi("1234");


Le cas du binaire est plus complexe. Il faudra tenir compte de l'endian du processeur.
Pour certains processeurs les entiers sont placés en mémoire avec l'octet de poids fort à gauche (big endian), pour d'autres processeurs le poids fort est à droite (little endian) :

Heureusement pour nous la majeure partie des processeurs que nous utilisons sont "little endian".

Sur un ARDUINO :

void setup() {
  Serial.begin(115200);
  char *s = "\x10\x11";
  int n = *(int *)s;
  s = "\x10\x11\x12\x13";
  long l = *(long *)s;
  Serial.write(10);
  Serial.println(n, HEX);
  Serial.println(l, HEX);
}

void loop()
{
}


Les deux chaînes de caractères "\x10\x11" et "\x10\x11\x12\x13" sont placées en mémoire dans l'ordre "naturel".
L'opération *(int *)s vise à faire croire au compilateur que les deux cases mémoire à l'adresse s sont en fait l'adresse d'un entier.
L'opération *(long *)s vise à faire croire au compilateur que les quatre cases mémoire à l'adresse s sont l'adresse d'un entier long.
Cette opération s'appelle un cast.

Le sketch affichera :

1110
13121110


Nous sommes bien en présence d'un processeur little endian.

Lors de la réception d'un entier binaire sur une ligne série, on pourra se dispenser de conversion. Imaginons que l'on ait reçu deux octets 0xD2 et 0x04 dans un buffer :

void setup() {
  Serial.begin(115200);
  char buf[] = "\xD2\x04";
  Serial.println(*(int *)buf);
}

void loop()
{
}


Ici aussi on utilise un cast.

"\xD2\x04" est la représentation du nombre 1234 ou 0x04D2 dans la mémoire d'un processeur little endian.

Le sketch affiche bien :

1234

Mais sur un processeur big endian on afficherait :

-11772

Ce qui n'est pas tout à fait la même chose.

Si l'on n'est pas familier avec ces notions il vaut mieux éviter le binaire et travailler en clair (commandes ASCII), même si cela implique des conversions.
D'autre part, comme dit précédemment, la transmission de trames binaires requiert l'utilisation d'un protocole beaucoup plus élaboré qu'un simple terminateur.

1.2.3. Commandes en clair
La commande peut être composée de mots en clair (ASCII) séparés par des espaces. Les mots clés et paramètres peuvent avoir une taille variable :

COMMANDE PARAM1 PARAM2

Exemple :

"LR ON
"LR OFF"
"LR BL 500"

Où chaque mot a une signification :
  • "LR" correspond au code "LED-ROUGE"
  • "ON" signifie allumer
  • "OFF" signifie éteindre
  • "BL" signifie clignoter
  • "500" donne la période de clignotement
1.2.4. Commandes compactes
Il est possible de compacter les données à transmettre, en réduisant la taille des mots clés et en supprimant les séparateurs. Dans ce cas les données doivent avoir une taille fixe.

Exemple :

"R1"
"R0"
"RB01F4"
  • "R" correspond au code "LED-ROUGE"
  • "1" signifie allumer
  • "0" signifie éteindre
  • "B" signifie clignoter
  • "01F4" (500) donne la période de clignotement (l'hexadécimal occupe moins de place)
Il sera là aussi possible d'utiliser un caractère terminateur du type retour ligne ou chariot, car ces caractères n'aparaissent jamais dans les données.

1.2.5. Commandes binaires
La commande peut aussi être composée de codes binaires de taille définie.

Exemple :

"\x11\x01"
"\x11\x00"
"\x11\x02\x01\xF4"
  • '\x11' correspond au code "LED-ROUGE"
  • '\x01' signifie allumer
  • '\x00' signifie éteindre
  • '\x02' signifie clignoter
  • "\x01\xF4" (500) donne la période de clignotement
Il est bien évident qu'envoyer quelques caractères binaires est plus économique qu'envoyer des caractères en mode texte pour faire le même travail.
Ici la période est transmise sur deux octets : "\x01\xF4"
Dans le cas précédent il en fallait quatre :  "01F4" c'est à dire "\x30\x31\x46\x34"

Mais le binaire complique pas mal les choses, et c'est nettement moins lisible.

Dans le cas où la commande est binaire, il conviendra d'adopter un format (structure) adapté. Il sera difficile d'envoyer les commandes à l'aide d'un terminal, il faudra écrire un logiciel émetteur adapté.
Il sera également impossible d'utiliser un caractère terminateur du type retour ligne ou chariot, car ces caractères peuvent faire partie des données binaires.

Si par exemple nous avions à transmettre la commande suivante :

"\x11\x03\x01\x0A"

Cette fois-ci la période de clignotement vaut 0x010A (266). Malheureusement le dernier caractère vaut 0x0A ou '\n' (retour à la ligne). La réception s'arrêtera donc à ce caractère et la commande sera tronquée :

"\x11\x03\x01"

 Il faudra nécessairement passer par un protocole plus complexe, comportant un caractère de début de trame, un caractère de fin de trame et une gestion des caractères de contrôle (mode transparent).

1.2.6. Restons simple
Dans cet article, censé rester simple, nous parlerons uniquement des deux premier cas : les commandes en clair et compactes.

1.3. Exécution

L'exécution de la commande revient à associer un morceau de code correspondant à un identifiant de commande connu.

Nous allons étudier un petit exemple répondant à quelques requêtes simples. Les requêtes sont du type chaîne de caractères (C string), de longueur variable, et le caractère terminateur sera un retour à la ligne :
  • "HELLO" : l'ARDUINO répond "HELLO"
  • "UPPER string" : l'ARDUINO renvoie la chaîne string en majuscules
  • "LOWER string" : l'ARDUINO renvoie la chaîne string en minuscules
  • "ADD a b" :  l'ARDUINO renvoie la somme de a + b
Il est ensuite facile de créer des commandes pour effectuer des actions plus concrètes :
  • commander un moteur
  • allumer des LEDs
  • afficher un message sur un LCD, un TFT
  • lire une température
  • etc.
Cet article développera donc les points suivants :
  • recevoir une chaîne de caractères provenant d'une ligne série
  • écrire un analyseur ou "parser" en utilisant sscanf et les pointeurs
  • exécuter des actions correspondant à une commande précise
L'intérêt des exemples suivants est de pouvoir être testés sans matériel particulier. Une simple carte ARDUINO suffit et un script PYTHON est même fourni si l'on désire essayer de faire communiquer l'ARDUINO avec un PC ou une RASBERRY PI.

2. La ligne série

Qu'il s'agisse d'une ligne série hardware ou software (émulée par logiciel sur deux broches d'entrée / sortie standard) une ligne série est un objet instancié à partir de la classe HardwareSerial ou SoftwareSerial.

2.1. HardwareSerial

Lorsqu'il s'agit d'un HardwareSerial l'objet est déjà instancié par la librairie ARDUINO : il s'appelle Serial et permet de communiquer par le cordon USB.

Certaines cartes disposent de plusieurs lignes série : la MEGA par exemple (Serial1, Serial2, etc.). Ces lignes série supplémentaires nécessitent l'emploi d'un convertisseur USB / série (voir plus bas).

2.2. SoftwareSerial

Lorsqu'il s'agit d'un SoftwareSerial il faudra instancier l'objet soi-même :

const byte rxPin = 2;
const byte txPin = 3;
SoftwareSerial mySerial (rxPin, txPin);


Il faudra également ajouter un convertisseur USB / Série si le but est de communiquer avec un PC ou une carte RASPBERRY PI :

Si deux ARDUINO communiquent entre eux à courte distance, on pourra les relier directement à l'aide de deux fils (RX sur TX et TX sur RX), et d'un fil de masse.
Si les distances sont plus importantes, un convertisseur de niveaux et un câble RS232 devront être utilisés :
S'il s'agit de communiquer par BLUETOOTH, le module (HC-05 par exemple) pourra être relié directement à l'ARDUINO toujours en croisant RX / TX et TX / RX. Je vous renvoie au blog d'Eskimon : utiliser-un-module-bluetooth-hc-05-avec-arduino

2.3. HardwareSerial ou SoftwareSerial

Si l'on désire faire communiquer l'ARDUINO avec une ligne série ou un module BLUETOOTH tout en conservant la ligne réservée au cordon USB pour le chargement et pour afficher des information aidant à déboguer, le SoftwareSerial est fortement recommandé.
Attention cependant, un SoftwareSerial est moins rapide. 57600 baud me semble la limite haute. Si l'on rencontre des problèmes de transmission, réduire la vitesse.

Qu'il s'agisse de l'un ou l'autre de ces deux classes, HardwareSerial et SoftwareSerial héritent des méthodes de la classe Stream (available(), read(), write(), etc.).

Ceci veut dire que, quel que soit le type de ligne série employé, dans notre programme seul l'instanciation de l'objet ligne série sera différente. Tout le reste du code sera identique :

Avec HardwareSerial :

void loop()
{
  if (Serial.available()) {
  // ...


Avec SoftwareSerial :

SoftwareSerial mySerial (rxPin, txPin);

void loop()
{
  if (mySerial.available()) {
  // ...


3. Choix de la méthode de lecture

Il existe plusieurs possibilités de lecture d'une chaîne de caractères sur la ligne série :
  • Stream.read() : lecture caractère par caractère
  • Stream.readStringUntil() : lecture d'une chaîne de caractères
  • Stream.readBytesUntil() : lecture d'une chaîne de caractères
Suivant la méthode choisie certaines options seront possibles, ou non.

3.1. Time-out

Le time-out est le temps en millisecondes que l'appelant peut tolérer pour la réception d'un caractère. On peut limiter ce temps à l'aide de la méthode setTimeout().

Cela veut dire que si aucun caractère n'est reçu dans les temps la méthode read() retournera -1, la méthode readStringUntil() retournera une chaîne vide, et la méthode readBytesUntil() retournera une taille de ZÉRO.

Par défaut ce temps vaut 1 seconde.

Si l'on a besoin de 3 secondes on ajoutera au setup() :

Serial.setTimeout(3000);

3.2. ReadBytesUntil

Cette méthode stocke les caractères reçus dans un buffer fourni par l'appelant (l'appelant fournit également sa taille). La lecture est effectuée jusqu'à rencontrer le caractère terminateur spécifié par l'appelant :

static char buf[100];
size_t size = Serial.readBytesUntil('\n', buf, 100);


La méthode a un inconvénient : la lecture est bloquante jusqu'à rencontrer le caractère terminateur ou jusqu'à ce que le time-out soit écoulé.

Cela veut dire que notre sketch ne pourra en aucun cas exécuter d'autres actions pendant la réception de la commande (sauf gestion par interruptions).

Voici notre exemple utilisant ReadBytesUntil() :

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Boot message"));
}

#define CMD_MAX               100

void loop()
{
  static char buf[CMD_MAX];

  if (!Serial.available()) {
    return;
  }
  memset(buf, 0, CMD_MAX);
  size_t size = Serial.readBytesUntil('\n', buf, CMD_MAX - 1);

  // exécution de la commande

  if (!strncmp(buf, "HELLO", 4)) {
    Serial.println("HELLO");
  }
  else if (!strncmp(buf, "UPPER", 5)) {
    char s[20];
    char arg[20];
    if (sscanf(buf, "%s %s", s, arg) != 2) {
      // invalid arguments
      Serial.println("EINVAL");
      return;
    }
    Serial.println(strupr(arg));
  }
  else if (!strncmp(buf, "LOWER", 5)) {
    char s[20];
    char arg[20];
    if (sscanf(buf, "%s %s", s, arg) != 2) {
      // invalid arguments
      Serial.println("EINVAL");
      return;
    }
    Serial.println(strlwr(arg));
  }
  else if (!strncmp(buf, "ADD", 3)) {
    char s[20];
    int a, b;
    if (sscanf(buf, "%s %d %d", s, &a, &b) != 3) {
      // invalid arguments
      Serial.println("EINVAL");
      return;
    }
    Serial.println(a + b);
  }
  else {
    // command not supported
    Serial.println("WHAT'S UP DOC ?");
  }

  // -------------------------
}


Les réponses renvoyées à l'émetteur de la commande sont représentée en caractères gras.

3.2.1. Essai

Les commandes UPPER, LOWER et ADD peuvent être essayées à l'aide du moniteur série de l'IDE ARDUINO :
  • HELLO
  • UPPER azertyuiop
  • LOWER AZERTYUIOP
  • ADD 33 12
On peut ainsi directement visualiser les réponses.

3.2.2. Les arguments

Nous allons aborder la partie "parser" ou analyseur et donc voir comment récupérer les données de la commande.

Les commandes UPPER, LOWER et ADD acceptent un ou deux arguments ou paramètres. Ces arguments sont extraits de la commande à l'aide de sscanf().

La chaîne de format permet de spécifier le nombre d'arguments et leur type :
  • %s : chaîne de caractères
  • %d : entier
  • %u : entier non signé
  • %l : long
  • %lu : long non signé
La chaîne de format est suivie des adresses où vont être stockés les valeurs :

    char s[20];
    int a, b;
    if (sscanf(buf, "%s %d %d", s, &a, &b) != 3) {


s représente l'adresse d'une chaîne s de 20 caractères (la commande).
&a représente l'adresse d'un entier a (l'argument a).
&b représente l'adresse d'un entier b (l'argument b).

Si les pointeurs vous sont inconnus :
https://zestedesavoir.com/tutoriels/755/le-langage-c-1/1043_aggregats-memoire-et-fichiers/4277_les-pointeurs/

sscanf retourne le nombre d'arguments reconnus. Il est donc facile de contrôleur la validité de la commande.

On pourrait utiliser également strtok() pour analyser la commande.

3.2.3. Réponse
La réponse à une commande est renvoyée par la ligne série sous forme de chaîne de caractères terminée par '\n' : Serial.println().

L'émetteur de la commande sera ainsi en mesure de lire facilement la réponse.

3.2.4. Commande inconnue
Lorsque la commande n'est pas reconnue le sketch renvoie une erreur par la ligne série :

  else {
    // command not supported
    Serial.println("WHAT'S UP DOC ?");
  }


On peut renvoyer une erreur plus typée bien entendu : un numéro d'erreur par exemple.

3.3. ReadStringUntil

Cette méthode peut sembler la plus pratique, mais elle s'accompagne d'un inconvénient majeur : elle revoie un objet C++ du type String. Cela suppose une allocation dynamique de mémoire, et donc une possible fragmentation de celle-ci.

Elle est plutôt déconseillée sur les plateformes ATMEGA328, équipées de seulement 2K octets de RAM.

Si l'on désire malgré tout l'utiliser on peut remplacer dans l'exemple précédent :

  if (!Serial.available()) {
    return false;
  }
  memset(buf, 0, CMD_MAX);
  size_t size = Serial.readBytesUntil('\n', buf, CMD_MAX - 1);

  // exécution de la commande


Par :

  if (!Serial.available()) {
    return false;
  }
  String buf = Serial.readStringUntil('\n');

  // exécution de la commande


3.4. Read

Cette méthode stocke les caractères reçus dans un buffer fourni par l'appelant. L'appelant contrôle lui-même que la taille du buffer n'est pas dépassée. La lecture est effectuée caractère par caractère et entre chacun d'eux le programme peut faire autre chose.

Voici notre exemple utilisant read() :

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Boot message"));
}

void loop()

{
  commandShell();

  // ICI : autres traitements

}

#define CMD_MAX               100

bool commandShell(void)
{
  static char buf[CMD_MAX];
  static int i;

  if (!Serial.available()) {
    return false;
  }
  int c = Serial.read();
  if (c != -1) {
    if (c == '\n') {
      if (strlen(buf) == 0) {
        return false;
      }

      // exécution de la commande

      if (!strncmp(buf, "HELLO", 4)) {
        Serial.println("HELLO");
      }
      else if (!strncmp(buf, "UPPER", 5)) {
        char s[20];
        char arg[20];
        if (sscanf(buf, "%s %s", s, arg) != 2) {
          // invalid arguments
          Serial.println("EINVAL");
          return true;
        }
        Serial.println(strupr(arg));
      }
      else if (!strncmp(buf, "LOWER", 5)) {
        char s[20];
        char arg[20];
        if (sscanf(buf, "%s %s", s, arg) != 2) {
          // invalid arguments
          Serial.println("EINVAL");
          return true;
        }
        Serial.println(strlwr(arg));
      }
      else if (!strncmp(buf, "ADD", 3)) {
        char s[20];
        int a, b;
        if (sscanf(buf, "%s %d %d", s, &a, &b) != 3) {
          // invalid arguments
          Serial.println("EINVAL");
          return true;
        }
        Serial.println(a + b);
      }
      else {
        // command not supported
        Serial.println("WHAT'S UP DOC ?");
      }

      // -------------------------

      buf[i] = 0;
      i = 0;
      return true;
    }
    if (c != '\r') {
      if (i < CMD_MAX - 1) {
        buf[i++] = c;
        buf[i] = '\0';
      }
      else {
        Serial.println("ETOOBIG");
      }
    }
  }
  return true;
}


Dans cet exemple on peut remarquer plusieurs choses :

La fonction loop() peut exécuter d'autres actions pendant la réception d'une commande :

void loop() {
  commandShell();

  // ICI : autres traitements

}


La réception s'arrête aussitôt que '\n' est rencontré :

    if (c == '\n') {

Le caractère '\r' est ignoré :

    if (c != '\r') {

Une commande de longueur nulle est ignorée :

      if (strlen(buf) == 0) {
        return;
      }


La commande est terminée par zéro, ce qui permettra son exploitation à l'aide des routines de manipulation de chaînes de caractères.
La longueur de la commande est vérifiée afin d'éviter les débordements :

      if (i < CMD_MAX - 1) {
        buf[i++] = c;
        buf[i] = '\0';
      }
      else {
        Serial.println("ETOOBIG");
      }


Si l'émetteur envoie une commande trop longue, le sketch renvoie une erreur.

3.5. Read (commandes compactes)

Cet exemple commande la LED D13 :

"R1" : allumage de la LED rouge
"R0" : allumage de la LED rouge
"RBXXXX" : clignotement de la LED rouge. XXXX est un nombre hexadécimal

Si XXXX vaut "01F4", cela équivaut à un clignotement à 500ms.

void setup()
{
  Serial.begin(115200);
  pinMode(13, OUTPUT);
  Serial.println(F("Boot message"));
}

int blink;
int timer;
byte ledState;

void loop()
{
  static unsigned long previousMillis = 0;

  commandShell();
  if (blink) {
    unsigned long currentMillis = millis();

    if (currentMillis - previousMillis >= blink) {
      previousMillis = currentMillis;
      if (ledState == LOW) {
        ledState = HIGH;
      } else {
        ledState = LOW;
      }
      digitalWrite(13, ledState);
      Serial.println(ledState);
    }
  }
}

#define CMD_MAX               10

bool commandShell(void)
{
  static char buf[CMD_MAX];
  static int i;

  if (!Serial.available()) {
    return false;
  }
  int c = Serial.read();
  if (c != -1) {
    if (c == '\n') {
      if (strlen(buf) == 0) {
        return false;
      }

      // exécution de la commande

      if (buf[0] == 'R') {
        if (buf[1] == 'B') {
          if (strlen(buf) != 6) {
            // invalid arguments
            Serial.println("EINVAL");
            return true;
          }
          sscanf(buf + 2, "%x", &blink);
          Serial.println("OK");
        }
        else {
          if (strlen(buf) != 2) {
            // invalid arguments
            Serial.println("EINVAL");
            return true;
          }
          if (buf[1] != '0' && buf[1] != '1') {
            // invalid arguments
            Serial.println("EINVAL");
            return true;
          }
          blink = 0;
          digitalWrite(13, buf[1] == '0' ? LOW : HIGH);
          Serial.println("OK");
        }
      }
      else {
        // command not supported
        Serial.println("WHAT'UP DOC ?");
      }
      // -------------------------

      buf[i] = 0;
      i = 0;
      return true;
    }
    if (c != '\r') {
      if (i < CMD_MAX - 1) {
        buf[i++] = c;
        buf[i] = '\0';
      }
      else {
        Serial.println("ETOOBIG");
      }
    }
  }
  return true;
}


Dans cet exemple à un seul octet de commande, une simple comparaison d'octet suffit à identifier la commande :

      if (buf[0] == 'R') {

Pour le deuxième octet également :

        if (buf[1] == 'B') {

Les valeurs possibles sont 'B', '1' et '0' (sinon une erreur est renvoyée) :

          if (buf[1] != '0' && buf[1] != '1') {
            // invalid arguments
            Serial.println("EINVAL");
            return true;
          }


Dans tous les cas, la longueur de la commande est contrôlée :

Clignotement :

          if (strlen(buf) != 6) {

Allumage et extinction :

          if (strlen(buf) != 2) {

La durée de clignotement est récupérée aussi à l'aide de sscanf :

          int blink;
          sscanf(buf + 2, "%x", &blink);


La valeur du clignotement est récupérée à l'adresse du troisième octet (buf + 2). Le format est %x, ce qui signifie entier hexadécimal.

Un format %lx signifierait entier long hexadécimal.

          long blink;          sscanf(buf + 2, "%lx", &blink);

4. Côté émetteur

Du côté émetteur, si celui-ci est un PC ou une RASPBERRY PI, on peut écrire un logiciel d'envoi de commandes à l'aide d'un langage quelconque, celui avec lequel on se sent le plus à l'aise :
  • PYTHON
  • JAVA
  • RUBY
  • GCC
  • Visual Studio
  • etc.
L'implémentation de routines de communication à l'aide de Visual Studio en C ou C++ n'est pas simple, à moins d'être spécialiste, et je recommande plutôt l'utilisation d'autres langages.

Dans tous les cas, si la communication doit se faire par le cordon USB de la carte ARDUINO, le moniteur série de l'IDE ARDUINO ou le terminal devra être fermé, sinon une erreur sera affichée :

serial.serialutil.SerialException: [Errno 16] could not open port /dev/ttyUSB1: [Errno 16] Device or resource busy: '/dev/ttyUSB1'

Les amateurs de BLUETOOTH s'orienteront sur ceci :
Serial Bluetooth Terminal

4.1. PYTHON

4.1.1. Commandes en clair
Voici un script PYTHON permettant de communiquer avec le sketch ARDUINO des exemples suivants :
  • 3.2. ReadBytesUntil 
  • 3.4. Read
#!/usr/bin/env python

import time, serial

def sendReply(request):
 global ser
 ser.write(request+'\r\n')
 answer = ser.read_until('\n')
 return answer.strip('\r\n')

if __name__ == "__main__":
 ser = serial.Serial('/dev/ttyUSB1', baudrate=115200)
 ser.timeout = 3
 time.sleep(1)
 ser.read_until('\n') # read boot message
 answer = sendReply('HELLO')
 print "answer:", answer
 answer = sendReply('UPPER azertyuiop')
 print "answer:", answer
 answer = sendReply('LOWER AZERTYUIOP')
 print "answer:", answer
 answer = sendReply('ADD 12 13')
 print "answer:", answer
 answer = sendReply('blabla')
 print "answer:", answer


4.1.2. Commandes compactes
Voici un script PYTHON permettant de communiquer avec le sketch ARDUINO de l'exemple suivant :
  • 3.5. Read (commandes compactes)
#!/usr/bin/env python

import time, serial

def sendReply(request):
    global ser
    ser.write(request+'\r\n')
    answer = ser.read_until('\n')
    return answer.strip('\r\n')

if __name__ == "__main__":
    ser = serial.Serial('/dev/ttyUSB1', baudrate=115200)
    ser.timeout = 3
    time.sleep(1)
    ser.read_until('\n') # read boot message
    answer = sendReply('R1')
    print "answer:", answer
    time.sleep(2)
    answer = sendReply('R0')
    print "answer:", answer
    time.sleep(2)
    answer = sendReply('RB01F4')
    print "answer:", answer
    time.sleep(4)
    answer = sendReply('R0')
    print "answer:", answer
    answer = sendReply('B')
    print "answer:", answer


Dans les deux cas la fonction sendReply() envoie dans un premier temps la commande :

 ser.write(request+'\r\n')

Puis reçoit la réponse de l'ARDUINO :

 answer = ser.read_until('\n')

Le principe de réception est tout à fait analogue à celui utilisé pour l'ARDUINO :

PYTHON :

 answer = ser.read_until('\n')

ARDUINO :

  String buf = Serial.readStringUntil('\n');

Ces scripts utilisent PySerial, un module permettant de communiquer par une ligne série sur un PC (Linux, Windows, BSD) ou bien sûr une RASPBERRY PI.

Il suffit de remplacer '/dev/ttyUSB1' par le port réel utilisé. Ce port est facilement identifiable sur une machine LINUX à l'aide de dmesg (après avoir connecté la carte sur le port USB) :

$ dmesg
[19050.492772] usb 2-1.1.1: new full-speed USB device number 22 using ehci-pci
[19050.586006] usb 2-1.1.1: New USB device found, idVendor=1a86, idProduct=7523
[19050.586012] usb 2-1.1.1: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[19050.586015] usb 2-1.1.1: Product: USB2.0-Serial
[19050.586575] ch341 2-1.1.1:1.0: ch341-uart converter detected
[19050.588589] usb 2-1.1.1: ch341-uart converter now attached to ttyUSB1


Sur une machine Windows, utiliser le gestionnaire de périphériques et remplacer '/dev/ttyUSB1' par le port de COM adéquat (COM3, COM4, etc.).

L'exécution du premier script donne le résultat suivant :
 
$ ./serial-python.py 
answer: HELLO
answer: AZERTYUIOP
answer: azertyuiop
answer: 25
answer: WHAT'S UP DOC ?
$

L'exécution du deuxième script donne le résultat suivant :

$ ./serial-python-compact.py
answer: OK
answer: OK
answer: OK
answer: OK
answer: WHAT'UP DOC ?


4.2. RUBY

Voici un script RUBY permettant de communiquer avec le sketch ARDUINO des exemples suivants :
  • 3.2. ReadBytesUntil 
  • 3.4. Read
require "serialport"

def sendReply tty, s
    tty.write s
    return tty.readline
end
   
tty = SerialPort.new("/dev/ttyUSB1", 115200, 8, 1, SerialPort::NONE)
tty.read_timeout = 3000

boot = tty.readline
answer = sendReply tty, "HELLO"
puts "answer " + answer
answer = sendReply tty, "UPPER azertyuiop"
puts "answer " + answer
answer = sendReply tty, "LOWER AZERTYUIOP"
puts "answer " + answer
answer = sendReply tty, "ADD 12 13"
puts "answer " + answer
answer = sendReply tty, "blabla"
puts "answer " + answer


Ce script utilise SerialPort, l'équivalent RUBY de PySerial.

L'exécution du script donne le résultat suivant :

$ ruby serial-ruby.rb
answer HELLO
answer AZERTYUIOP
answer azertyuiop
answer 25
answer WHAT'UP DOC ?
$


5. Liens utiles

Un protocole série sécurisé avec CRC :
https://riton-duino.blogspot.com/2019/04/arduino-un-protocole-serie.html

Ce protocole est utilisable également lorsque les données doivent être transmises en toute sécurité avec un CRC.

6. Conclusion

J'espère avoir répondu aux questions que se posent souvent les débutants quand il s'agit de répondre à la problématique de la communication série, de l'interprétation et de l'exécution de commandes à distance.


Cordialement
Henri

7. Mises à jour
22/12/2019 : 1.2.1. binaire, hexadécimal et ASCII
                     1.2.2. Chaîne de caractères
                     3.5. Read (commandes compactes)
                     4.2. RUBY