dimanche 31 décembre 2017

Arduino - C+ASM

Dans un précédent article disponible ici nous avons vu comment faire pour utiliser TextWrangler (ou BBEdit) en lieu et place de l'éditeur de texte de l'Ide Arduino, avec une  coloration de syntaxe et un Apple Script permettant de lancer directement la compilation et le transfert. Dans un second article, disponible ici, nous avons vu comment installer AVRA et AVRDUDE ainsi qu'un module AppleScript pour TextWrangler afin de programmer notre Arduino en Assembleur.
Puisque nous avons tout pour travailler en C et en Assembleur avec TextWrangler, nous allons voir cette fois comment mélanger ces 2 langages, sans se prendre la tête à taper du code inline.

Pourquoi mélanger le C et l'assembleur?

L'assembleur possédait, possède et possédera toujours un énorme avantage sur les langages dit "évolués": celui de vous fournir une connexion directe avec le processeur en lui fournissant exactement les instructions nécessaires, ni plus, ni moins.
Un langage évolué quant à lui, permet l'usage de fonctions plus complexes mais qui doivent être évidement traduites en langage machine par le compilateur, cette traduction produisant des résultats plus ou moins efficaces.
Ainsi, le simple "blink" qui fait clignoter une LED de notre Arduino, occupe environ 900 octets de FLASH quand il est écrit en C et moins de 200 quand il est écrit en assembleur.
Code plus petit signifiant moins d'instructions pour le processeur, signifie donc aussi exécution plus rapide.

Ceci étant ce gain de vitesse et de taille n'est pas toujours intéressant.
En effet si on regarde nos codes Arduino, on voit que très souvent nous rajoutons des delay() pour que les Led ne clignotent pas trop vite ou pour que l'utilisateur ait le temps de réagir. Or si on passe notre temps à attendre, pourquoi faire du code assembleur pour aller plus vite?
De même, si nous regardons la consommation mémoire nous constatons que la mémoire qui est la plus rapidement saturée ce n'est pas la FLASH donc la mémoire qui contient le code machine, mais la SRAM qui contient les variables. Or l'assembleur va essentiellement permettre d'obtenir un résultat plus petit au niveau du code donc de l'occupation de la FLASH. De plus, si nous arrivons en limite de FLASH, c'est généralement parce que notre projet est très gros et dans ce cas il y a de fortes chances qu'il nécessite un grand nombre de branchements. En fait c'est au niveau de la SRAM et du nombre de PIN que nous allons le plus souvent atteindre les limites de notre Arduino nous obligeant à passer par exemple du UNO au Mega.

Par contre, imaginons que nous soyons sur un projet Arduino avec de la communication: une capture d'image puis un envoi via un module HC-12 donc sur une grande distance. Plus nous allons augmenter la distance de transmission, plus nous allons perdre en vitesse. Une solution consisterait à compresser les données. Ainsi, si notre image nécessite 6 secondes de transmission et que nous compressons les données pour qu'elles n'occupent plus que la moitié, nous pourrons transmettre en 3 secondes. Sauf qu'il va falloir compresser et que cela va prendre du temps. Si la compression prend 4 secondes cela ne vaut plus la peine, car au lieu de 6 secondes de transmission de données non-compressées, nous aurons 4 secondes de compression plus 3 secondes de transmission soit un total de 7 secondes. Cela vaut donc la peine à condition que la compression prenne moins de temps que le gain de temps de transmission. Et plus la compression sera rapide et plus cela sera intéressant. Nous avons ici un exemple d'application de l'assembleur.
Mais pour tout le reste de notre développement, autant utiliser le C car le développement est plus rapide (on tapes moins de code).
En clair, la solution serait de mélanger le C et l'assembleur: on utilise le C pour le corps de notre programme et l'assembleur pour les parties nécessitant un code plus compact et/ou plus rapide.

Comment mélanger?

Pour mélanger le code C et le code ASM, il existe 3 solutions.
La première consiste à utiliser du code 'inline'. Pour cela on tape le code assembleur en plein milieu du code C, en ajoutant des informations pour que le compilateur sache que c'est de l'assembleur. Vous trouverez ici un document expliquant comment faire.
https://web.stanford.edu/class/ee281/projects/aut2002/yingzong-mouse/media/GCCAVRInlAsmCB.pdf
Le problème c'est que la syntaxe de l'assembleur n'est pas forcément très claire et là, en devant ajouter des guillemets, des retours chariots etc... pour se conformer à cette syntaxe un peu spéciale, cela devient encore moins clair. En plus nous tapons en plein milieu de notre code en C, ce qui veut dire que nous ne pouvons pas exécuter notre code Assembleur tout seul, par exemple avec AVRA.

La seconde solution consiste à s'insérer dans le processus de création de l'exécutable, au niveau du linker. C'est la solution la plus communément utilisée. Lorsque vous compilez votre code C, cela ne génère pas tout de suite un fichier exécutable. Il y a génération d'un fichier intermédiaire, un fichier (objet) dont l'extension est généralement ".o" ou ".obj". Tous les fichiers C de votre projet vont donc donner des fichiers objets et c'est un autre outil, le "linker" qui va lier ces fichiers entre eux pour générer le fichier exécutable. Sachant que de son côté l'assembleur génère lui aussi ces fichiers intermédiaires, on a donc là une solution apparement simple: le compilateur C produit des fichiers objets, l'assembleur produit des fichiers objets, nous fournissons tous ces fichiers objets au linker qui va tout coller ensemble et nous produire notre exécutable.


Le contenu du dossier après assemblage avec AVRA

En théorie, c'est une excellente idée. Le problème c'est que les fichiers objets contiennent des références les uns aux autres. En effet, un fichier objet peut contenir des fonctions qui sont appelées à partir du code contenu dans un autre fichier objet. A la compilation, chaque fichier C donc chaque fichier objet pourra paraître bon, et c'est le linker qui découvrira que telle fonction appelé dans le code d'un fichier n'existe en fait dans aucun des autres fichiers du projet. Mais pour découvrir cela et coller correctement ensemble les fichers .o encore faut-il qu'ils soient reconnus par le linker.
Le problème c'est qu'apparement le compilateur GCC utilisé par l'Ide Arduino, produit des fichiers objets dans un format différent de ceux produits par l'assembleur AVRA. On ne peut donc pas tout fournir au "linker"...

La vérité est ailleurs

Une recherche dans les dossiers de nos projets soulève une interrogation: où sont les fichiers intermédiaires donc les fichiers "objet" de nos projets Arduino?
De plus, tout le monde affirme sur les différents forums que l'Arduino se programme en C++. Mais dans ce cas, pourquoi nos fichiers ne portent pas l'extension ".cpp" mais une extension ".ino"?
La réponse à ses deux interrogations va nous permettre de comprendre quelques détails dans la gestion des projets et va nous permettre de mettre au point une troisème méthode pour mélanger, cette fois-ci très facilement, du C et de l'assembleur!
Lorsque l'on développe avec XCode ou Android Studio, chaque projet se compose d'un grand nombre de dossiers et de fichiers. Or, si nous commençons à déplacer ou à renommer ces fichiers ou ces dossiers, plus rien ne fonctionne.

L'Ide Arduino ne possède pas un tel sysème de projet. Son système est beaucoup plus simple mais quand même assez "tordu": quand on crée un projet sur Arduino, ce projet doit être mis dans un dossier et le fichier ".ino" doit avoir le même nom que ce dossier. On peut renommer le dossier mais dans ce cas on doit aussi renomer le fichier pour que les deux aient le même nom.
Or le fichier ".ino" que vous générez, n'est pas un totalement un fichier C++. Il y ressemble beaucoup mais il lui manque quelques informations qui font que ce fichier n'est pas utilisable directement par GCC, le compilateur. Quand vous cliquez sur "Téléverser", l'Ide Arduino ouvre le dossier du projet et y cherche un fichier ".ino" avec le même nom que le dossier. Il charge ce fichier .ino et en fait une copie qu'il modifie en y ajoutant les éléments manquants, ceci afin d'en faire un vrai fichier C++. Il sauve ensuite cette copie en y ajoutant l'extension ".cpp". Sur Mac OSX, ce fichier est sauvé dans le compte utilisateur, dans Documents/Arduino/sketch. Si votre sketch se nomme "essai.ino", ce nouveau fichier s'appelera "essai.ino.cpp"
Si vous jetez un oeil dans le dossier Documents/Arduino vous y verrez aussi un dossier "core" avec tout un tas de fichiers ".o" pour les fonctions de base de l'Arduino.

Mais stop! Nous avons raté un détail. Regardez le fichier ci-dessous. C'est un bout de code tout simple. Une fonction setup et une fonction loop qui appelle une fonction blink, le tout dans un seul fichier. Créez un dossier et déposez ce fichier en lui donnant le même nom que le dossier, par exemple "tst_blink.ino" dans le dossier "tst_blink". Téléversez: la Led clignote, rien de bizarre. Maintenant, sélectionnez la fonction "blink" qui est en bas de ce fichier, découpez là et sauvez là dans un autre fichier que vous appelerez par exemple "bloc.ino" et que vous sauverez dans le même dossier.

Vous aurez donc un dossier "tst_blink" avec deux fichiers: "tst_blink.ino" et "bloc.ino". Cliquez dans l'éditeur pour téléverser "tst_blink.ino". Suprise, ça continue à fonctionner tout pareil... Ouvrez maintenant le dossier Documents/Arduino/sketch. Vous y verrez "tst_blink.ino.cpp" donc le fichier qui a été dupliqué par l'ide Arduino. Mais ouvrez le avec un éditeur de texte: surprise! Il contient à la fois les données de "tst_blink.ino" et celle de "bloc.ino".
En fait, l'Ide Arduino se repère sur le nom du dossier, prend le fichier qui à la même nom, le charge puis y recopie tous les autres fichiers ".ino" qu'il trouve dans le même  dossier.
C'est ce qui explique que si vous avez des anciennes versions de votre code dans le dossier de travail, plus rien ne marche car l'Arduino incorporera tout dans le code C++ qu'il enverra à GCC pour la compilation.

La solution

En réfléchissant un peu, nous tenons notre solution! Nous allons créer un dossier de projet, y mettre notre code C dans un fichier .ino avec le même nom que ce dossier. Nous allons ensuite créer un fichier .asm dans lequel nous allons taper notre code assembleur au format AVRA. Nous allons pouvoir l'assembler avec AVRA, le modifier etc... Ce fichier nous le sauverons lui aussi dans le dossier de notre projet, mais avec un nom différent. Nous aurons par exemple un dossier "essai", un fichier "essai.ino" et un fichier "bloc.asm". Nous allons ensuite programmer un module AppleScript pour TextWrangler (ou BBEdit, c'est pareil). Lorsque nous déclencherons ce module il chargera "bloc.asm", le transformera pour le mettre au format "inline", puis le sauvera sous le nom "bloc.ino" dans le dossier. Ensuite notre script appelera l'ide Arduino. Celui-ci chargera "essai.ino" y incorporera "bloc.ino" et enverra le résultat à GCC!

Le Script

Voici le code AppleScript. Pour l'installer, reportez vous à l'article sur l'utilisation de TextWrangler comme éditeur pour l'Arduino (ici). Le principe d'installation est identique.



Attention: il faut régler les préférences de l'Editeur AppleScript. Cliquez dans le menu "Editeur de script", option "Préférences" puis sur l'onglet "Modifications" et cochez "Tabulations et sauts de ligne dans les chaînes". Ceci permet de faire des scripts qui écrivent des tabulations et des sauts de lignes sans que ceux-ci ne soient "éxécutées" dans l'éditeur.


Faisons un petit tour dans ce script, bien que la masse de commentaires devraient vous permettre de vous en sortir. On commence par prélever le texte, soit la zone sélectionnée, soit tout. Puis on va dans la fonction de génération du code ASM. On remplace les tabulations et les doubles espaces dans tout le texte, puis on analyse celui-ci, ligne par ligne. On supprime les commentaires et on nettoie les espaces de fin de ligne. Puis nous regardons si cette ligne contient un ".EQU" c'est-à-dire l'équivalent d'un #define en Assembleur. Si c'est le cas nous en prélevons le nom et la valeur.
Nous allons ensuite regarder si nous devons déposer le code ASM ou non. J'ai en effet trouvé intéressant de ne pas forcément tout "traduire" en code Inline. Afin de délimiter la ou les zones de code à "traduire", on doit simplement ajouter "_start_asm:" pour marquer le début d'une zone et "_end_asm:" pour en marquer la fin. On peut ajouter autant de start et de end que l'on veut mais ils ne doivent évidement pas être imbriqués.
Nous faisons ensuite un chercher-remplacer des éventuels ".equ" et nous ajoutons la chaine au code final.
Nous allons ensuite sauver le fichier (subroutine save_asm). Nous prenons le chemin complet du fichier, nous changeons l'extension pour mettre ".ino" et nous sauvons le code généré. Ensuite, nous prenons le nom du dernier dossier du chemin: puisque c'est le nom du dossier du projet, c'est donc aussi le nom du fichier .ino principal! Nous construisons donc le chemin pour pointer vers ce fichier .ino principal (qui est donc notre fichier en C, avec setup() et loop() et nous demandons à l'utilisateur s'il veut qu'on déclenche l'ide Arduino pour "Téléverser". S'il accepte, on lance l'Ide, qui va donc prendre le fichier C ".ino" et va le traiter en y incorporant notre code ASM.



Assez souvent le premier déclenchement de l'Ide Arduino ouvre celui-ci, mais il ne téléverse rien. Mais dès le second déclenchement, ça marche.

Une fois ce script installé dans le dossier avec les autres scripts pour TextWrangler, il apparaîtra dans le menu avec l'icône en forme de parchemin avec le nom sous lequel vous avez sauvé le script (pour moi c'est "Arduino ASM+C.scpt")



Deux petits détails: en haut du script se trouve "tell application "TextWrangler"". Si vous utilisez BBEDit, remplacer par "tell application "BBEdit"". Tout en bas du code se trouve "set MyArduinoPath to quoted form of POSIX path" avec le chemin vers l'Ide Arduino. En ce qui me concerne l'Ide est dans un dossier "Dev" ce qui explique ce chemin. A vous de modifier suivant votre configuration.

Un exemple

Mettons notre méthode en pratique avec un bout de code C qui va appeler une fonction en Assembleur pour faire un simple "blink".
Dossier: c_asm_blink" dans lequel je met le fichier c_asm_blink.ino ci-dessous
C'est un fichier C classique, avec notre setup() et notre boucle loop(). Dans celle-ci nous appelons notre fonction ams_blink qui sera mise dans notre fichier assembleur.
cet appel se fait avec une ligne d'assembleur inline avec l'instrution "call".
Donc asm ("call asm_blink");



Ensuite je crée un fichier blink.asm dans lequel je met le code ci-dessous.
C'est un fichier assembleur au format AVRA, qui commence par des commentaires puis des .equ et ensuite le code. Au départ nous réglons la PIN du Led en sortie. Comme ceci ne nous interesse pas, nous mettons le tag "_start_asm:" plus bas afin que seule la fin du fichier soit convertie (Note: si nous ne mettons pas de tag "_end_asm:" le conversion se fait jusqu'à la fin)
Je trouve bien ici le label "asm_blink:" qui permettra à ma fonction "loop()" de mon fichier C d'appeller ce code assembleur.

Tout ceci étant en place, je lance mon script "Arduino ASM+C" via le menu avec le petit parchemin. Une alerte me demande si je veux lancer l'Ide Arduino. Je répond oui, l'Ide s'ouvre, charge le fichier c_asm_blink/c_asm_blink.ino, y incorpore blink.ino, envoi tout ça à GCC qui compile puis passe ça au linker. Le résultat est envoyé à la carte et.... ça blink!
Par curiosité, ouvrons le dossier c_asm_blink. Nous y voyons notre fichier C, notre fichier ASM mais aussi le fichier que le script à généré donc "blink.ino". En l"ouvrant nous y voyons le code assembleur au format Inline, code issu du traitement de notre fichier assembleur d'origine.
Si nous sommes encore plus curieux, nous pouvons allez voir dans le dossier Documents/Arduino/sketch. Nous y verrons le fichier c_asm_blink.ino.cpp et en l'ouvrant nous verrons qu'il contient à la fois le code C et le code assembleur.

Un deuxième exemple

Dans ce premier exemple nous avons appelé un bout de code assembleur qui travaillait tout seul, sans aucun lien avec le C (sauf l'appel). Dans ce second exemple, nous allons envoyer à notre fonction assembleur un mot écrit en minuscule. Le code assembleur va le mettre en majuscule et au retour le code C va l'afficher.

Le principe est le même: dossier "c_asm_string" dans lequel nous mettons notre code "c_asm_string.ino", ci-dessous.

Définition de notre chaîne, puis setup() avec réglage de la vitesse du port série puis affichage du mot avec un classique Serial.println(chaine);
Nous appelons ensuite notre fonction assembleur, qui se nomme "uppercase". L'appel se fait avec:
    asm("call uppercase"::"z"(chaine));
Le mot "asm" au début indique que c'est du code assembleur. Nous avons ensuite l'instruction d'appel ("call uppercase"), puis nous avons des ":". Dans un prochain article nous verrons cela plus en détal mais sachez que cela sert à passer des valeurs au code assembleur. Dans le cas présent, nous avons "z"(chaine) ce qui signifie que nous allons mettre l'adresse de notre chaine dans le registre z qui est un registre que le processeur utilise pour gérer les adresses.
En retour l'assembleur aura modifié la chaine que nous allons à nouveau afficher et qui, cette fois, sera en majuscule.

Nous créons maintenant notre fichier "uppercase.asm" que nous mettons dans le dossier "c_asm_string". Le code est très simple: nous sauvons le registre r16 sur la pile, pour le protéger. Puis nous mettons dans r16 un octet que l'on trouve à l'adresse z. Donc ici, c'est le premier caractère de notre chaine. Nous testons pour voir si c'est l'octet de fin (qui vaut 0). Si oui, nous sautons au label "exit" auquel nous récupérons la valeur de r16 que nous avions sauvé, et nous retournons au C (ret = return). Si r16 ne vaut pas 0, nous lui soustrayons 0x20 (soit 32 en décimal). Comme le code ascii d'une lettre minuscule est de 32 plus haut que celui de son équivalent en majuscule, en retirant 32 au code ascii de nos lettres minuscules nous les passons en majuscule. Nous remettons ensuite r16 dans le registre z en incrémentant celui-ci, ce qui le fait pointer sur le caractère suivant. Nous bouclons ensuite au label "loop:" et ainsi de suite jusqu'à la fin de la chaîne.

Quand nous sommes à la fin, nous sortons donc nous revenons dans le C qui affiche à nouveau la chaîne avec     Serial.println(chaine); montrant désormais la chaine en majuscule.

Comme tout à l'heure, il suffit de déclencher le script via le menu de TextWrangler pour que le .asm soit mis en .o et que tout soit compilé et téléversé.

 

Conclusion

Mélanger du C et de l'Assembleur devient ainsi beaucoup plus facile. Soit vous ne tapez que le code nécessaire (comme nous l'avons fait avec uppercase.asm) c'est-à-dire que vous ne pourrez sans doute pas l'assembler avec AVRA, soit vous tapez tout le code ASM avec les init, les EQU etc... ce qui vous permettra de l'assembler avec AVRA et de le tester. Dans ce cas, l'usage des tags "_start_asm:" et "_end_asm:" vous permettra de sélectionner les blocs à traduire en "inline".

mercredi 20 décembre 2017

HC-06 à 115200 Bauds entre Arduino et Android

Il existe de très nombreux articles sur internet, relatifs aux modules HC-05 et HC-06. Tous les exemples fonctionnent ou du moins semblent fonctionner. Tant que vous vous contentez d'allumer une Led ou de réagir à l'appui sur un petit bouton, ça va. Quand vous commencez à vouloir paramétrer les modules, augmenter la vitesse, transmettre pas mal de données, les problèmes commencent à arriver.

Pire, la réalisation d'un bon système de communication nécessitant tout un tas de réglages, il n'y a généralement pas un point qui ne marche pas, mais au contraire une succession de micro détails plus ou moins défaillants, sans nécessairement de lien les uns avec les autres.
Ayant eu à réaliser une application avec communication rapide et obligation de haute fiabilité entre un système de type Arduino et des applications Android, j'ai été obligé de chercher ce qui se passait afin de résoudre tout ces petits détails.
Cet article va donc tenter de vous aider, non pas en vous donnant du code tout fait, mais en vous donnant des explications. Ainsi, si d'autres problèmes surviennent, vous saurez comment analyser et vous saurez certainement les résoudre.

HC-05/ HC06

Ces sont les deux modules les plus fréquemment utilisés sur Arduino. On les trouve facilement en boutique et ils ne sont pas très chers. Ils ont plusieurs différences, certaines évidentes, d'autres beaucoup moins.

iPhone

Le premier point c'est que ces modules ne sont pas compatibles avec les iPhone. Si votre application doit communiquer avec un appareil Apple, il vous faut (à priori) un module BLE (Bluetooth Low Energy) donc par exemple un module HC-08.

Esclave vs maitre-esclave

Le HC-05 est un module maitre-esclave alors que le HC-06 est un module exclusivement esclave. Un module maitre est capable de détecter les autres modules et de leur demander l'autorisation de communiquer. Un module seulement esclave ne peut que répondre aux demandes des modules maitres.
Votre smartphone a un module maitre-esclave: il peut détecter les autres modules (donc il peut fonctionner en maitre) et peut aussi être détecté par les autres (donc il peut fonctionner en esclave).
Si vous voulez faire un système Arduino qui reçoit ou envoi des données depuis un smartphone, prenez un module HC-06 donc seulement esclave puisque la détection du module se fera sur le smartphone.
Par contre si vous voulez faire communiquer deux Arduino ensemble, l'un des deux devra avoir un HC-05 (cet Arduino sera alors l'initiateur de la connexion puisqu'il aura un module maitre esclave) et un HC-06 (sur l'Arduino qui sera esclave).

Distance de communication

Nous parlons ici de modules Bluetooth. La communication se fait sur quelques mètres, pas plus. Si vous voulez communiquer sur une plus grande distance, jetez un oeil du côté des modules Xbee, ou bien du côté du module HC-12. Concernant le HC-12, celui-ci ressemble beaucoup aux modules HC-05 et HC-06 (au niveau visuel et au niveau du nom!) mais, contrairement à ce que l'on trouve indiqué sur certaines boutiques, ce n'est pas un module Bluetooth! Il ne sera donc pas visible par votre Smartphone, ni pas les HC-05. pour faire communiquer un Arduino équipé d'un HC-12 avec un appareil Android situé par exemple à 500m, il vous faudra l'Arduino distant, mais également un "pont", proche de votre Android, pont qui aura un HC-12 et un HC-06 (pour faire le relais entre le HC-12 distant et le Bluetooth).

Paramètrage

Les HC-05 et HC-06 sont paramétrables avec des commandes Hayes, également appelées commandes AT. Les HC-05 et HC-06 ne reconnaissent pas exactement les mêmes commandes mais surtout, ne les reçoivent pas de la même manière.

Les commandes AT

Elles sont composées de lettres et commençent toutes par "AT" donc A majuscule et T majuscule. On envoi ces commandes aux modules avec un appel à Serial.print() et on lit le résultat avec un classique Serial.available() suivi d'un Serial.read().

Sauf qu'il y a un petit détail...
Intéressons nous à 2 commandes afin de comprendre:
  • La commande "AT" permet de savoir si le module est présent. Si oui, il répondra "OK"
  • La commande "AT+NAMEXXXXX" permet de changer le nom du module, XXXXX représentant le nouveau nom. C'est ce nom qui apparait sur votre Smartphone quand vous demandez la liste des appareils Bluetooth.

Maintenant imaginons que je veuille renommer mon module "PATRICK". Je vais donc lui envoyer AT+NAMEPATRICK. Le problème c'est que dans "PATRICK" il y a "AT". La question est donc de savoir si le module va croire que j'envoi "AT+NAMEPATRICK" ou s'il va croire que j'envoi "AT+NAMEP" puis "AT" donc deux commandes. Dans le premier cas mon module sera bien renommé "PATRICK" mais dans le second il sera simplement renommé "P".

Dans le cas du HC-05, les commandes "AT" doivent se terminer avec un retour à la ligne. On pourra donc les envoyer avec Serial.println() ce qui rajoutera le retour à la ligne en fin de commande. Ce "retour à la ligne" c'est donc la solution pour séparer les commandes. Avec le HC-05 il n'y a donc pas de risque d'avoir le module nommé "P" à la place de "PATRICK".
Mais dans le cas du HC-06, ça se complique car il n'y a pas de code de séparation! En fait, c'est le temps qui s'écoule entre la réception de chaque caractère qui fait que le HC-06 décide que la commande est finie ou non... D'après la Datasheet du HC-06, ce temps limite est de 1 sec.
En clair, si vous écrivez:

Serial.print("AT+NAMEPATRICK");
Serial.print("AT");

votre HC-06 se nommera "PATRICKAT" car vous n'avez pas laissé de temps entre la première et la seconde commande donc le HC-06 va considérer que c'est une seule et même commande.
Ce qu'il faut c'est donc laisser un peu de temps. Pour ma part, je laisse 2 secondes pour être plus sûr.
Donc:

Serial.print("AT+NAMEPATRICK");
delay(2000);
Serial.print("AT");

Là, votre module sera nommé "PATRICK" et la seconde commande vous retournera "OK".

Le port Série

Certains articles vous proposent de brancher votre HC-06 et de la paramétrer en tapant les commandes sur le port série. Sauf que là encore des fois ça marche, des fois pas. J'ai ainsi trouvé un exemple qui lisait les données du Terminal Serie, qui renvoyait pour test ces caractères vers le Terminal et les envoyait aussi vers le HC-06 puis qui affichait sur le Terminal ce qui était reçu du HC-06. Le tout à 9600 bauds donc lentement.

Le résultat était étrange: quand on envoyait "AT+VERSION" à partir du Terminal, donc un ordre censé retourné le numéro de version du module, on voyait "AT+OVKERSION" s'afficher sur le Terminal! Là encore, problème de vitesse: la transmission étant lente, dès que le module avait reçu "AT", comme il s'écoulait du temps entre les envois, le module ne voyait rien d'autre arriver et déduisait que la commande est finie.  Il avait donc reçu "AT", et répondait "OK". Tout se faisant lentement, on avait le temps d'envoyer le "+" puis on recevait le "O" de "OK", on envoyait le "V" de "VERSION", puis on recevait le "K" de "OK" etc... Tout était mélangé.
Comment faire? d'abord il ne faut pas oublier de mettre votre Terminal en mode "pas de fin de fin de ligne" (pour le HC-06), ensuite il ne faut pas faire de Serial.print() vers le Terminal ou alors il faut faire ça rapidement. Mais franchement, la meilleure solution c'est de paramétrer votre module avec un sketch et pas avec le Terminal!

Software Serial; la mauvaise bonne idée

Si vous travaillez avec un Arduino UNO, vous savez que celui-ci ne comporte qu'une liaison série, sur ses broches 0 et 1. Or ces broches sont utilisées pour recevoir le sketch et pour communiquer avec le Terminal. Malheureusement les modules HC-05 et HC-06 ont besoin d'un port série pour communiquer.
La solution préconisée dans la quasi-totalité des articles, consiste à utiliser la librairie Software Serial. Cette librairie va créer un (ou plusieurs) port série "virtuel" sur votre Arduino. On l'initialise en lui indiquant la connexion (pour notre TX et notre RX virtuel) puis on l'utilise avec print, write etc... Donc:

#include <SoftwareSerial.h>
SoftwareSerial mySerial(10, 11); // Nouveau port série sur PIN 10 et 11
mySerial.begin(9600);
mySerial.println("Coucou");

Le problème c'est que ce tour de force qui consiste à "virtualiser" le port série, est fait par logiciel. Or l'Arduino est une bien petite machine, avec une puissance très limitée. Le résultat c'est que Software Serial n'est utilisable que pour de faibles vitesses. 38400 Bauds est la vitesse maximale que l'on peut compter atteindre (et encore, à cette vitesse j'ai eu parfois des phénomènes un peu étrange). Au delà, donc à 57600 ou à  115200 Bauds, vous allez avoir des pertes de données, avec des octets qui vont arriver avec des valeurs fausses.

Il y a principalement trois options:
  1. Utiliser Software Serial en limitant la vitesse. Si par exemple vous n'avez que quelques données à transmettre, une vitesse même aussi faible que 9600 Bauds peut largement suffire.
  2. Utiliser Software Serial pendant le développement, puis en supprimer l'usage quand tout est prêt. Cette solution est simple mais possède deux inconvénients. En premier il va falloir modifier votre code. Si en théorie c'est facile, l'expérience démontre que le risque de produire des dysfonctionnements est toujours réel. En second il va falloir régler la vitesse de votre module HC pour le développement, puis le régler à nouveau quand ce développement sera terminé. Et si vous trouvez des bugs, il faudra à nouveau changer le code pour réactiver Software Serial et donc rechanger la vitesse du module.
  3. Ne pas utiliser Software Serial. Pour ma part c'est la solution que j'utilise. Cela demande de bien relire son code pour éviter les bugs et de travailler "en aveugle" ou alors si vous disposez de PIN libres, utiliser un petit écran OLED ou LCD pour afficher des petits messages pour vous aider à la mise au point.

Réglage de vitesse

Le réglage de la vitesse des modules se fait avec la commandes "AT+BAUDX" avec X pouvant prendre les valeurs suivantes:
1 -> 1200 Bauds
2 -> 2400 Bauds
3 -> 4800 Bauds
4 -> 9600 Bauds
5 -> 19200 Bauds
6 -> 38400 Bauds
7 -> 57600 Bauds
8 -> 115200 Bauds

Pour passer à 115200 Bauds, on fait donc:

Serial.print("AT+BAUD8");

Changer le code PIN

Le changement du code PIN se fait lui aussi avec une commande AT. Ce code c'est celui que vous taperez sur votre Smartphone pour valider la connexion avec le module.
La commande est "AT+PINXXXX" ou XXXX est une valeur de 4 chiffres. Donc par exemple:

Serial.print("AT+PIN1212");

L'ordre des commandes

Nous savons maintenant comment envoyer des commandes pour changer la vitesse, changer le code PIN et le nom et nous savons que, sur notre HC-06, il faut envoyer chaque commande en une fois pour ne pas avoir d'écart de temps entre les caractères et ensuite attendre 2 secondes (1 en théorie) avant d'envoyer la commande suivante.
Nous allons donc brancher notre HC-06 directement sur RX et TX de l'Arduino car nous voulons travailler à grande vitesse.

Note: sachant que l'Ide Arduino communique avec la plaque via les PIN RX et TX, quand vous envoyer votre sketch, vous devez les déconnecter.

Nous préparons donc notre code et nous l'envoyons:

Serial.begin(9600);
Serial.print("AT+PIN1212");
delay(2000);
Serial.print("AT+BAUD8");
delay(2000);
Serial.print("AT+NAMEMARCEL");
delay(2000);

La compilation se passe bien l'envoi également, tout semble OK. Nous ouvrons notre Smarphone, Android, nous allons dans les préférences Bluetooth et là, surprise, le module est bien visible mais il n'a pas changé de nom. Par contre, quand on veut se connecter sur ce module, on se rend compte que le code PIN a changé et qu'il faut maintenant taper "1212".

Réfléchissons: au départ notre HC-06 est à 9600 Bauds, ce qui est son réglage d'usine. Nous initialisons donc notre port série à 9600 bauds avec Serial.begin(9600);
Ensuite nous envoyons la commande qui change le code PIN. Ayant été paramétré à 9600 Bauds, c'est à cette vitesse que l'Arduino envoi cette commande. Et comme notre HC est aussi réglé à 9600 Bauds, il va comprendre la commande et le code PIN va être modifié.
Ensuite nous envoyons la commande qui change la vitesse. Là encore, nous envoyons cette commande à 9600 Bauds et elle est reçue à 9600 Bauds. Elle est donc comprise par le HC, qui l'exécute.
Sauf qu'à partir de cet instant, notre HC n'est plus réglé à 9600 Bauds, mais à 115200. Or notre Arduino va continuer à travailler à 9600 Bauds et va donc envoyer la commande de changement de nom à une vitesse différente de celle à laquelle le HC attend les données.
La commande ne va donc pas être comprise par le HC et le nom du module ne sera donc pas changé.

Note: pour comprendre ce qui se passe, il vous suffit de débrancher les PIN RX et TX et de faire Serial.print ("Coucou"); Le résultat s'affichera sur le Terminal Serie. Recommencez plusieurs fois en réglant le Terminal Serie à différentes vitesses et vous verrez que la communication n'est bonne que lorsque l'émetteur et le récepteur sont réglés à la même vitesse.

La solution consiste donc à changer l'ordre des commandes:

Serial.begin(9600);
Serial.print("AT+PIN1212");
delay(2000);
Serial.print("AT+NAMEMARCEL");
delay(2000);
Serial.print("AT+BAUD8");
delay(2000);

Ici nous faisons tous les réglages à 9600 Bauds, le changement de vitesse se faisant en dernier.

Une fois, mais pas deux!

Mais attention, ce bout de code ne va fonctionner qu'une seule fois! En effet, les modules HC mémorisent les réglages et conservent ceux-ci même quand on les éteint! En clair, il suffit d'exécuter ce code une seule fois pour que le module conserve son nouveau nom, son nouveau code PI mais, aussi sa nouvelle vitesse!
Si maintenant vous désirez rechanger le nom et que vous vous contentez de modifier la commande AT+NAME puis que vous relancez le sketch, vous verrez que le module ne changera pas de nom. Pourquoi? Parce que ce bout de code communique à 9600 Bauds et que lors de la première exécution de ce code, vous avez changé la vitesse du module en 115200 Bauds.

En clair, il faut faire deux bouts de code: un code pour la première initialisation, qui communiquera à 9600 Bauds (vitesse d'usine des modules) et un code pour les changements qui communiquera à la vitesse réglée lors de la première initialisation. Par exemple:

// Code pour la 1ère init du module, à 9600 Bauds
Serial.begin(9600);   
Serial.print("AT+PIN1212");        // Change code PIN
delay(2000);                    // Attente pour HC-06
Serial.print("AT+NAMEMARCEL");    // Change le nom
delay(2000);
Serial.print("AT+BAUD8");        // Passe en 115200 Bauds

// Code pour les autres changements, à 115200 Bauds
Serial.begin(115200);            // Car maintenant le module est à 115200   
Serial.print("AT+NAMEROGER");    // Change le nom
delay(2000);

Où placer l'initialisation?

Généralement quand on fait du code sur Arduino, on a une fonction setup() et une fonction loop(). Il semble donc logique de mettre l'initialisation dans le setup(). Sauf que non!
Réfléchissons encore un peu: au premier lancement de notre programme, l'initialisation du module va se faire. Mais comme celui-ci mémorise les réglages, au second lancement du programme, notre initialisation ne va plus rien faire du tout. Nous allons donc utiliser de la place en mémoire pour un bout de code complètement inutile.
La solution consiste en fait à créer deux Sketch. Un qui ne vous servira qu'une seule fois par module, afin de faire les réglages de celui-ci: réglage du port série à 9600 bauds (si c'est la première fois que vous réglez le module), changement de nom, changement de code PIN, changement de vitesse. Et c'est fini.
Ensuite dans le code de votre projet, il vous suffit de régler le port série à la bonne vitesse, mais plus la peine d'envoyer de commande AT pour le nom, etc...

Attention: si par la suite vous désirez changer le nom du module ou son code PIN, il ne faudra pas oublier de modifier le réglage de vitesse dans votre sketch de configuration!

Ecriture et lecture de données

Pour écrire des données vers le module HC, il vous suffit de faire des Serial.print() ou des Serial.write().
Veillez cependant à ne pas trop prendre de temps pour envoyer les données, ceci afin d'éviter qu'une transmission du "PATRICK EST CONTENT" ne fasse croire au HC que les "AT" de "PATRICK" sont une commande Hayes!
Pour la réception, c'est simple:

if (Serial.available() > 0)
  { 
    cmd_char = Serial.read();    // Lecture octet reçu
  }

Côté Android

Passons maintenant du côté Android. Là, il y a deux options:
  1. Vous développez un petit truc pour vous amusez, qui tournera sur votre Smartphone "à vous"
  2. Vous développez une application qui sera utilisée par plusieurs personnes que vous ne connaissez pas forcément
Dans le premier cas, il y a peu de choses à tester car vous connaissez votre matériel. Dans le second cas, c'est un peu plus complexe car pas mal de choses doivent être vérifiées:
  • Le smartphone a-t-il un système Bluetooth?
  • Le Bluetooth est-il activé?
  • Le module de l'Arduino est-il apairé c'est-à-dire a-t-il été identifié et son mot de passe  (code PIN) corectement donné
  • Le module de l'Arduino, s'il n'est pas apairé, est-il visible?
  • Enfin il faut vérifier la transmission. Avec, pour ce dernier point, un petit détail génant: alors que la plupart des fonctions de communication Android possédent une gestion de time-out, ce n'est pas le cas pour le Bluetooth. L'accès est donc bloquant et si en cours de transmission, la connexion est interrompu, tout est bloqué. Il faut donc trouver une "ruse" pour éviter cela, ce qui complique encore un peu les choses.
  • etc...

Quelques bouts de code...

Evidement, ne pas oublier de mettre les autorisations Bluetooth dans votre Manifest:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />

et dans votre code, ne pas oublier d'inclure les librairies:
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;

Ensuite voyons toutes les étapes.

Tester si l'appareil a du Bluetooth

 BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
// S'il n'y a pas de réseau Bluetooth sur ce smartphone...
if (btAdapter == null)
{
    // On prévient et on s'en va
}

Si l'appareil a du Bluetooth, on test si celui-ci est activé

if (!btAdapter.isEnabled())
{
    // Le Bluetooth n'est pas activé. On pourra proposer de l'activer de la
    // façon suivante:
   
    Intent enableAdapter = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableAdapter, 0);

Note: pour des raisons de sécurité, on ne peut pas activer Bluetooth automatiquement. Ceci a pour but d'empêcher une application de l'activer et de se connecter sans accord de l'utilisateur.

On recherche maintenant si l'appareil avec lequel nous voulons communiquer, est déjà couplé avec notre Smartphone. Cela se fait en demandant la liste des appareils puis en bouclant sur cette liste pour chercher cet appareil.

 // Demande la liste des appareils couplés
Set bondedDevices = btAdapter.getBondedDevices();
// Si elle est vide c'est qu'aucun appareil n'est couplé
if (bondedDevices.isEmpty())
{
    // Aucun device couplé...
}
else
{
 // On va lire la liste des devices couplés afin de chercher notre Arduino.
    Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();

    List<String> s = new ArrayList<String>();
    // On boucle sur les appareils
    for (BluetoothDevice bt : pairedDevices)
    {
        // On test si le nom de ce module c'est celui du notre
         if (bt.getName().equals("PATRICK"))
        {
         // On peut se connecter
         // A noter qu'on peut tester le Nom mais aussi l'adresse MAC
         // disponible avec if (bt.getAddress().equals(Adresse_Mac))
}

Si on ne trouve pas notre appareil dans la liste des appareils couplés, il faut le chercher dans la liste des non-couplés afin de pouvoir proposer de le coupler. Cela se fait en deux étapes:

D'abord création de l'adapter et du registerReceiver

BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevice.ACTION_FOUND);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
adapter.startDiscovery();

On doit ensuite déclarer notre mReceiver de la façon suivante (le flag_arduino a été déclaré en boolean et initialisé plus tôt dans le code)

 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action))
            {
                //Début de la recherche des device
            }
            else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action))
            {
                // Fin de la recherche... Si le device n'a pas été trouvé, on l'indique
                if (flag_arduino == false)
                {
                    // petit message et bye bye par exemple
                }
            }
            else if (BluetoothDevice.ACTION_FOUND.equals(action))
            {
                // On a trouvé des devices Bluetooth
                BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(EXTRA_DEVICE);
                String bt_name = device.getName();
                String bt_mac = device.getAddress();
                //Log.i("INFO", "Found device " + device.getName() + " MAC=" + device.getAddress());

                // Nous regardons si le Device est celui que nous voulons
                if (bt_name.equals("PATRICK"))
                {
                    // Un appareil a été trouvé. Il faut aller le coupler
                    flag_arduino = true;   // Flag pour dire qu'on a trouvé
                    // Ici lien vers les préférences Bluetooth.
                    Intent intentOpenBluetoothSettings = new Intent();
       intentOpenBluetoothSettings.setAction(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
        startActivity(intentOpenBluetoothSettings);
                }
            }
        }
    };

Se connecter...

Si on a trouvé notre module et qu'il est apairé, il faut se connecter pour envoyer des données et pour en recevoir. A chaque fois nous allons gérer les exceptions (avec try / catch).

On prépare un socket avec

try
{
// La connection avec createInsecureRfcommSocketToServiceRecord ou avec
// createRfcommSocketToServiceRecord ne marche pas à 100%
// On utilise donc une solution un peu "bizarre" mais qui marche tout le temps...
//socket = bt.createInsecureRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//socket = bt.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
    socket =(BluetoothSocket) bt.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(bt,1);
} catch (Exception e)
{
    // Erreur la connexion a échoué
}

On se connecte ensuite au Socket:

try
{
    socket.connect();
} catch (IOException e)
{
    // Raté, la connexion au seock a échoué
}

Envoyer et recevoir

On déclare l'envoi et la réception si nécessaire

try
{
    outputStream = socket.getOutputStream();
} catch (IOException e)
{
    // Erreur on arrive pas à déclarer la sortie donc l'envoi
}

// Déclare la réception
try
{
    inStream = socket.getInputStream();
} catch (IOException e)
{
    // Erreur on arrive pas à déclarer la réception
}

Et on prépare un flux d'entrée
DataInputStream dinput = new DataInputStream(inStream);

Si on doit envoyer quelque chose:

String code = "Ce qui va être envoyé";
 try
{
    outputStream.write(code.getBytes());
} catch (IOException e)
{
   // Erreur, l'envoi n'a pas été effectué
}

Si on doit recevoir. Là, c'est un peu plus compliqué à cause de l'absence de Time-Out. Nous allons donc créer notre propre Time-Out, avec une simple petite boucle.

// Buffer générale pour les datas
int BUFFER_SIZE = 5000;
byte[] main_buffer = new byte[BUFFER_SIZE];

// Data pour le time out
// Principe: on demande s'il a des bytes disponibles
// Si non, on attend un tout petit peu puis on redemande
// On note le temps global qu'on a attendu sans rien recevoir
// Si ça atteint 8 sec on estime qu'on ne va rien recevoir
// donc bye bye...
int timeOut = 8000; // 8 secondes
int currTime = 0;    // temps global qu'on a attendu
int interval = 50;    // Ecart de temps entre les demandes
stopWorker = 0;     // Flag pour ne pas sortir
main_buffer_idx = 0;
main_buffer_old_idx = 0;

// Tant qu'on a des data, on va les lire...
// Ceci dépend évidement de ce que vous voulez recevoir. Il est par
// exemple intéressant de définir combien de données vous allez recevoir.
// Soit en commençant l'envoi par une valeur donnant le nombre d'octet qui suivent
// soit en prévoyant un caracrère de fin qui sera testé à chaque lecture
// Dans cet exemple, mes données commencent et finissent par "*"
// Donc quand je reçois "*" et que je ne suis pas en train de recevoir
// le 1er caractère, c'est donc que c'est le dernier, ce qu me fait sortir
// de ma boucle.
while (stopWorker == 0)
{
    try
    {
        // Demande combien de byte il y a...
        int bytesAvailable = dinput.available();

        // S'il y en a, on va les lire
        if (bytesAvailable > 0)
        {
            currTime = 0;   // On reçoit donc remet compteur time out à 0

            // Petit buffer de réception pour ce paquet qu'on a reçu
            byte[] packetBytes = new byte[bytesAvailable];
            dinput.read(packetBytes);   // On lit ce paquet

            // On recopie ce paquet dans notre gros buffer
            for (int i = 0; i < bytesAvailable; i++)
            {
                byte b = packetBytes[i];

                // Si ce byte n'est pas le premier et qu'il vaut "*"
                // c'est donc le code de fin. Dans ce cas, on sort
                // de la boucle et on va traiter les données
                if (main_buffer_idx > 0 && b == 42)
                {
                    stopWorker = 1; // Sortie par fin de data
                }
                // On recopie ce byte reçu dans notre gros buffer principal
                // Il aura donc aussi bien le * de début que celui de fin
                main_buffer[main_buffer_idx] = b;
                main_buffer_idx++;  // Sinon passe au suivant
            }
        }
        // Il n'y a pas d'octets à arriver. On va donc regarder
        // si on a atteint le Time out
        else if (currTime < timeOut)
        {
            // Si le time out est pas atteint, on attend un peu
            // La durée est dans interval qui ici vaut 50 millisecondes
            try {
                Thread.sleep(interval);
            } catch (InterruptedException e) {
                // ...
                // exception handling code
            }
            // Et on compte ce temps...
            currTime += interval;   // On augmente le compteur de timer out
        }
        else
        {
           
            // Timeout. On réinit, on attend encore, on alerte etc...
            // C'est comme vous voulez...
           
        }

        ;
    } catch (IOException e)
    {
        // Si la lecture rate, on s'en va
       
    }
}

Et enfin, ne pas oublier de fermer la connexion!

try
{
    socket.close();
} catch (IOException e)
{
    // Echec de la fermeture de la connexion
}

Ouf! Rien de bien compliqué pour la partie Android, mais un multitude de petits cas avec plein de trucs qui peuvent dépendre de l'utilisateur.

Conclusion

La gestion du Bluetooth entre un Arduino et un appareil Android est l'exemple typique de la partie programmation "pénible": rien de compliqué mais plein de petits détails, de choses à faire dans un certain ordre, qui marche une fois mais pas deux etc... J'espère qu'avec cet article vous arriverez à vous débrouiller. En tout cas, en suivant ces conseils vous devriez obtenir une communication fiable à 115200 Bauds.

lundi 11 décembre 2017

Assembleur pour Arduino - Mac OSX

Lorsque l'on cherche à programmer sur "Arduino" en assembleur sur Mac, on se retrouve assez rapidement perdu dans tout un tas d'installations, plus compliquées les unes que les autres. Pourtant il ne faut pas plus de 10 min pour tout installer et réaliser son premier programme. Etes-vous OK? Alors allons-y les Lapinots!

Note: l'installation décrite ici tourne sur un Mac Mini avec  macOS High Sierra 10.13.2 et TextWrangler 5.5.1

Les outils

Pour développer en assembleur et réaliser des programmes pour l'Arduino, il nous faut 4 choses:
  • Un Arduino ("Là Tonton Lapin, tu nous étonnes!") qui va recevoir le programme que nous allons réaliser et qui va l'exécuter. Nous utiliserons ici un Arduino UNO, mais peu importe le modèle.
  • Un programmeur, c'est-à-dire un logiciel qui va envoyer notre progamme à l'Arduino. En fait un outil qui va faire la fonction "Téléverser" de l'Ide Arduino. Nous utiliserons AVRDUDE.
  • Un assembleur pour assembler le code c'est-à-dire transformer ce que nous allons taper dans notre éditeur de texte, en fichier exécutable. Nous utiliserons AVRA.
  • Un éditeur de texte pour taper notre code. Nous utiliserons TextWrangler. Ce choix s'explique par le fait que c'est un éditeur de texte simple, très popularie sur Mac, gratuit et qui posséde la capacité de gérer des modules externes en AppleScript, ce qui nous sera bien utile ici! Si vous préférez utiliser BBEdit, le grand frère de TextWrangler, cela ne pose aucun problème, ce qui est décrit ici fonctionne aussi bien sur ces deux logiciels.

Installation

Installons notre assembleur, notre programmeur et l'éditeur de texte. C'est l'affaire de quelques minutes.

Installer AVRA et AVRDUDE

Ouvrez le Terminal de votre Mac (Applications/Utilitaires/Terminal.app) et tapez la commande suivante:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

Cette commande va installer Homebrew. Hombrew c'est un gestionnaire de paquets pour Mac OSX. Grosso modo c'est un outil qui va se charger d'installer des logiciels. L'installation de Homebrew va prendre à peine quelques minutes. Une fois l'installation de Homebrew terminée, nous pouvons nous en servir pour installer AVRA et AVRDUDE.

Pour cela, tapez la ligne suivante dans le Terminal, ce qui va installer AVRA, en quelques dizaines de secondes.
brew install avra

Recommencer la même chose pour installer AVRDUDE en tapant la commande suivante:
 brew install avrdude

Impeccable: vous avez terminé l'installation de l'assembleur qui va créer l'exécutable et le programmeur qui va envoyer les données à l'Arduino.

Installer l'éditeur de texte

Si vous n'avez pas TextWrangler d'installé, aller le chercher sur la page de Barbones, en cliquant ici

Paramétrages

Nous allons paramétrer TextWrangler afin d'activer la coloration de syntaxe pour notre code assembleur puis nous allons le paramétrer pour qu'il lance AVRA et AVRDUDE. Car ce que nous voulons c'est taper le code sous TextWrangler et avec un seul clic dans le menu, assembler ce code et le transmettre à l'Arduino.

Activer la coloration de syntaxe

Cela se fait en plaçant un fichier .plist dans le dossier "Application Support" de TextWrangler.
Pour cela, commençez par ouvrir TextWrangler puis copier-y le code ci-dessous:



Sauvez ce fichier sous le nom AVR.plist puis fermer son édition dans TextWrangler (cliquez sur le petit rond avec une croix en face du nom de ce fichier dans la colonne de gauche de l'éditeur). Ce détail est important: quand le fichier plist est ouvert dans TextWrangler celui-ci ne le prend pas en compte et il n'y a donc pas de coloration de la syntaxe.
Ce fihcier AVR.plist doit être recopié dans "Bibliothéque/Application Support/TextWrangler/Language Module".
Attention, ce n'est pas le dossier Bibliothéque principal, visible quand vous ouvrez le disque de votre Mac, mais le dossier Biblothéque de votre compte Utilisateur.

Note: par défaut, ce dossier est caché. Si vous n'avez pas activé sa visualisation, ici cliquez ici pour savoir comment faire.

Lancer AVRA et AVRDUDE depuis TextWrangler

Une fois que nous aurons tapé notre code, nous allons devoir l'envoyer à AVRA puis à AVRDUDE. Pour cela nous allons créer un petit Script "AppleScript" que nous fournirrons à TextWrangler et auquel il nous donnera accés via son menu.

Dans textWrangler, allez dans le menu juste avant le menu Help donc le menu avec une icône en forme de petit parchemin. Deux cas possibles:

  1. vous avez déjà créé au moins un script pour piloter TextWrangler
  2. vous n'avez jamais utilisé ce menu, donc sous les trois premières entrées, il n'y a rien.

Vous avez déjà au moins un script

Si vous en êtes l'auteur, je suppose que vous savez comment créer un script, donc à vous de jouer. Le script est un peu plus bas dans cet article.
Si vous n'êtes pas l'auteur du ou des scripts actuellement disponibles, la solution la plus simple est la suivante:
  1. Cliquez dans le menu sur "Open Scripts Folder". Dans la fenêtre qui s'ouvre et qui liste tous les scripts, cliquez avec le bouton droit sur un des scripts (.scpt) et choisissez "Dupliquez". Donnez à ce fichier le nom que vous voulez voir apparaître dans le menu (moi je l'ai nommé "Arduino assemble and run.scpt").
  2. Double cliquez sur ce fichier ce qui ouvrira l'éditeur de script de votre Mac. Effacez le contenu du script qui s'affiche collez à la place le script qui est un peu plus bas dans cet article. Une fois que c'est fait, cliquez sur le petit marteau en haut de la fenêtre pour validez le script, puis cliquez sur "Enregistrer" dans le menu "Fichier".

Vous n'avez aucun script

Dans ce cas dans le menu de TextWrangler, cliquez sur "Open Scripting Editor". Dans la fenêtre d'édition, recopiez le code ci-dessous. Cliquez ensuite sur le petit marteau pour valider le script. Ensuite, cliquez dans le menu sur "Enregistrer" pour sauvez le fichier en lui donnant le nom que vous voulez voir apparaître dans le menu (moi je l'ai nommé "Arduino compile and run.scpt"). Le script doit être sauvé dans le dossier Bibliothéque/Application Support/TextWrangler/Scripts donc dans un dossier voisin de celui dans lequel nous avons déposé AVR.plist
Si vous n'arrivez pas à accéder à ce dossier depuis le sélecteur de fichier, sauvez AVR.plist sur votre bureau et ensuite, recopier le manuellement dans le dossier.



Ci-dessous le script coloré aprés avoir cliqué sur le petit marteau.

Analyse et éventuelle modification du Script

Jetons un oeil sur ce script afin de comprendre ce qu'il fait et éventuellement le modifier.
En premier on demande à TextWrangler de sauver le fichier. Puis nous créeons deux chaines de caractères: source_file et exec_file. La première (source_file) va contenir le nom du fichier. Donc si votre fichier se nomme "mon_test.asm", source_file contiendra donc "mon_test.asm". Quand nous enverrons ce fichier à AVRA, celui-ci l'assemblera et produira un fichier exécutable, portant le même nom mais avec ".hex" comme extension.
Donc le source "mon_test.asm" donnera l'exécutable "mon_test.hex" et c'est  "mon_test.hex" que AVRDUDE devra envoyer à l'Arduino. Notre chaîne exec_file va donc contenir le nom du fichier, complété par ".hex" afin d'être utilisée pour l'envoi vers l'Arduino.
Nous sauvons ensuite le chemin actuel pour accéder au fichier, dans la chaine de caractère nommée "p".
Le plus intéressant, c'est la ligne suivante. Nous y définissons la chaîne shell_script qui va contenir les commandes que nous allons envoyer au Terminal.
En fait, nous allons simuler le fait d'ouvrir le Terminal et d'y taper les commandes pour se placer sur le bon répertoire, lancer AVRA puis lancer AVRDUDE.
Deux choses pour comprendre: en AppleScript, la concaténation de chaînes de caractères se fait avec l'opérateur "&". Ensuite lorsque nous avons plusieurs commandes à envoyer au Terminal, nous pouvons toutes les mettre dans la même ligne, mais à condition de les séparer par ";".
Nous avons donc ici 3 commandes dans notre chaîne shell_script:
  1. cd suivi du chemin, qui va régler le chemin d'accés à notre fichier 
  2. avra suivi du nom du fichier source afin que celui-ci soit assemblé
  3. avrdude qui va envoyer le résultat vers l'Arduino.
Si vous désirez ajouter des paramètres lors de l'appel à AVRA, il suffit de les ajouter dans la commande par exemple "avra --max_errors 5" si vous voulez que AVRA sorte au bout de 5 erreurs (au lieu des 10 par défaut). La liste des commandes AVRA est disponible en bas de cette page.
Concernant AVRDUDE,  la liste compléte des options est disponible ici, dans le manuel d'utilisation. Ouvrez la page avec la liste des commandes et analysons celle que nous avons mis dans notre Script.

avrdude -patmega328p -carduino -P/dev/cu.usbmodemFD121 -b115200 -Uflash:w:'" & exec_file & "':i

Dans cette chaîne nous trouvons les éléments suivants
  • Le premier paramètre c'est "-patmega328p". Cela indique le processeur employé. Note: assez bizarrement cette valeur (atmega328p) n'est pas dans la liste des options indiquées sur le site de Avrdude...).
  • Le second paramètre (-c) indique le programmeur que AVRDUDE utilisera en interne. Ici nous utiliserons celui de l'Arduino.
  • Le troisième paramètre (-P) indique le port sur lequel est connecté le hardware destination.  Dans mon cas le port est "/dev/cu.usbmodemFD121". Mais ce n'est pas forcément le cas chez vous... Pour connaître le port utilisé, lancez l'Ide Arduino et ouvrez le menu "Outil". Notez la valeur indiquée dans l'option "Port" et recopiez là dans le script en la faisant précéder de "-P" (sans espace entre le -P et le premier / de la chaîne).

  • La commande -b donne la vitesse de transmission. Ici nous avons 115200 Bauds
  • Ensuite -U va indiquer le type de mémoire destination, comment va être traité cette mémoire, le nom du fichier et son type (-U memtype:op:filename[:format]) . "flash" indique que nous déposons dans la mémoire flash de l'Arduino, "w" que nous allons lire le fichier et l'écrire dans la mémoire. Nous avons ensuite le nom du fichier (donc celui qui est dans exec_file) puis son type. Ici nous mettons "i" qui signifie "Intel Hex" puisque c'est ce format qui a été généré par AVRA.
Nous exécutons ensuite le Script avec la commande "do shell" , ce qui ouvrira le Terminal pour lui envoyer les commandes que préparées dans la chaîne shell_script.

Notre premier programme en Assembleur!

Quittez et relancez TextWrangler pour activer la plist. Ouvrez un nouveau document et copiez-y le code suivant:



Ce code ASM c'est l'équivalent du code "blink" donné en exemple avec l'Ide Arduino. Sauvez ce fichier sous le nom que vous voulez mais avec l'extension ".asm" car c'est avec cette extension que TextWrangler fait le lien avec le fichier plist pour la coloration.

Vérifiez que votre Ardino UNO et bien branché, ouvrez le menu des outils de TextWrangler (avec l'icône en forme de parchemin) et cliquez sur le script que vous avez créé (dans mon cas "Arduino assemble and run"). Cela va ouvrir le Terminal, lancer AVRA donc assembler le code, puis lancer AVRDUDE qui va transmettre le fichier à l'Arduino et, oh miracle, la Led 13 (interne) de votre Arduino va clignoter. AVRA et AVRDUDE indiquerons les opérations réalisées dans la fenêtre du Terminal (erreurs, taille mémoire occupée etc...)


"Houlala, Tonton Lapin, autant de lignes pour faire un blink??" Oui Jeune Lapinot. Il est vrai qu'en utilisant l'exemple Blink, disponible via le menu Fichier>Exemples>01-Basic>Blink, de l'Ide Arduino, nous aurions obtenu le même résultat. Ou du moins, à peu prés. Car si on observe les valeurs affichées lors du transfert des données vers la carte Arduino, on constate une différence: l'exemple Blink de l'Ide Arduino occupe 928 octets dans la mémoire Flash, tandis que notre exemple Blink en assembleur n'en occupe que 160. La différence est assez énorme. Evidement lorsque l'on a un ordinateur avec 8Go de mémoire, gratter quelques octets n'est pas franchement utile. Mais avec la faible mémoire des Micro-Contrôleurs, il devient clair que ce gain est intéressant.

Conclusion

En à peine une dizaine de minutes, nous avons installé nos outils pour développer en assembleur sur Arduino, sachant qu'un coup d'oeil à la documentation d'AVRDUDE nous a montré que nous pouvions envoyer notre code vers d'autres cartes. La facilité de création des Scripts pour TextWrangler a facilité notre travail et rien ne nous empêche d'en créer d'autres, pour d'autres cartes.
A noter que les Script peuvent aussi présenter un interface, par exemple avec un menu déroulant: si vous avez plusieurs cartes, au lieu de créer un Script par carte, vous pouvez créer un Script unique qui présentera un formulaire dans lequel vous choisirez la carte de votre choix. Pour cela, chercher des informations sur AppleScript et vous trouverez votre bonheur (voir ici un article sur ce sujet).

dimanche 10 décembre 2017

Arduino et BBEdit / TextWrangler

Si comme moi vous codez sur un Mac, il y a de forte chance que vous ayez vos petites habitudes avec les outils de Barebones, à savoir TextWrangler ou BBEdit. Lorsque l'on code beaucoup en PHP (ce qui est mon cas) avec l'un de ces deux outils et que l'on passe sous l'Ide Arduino, il y a un choc... Pourtant les outils de Barebones peuvent remplacer l'éditeur de l'Ide de l'Arduino. Voyons ça!

Note: tout ce qui est expliqué ici concerne aussi bien TextWrangler que BBEdit.

Côté Arduino

Ouvrez l'Ide Arduino et dans les préférences, cochez la case "Utiliser un éditeur externe". C'est tout!

Côté TextWrangler

Allez dans le menu juste avant le menu Help donc le menu avec une icône en forme de petit parchemin. Deux cas possibles:
  1. vous avez déjà créé au moins un script pour piloter TextWrangler
  2. vous n'avez jamais utilisé ce menu, donc sous les trois premières entrées, il n'y a rien.

Vous avez déjà au moins un script

Si vous en êtes l'auteur, je suppose que vous savez comment créer un script, donc à vous de jouer. Le script est un peu plus bas dans cet article. Il lance l'Ide Arduino puis simule une compilation-téléversement avec Cmd-U. Il ouvre aussi le Moniteur série (Shift-Cmd-M).
Si vous n'êtes pas l'auteur du ou des scripts actuellement disponibles, la solution la plus simple est la suivante:
  1. Cliquez dans le menu sur "Open Scripts Folder". Dans la fenêtre qui s'ouvre et qui liste tous les scripts, cliquez avec le bouton droit sur un des scripts (.scpt) et choisissez "Dupliquez". Donnez à ce fichier le nom que vous voulez voir apparaître dans le menu (moi je l'ai nommé "Arduino compile and run.scpt").
  2. Double cliquez sur ce fichier ce qui ouvrira l'éditeur de script de votre Mac. Effacez le script actuel et collez à la place le script qui est un peu plus bas dans cet article. Une fois que c'est fait, cliquez sur le petit marteau en haut de la fenêtre pour validez le script, puis cliquez sur "Enregistrer" dans le menu "Fichier".

Vous n'avez aucun script

Dans ce cas dans le menu de TextWrangler, cliquez sur "Open Scripting Editor". Dans la fenêtre d'édition, recopiez le code ci-dessous. Cliquez ensuite sur le petit marteau pour valider le script. Ensuite, cliquez dans le menu sur "Enregistrer" pour sauvez le fichier en lui donnant le nom que vous voulez voir apparaître dans le menu (moi je l'ai nommé "Arduino compile and run.scpt"). Le script doit être sauvé dans le dossier Bibliothéque/Application Support/TextWrangler/Scripts. Si vous ne savez pas comment y accéder, sauvez le script sur le bureau. Nous verrons dans quelques instants comment accéder au dossier Bibliothéque pour y déposer un autre fichier et vous aurez simplement à recopier le script au bon endroit.

Attention: nous parlons ici du dossier Bibliothéque de votre compte utilisateur. Pas du dossier Bibliothéque "général".




Note: ce script est basé sur celui trouvé dans un message du forum Arduino.

On test...

Relancez TextWrangler. Dans le menu vous devez maintenant voir l'entrée Arduino. Une fois que vous aurez chargé un fichier .ino, il vous suffira de cliquez sur cette entrée Arduino pour que cela lance l'Ide Arduino qui chargera le fichier le compilera et l'enverra sur votre carte. Si rien ne se passe au niveau de l'Ide, cliquez une nouvelle fois dans le menu de TextWrangler.

Colorer le code

Outre le fait d'avoir maintenant votre code qui s'ouvre dans TextWrangler, avoir du code coloré, c'est quand même plus sympa.

Pour cela, recopier le code ci-dessous et sauvez-le sous le nom "arduino.plist" (faites un copier collez dans TextWrangler). Ce fichier doit être sauvé dans le dossier Bibliothéque de votre compte utilisateur. Le problème c'est que par défaut ce dossier est caché. Pour le faire apparaître, trois options, expliquées ici.
Une fois ce dossier ouvert, ouvrez son sous-dossier "Application Support". Cherchez et ouvrez le dossier TextWrangler (ou BBedit) et dépose le fichier "arduino.plist" dans le dossier "Language Modules".

Note: vous verrez, au même niveau que ce dossier "Language Modules" le dossier "Scripts".



Une fois le fichier recopié au bon endroit, s'il est encore ouvert dans TextWrangler, fermez ce fichier (quand ce fichier est ouvert et en édition, la coloration ne fonctionne pas).
Désormais, lorsque vous ouvrirez un fichier .ino avec TextWrangler, sa syntaxe sera colorisée.

Attention: le lien entre la coloration et le fichier se fait par l'extension qui doit donc être ".ino"

Ouvrir les .ino avec TextWrangler

Pour que ce soit TextWrangler qui ouvre les fichiers .ino lorsque vous cliquez sur ceux-ci, deux options:
  • Cliquez sur le fichier .ino avec le bouton droit de votre souris et choisissez "Ouvrir avec..." puis "Autre...". Sélectionnez TextWrangler (ou BBedit) dans le sélecteur de fichier. Si vous cochez la case "Toujours ouvrir avec..." ce fichier (et seulement celui-ci) s'ouvrira toujours avec TextWrangler. Les autres ".ino" continueront à s'ouvrir avec l'Ide Arduino.
  • Pour que tous les fichiers .ino s'ouvrent avec TextWrangler, cliquez sur un fichier .ino avec le bouton droit et choisissez "Lire les informations". Sélectionnez TextWrangler ou BBedit dans le menu de la zone "Ouvrir avec".  En cliquant sur "Tout modifier", tous les fichiers .ino seront modifiés pour s'ouvrir avec l'outil que vous aurez choisi.

Petites astuces...

Première fois

Quand vous ouvrez la première fois l'Ide Arduino via TextWrangler, il arrive que l'Ide n'ouvre pas le fichier. Il suffit de recliquer sur le menu dans TextWrangler.

Position des fenêtres

Vous noterez également que l'Ide Arduino sauvegarde la taille et la position de sa fenêtre d'édition lorsque vous le quittez, tout comme la position de la fenêtre du Moniteur Série.
Pour ma part j'ai donc positionné la fenêtre de l'éditeur en bas à droite, en augmentant la hauteur de la zone qui informe sur la compilation et en masquant le code (puisque je l'ai dans TextWrangler), le Moniteur série étant ouvert en partie supérieure (voir copie d'écran ci-dessous).


Pré-remplir un nouveau fichier

Quand vous créez un nouveau Sketch sur l'Ide Arduino, un bout de code est automatiquement généré. Pour faire la même chose lorsque vous créer un nouveau Sketch sous TextWrangler rien de bien compliqué. Il suffit là-encore de faire un petit Script, comme celui que nous venons de créer pour lancer l'Ide, de le nommer comme vous voulez (par exemple "Arduino new.scpt") et de le mettre dans le dossier Scripts de TextWrangler. Voici le script:


Pas besoin d'être un génie du code pour comprendre le principe et de voir qu'au lieu de simplement créer les fonctions setup() et loop() vous pouvez aussi ajouter un header avec votre  nom, date, des defines, des includes, etc... 

Conclusion

Avec quelques réglages simples, il est donc possible d'utiliser TextWrangler en lieu et place de l'éditeur de l'Ide Arduino. Il est aussi possible de faire la même chose pour compiler du C ou faire de l'assembleur, tout en gardant ses habitudes. De plus, la facilité de création des scripts fait qu'il est assez simple de se créer des raccourcis, générant automatiquement du code.

Arduino - C+ASM

Dans un précédent article disponible ici nous avons vu comment faire pour utiliser TextWrangler (ou BBEdit) en lieu et place de l'édite...