Affichage des articles dont le libellé est arduino. Afficher tous les articles
Affichage des articles dont le libellé est arduino. Afficher tous les articles


Arduino et infrarouge

Aujourd'hui, un article rapide sous forme de mémo, pour mettre en place une communication série entre deux Arduinos en infrarouge. Ceci pour permettre de trouver rapidement le matériel et code nécessaires.

Avant tout, il faut savoir que dès lors qu'une communication série filaire (USB par ex) a été mise en place, il suffit de "remplacer" le fil par une liaison optique infrarouge, puisque l'encodage reste le même (UART pour de la communication série).

Code et montage

Puisqu'il est inutile de réinventer la roue, voici un très bon tutoriel de Zola lab expliquant quel montage et quel code utiliser pour mettre en place la liaison optique :

Matériel

Et pour se fournir le bon matériel pour réaliser ce montage, rendez-vous chez Lextronic :

Have fun!


Fichier(s) joint(s) :



Préparer un projet audio avec Arduino

Je vais tâcher ici de vous donner quelques informations de base à connaître lorsque vous décidez de vous lancer dans un projet Arduino traitant avec de l’audio.

Mais attention, je ne parle pas de projet “audio-réactif”, comme on en voit en majorité sur le net, dont le but est de faire réagir le matériel en fonction d’une intensité sonore ou d’une fréquence particulière, ni même de “vu-meter” (visualisateur de fréquence type équaliseur). Mon but ici est de présenter les éléments importants à maîtriser dans le cas où vous souhaiteriez enregistrer réellement du son, c’est-à-dire capturer le signal sonore dans son intégralité en vue de traitement ultérieur : re-lecture, analyse spectrale, manipulation en Big Data pour créer votre propre Shazam... ;-)

Ainsi, sans avoir la prétention de vous donner un cours d’électronique avancée ou de traitement du signal (je n’en ai pas les capacités), je vais vous livrer tout ce que j’ai appris en tant que néophyte après m’être plongé tête baissée dans ce type de projet sans trop de connaissances de ces domaines.

Passage obligé : la théorie

Même si vous me voyez venir, je ne vais pas me lancer dans une explication des Transformées de Fourier (ou Fast Fourier Transform - FFT utilisées en électronique). Cependant, il est intéressant de comprendre leur concept de base : tout signal (ici sonore) peut être décomposé en signaux sinusoïdaux élémentaires, d’amplitudes et de fréquences différentes :

Comme on le voit ici, il s’agit donc d’ondes oscillant avec une amplitude variable autour de 0. Or, nos traitements numériques ne peuvent s’appuyer que sur des valeurs positives : nous allons donc devoir commencer par adapter les signaux captés pour pouvoir les traiter proprement, sans risquer de perdre la moitié des informations.

Pour ce faire, il va falloir décaler l’amplitude du signal de sorte que les oscillations aient une valeur minimum de 0. Ceci est possible en ajoutant une composante continue (pour créer un biais, ou “offset”), ayant pour valeur la moitié de l’amplitude max du signal : si la source émet une onde à partir d’une tension de 5V, le signal oscillera entre +2.5V et -2.5V. L’ajout d’une composante continue de 2.5V le fera alors osciller entre 0V et 5V.

La théorie est simple, n’est-ce pas? Mais voyons ce que cela implique au niveau matériel… Les choses se compliquent.

Un peu d’électronique

Pour additionner nos tensions (alternatives et continues), nous avons besoins d’un pont diviseur. Composé de deux résistances en série, il permet de faire entrer une tension de chaque côté et d’en restituer l’addition entre les deux résistances : nous avons d’ores-et-déjà réussi à décaler notre signal alternatif! Mais on ne peut pas s’arrêter là… Comme vous le constatez, son amplitude a été divisée de moitié, à cause des résistances. Petit aparté : nous venons de toucher du doigt la notion de Gain : c’est notamment la valeur de ces résistances qui fera varier le gain (la puissance) du signal en sortie. Il faut donc ajouter à notre montage un Amplificateur Opérationnel (AO) pour retrouver l’amplitude d’origine. Mais ce dernier ne fonctionnant qu’avec des tensions alternatives, il faut bloquer la composante continue. Pour ce faire, il faut ajouter un condensateur.

Un autre exemple avec un micro :

Voici donc le rôle de ce montage de base que vous retrouverez régulièrement lorsqu’il s’agit de traiter des signaux audio. Vous voyez donc que plusieurs composants électroniques entre en jeu : il est alors évident que les caractéristiques de chacun vont avoir un impact sur la qualité du signal restitué. C’est ce dont nous allons traiter par la suite.

Choix du matériel

Le premier élément à choisir est le micro lui-même, qui fera la captation du son. Sauf besoin particulier (nous l’aborderons un peu plus loin dans cet article), les modules à électrets les plus répandus suffisent amplement.

Cependant, ils peuvent s’avérer très sensibles au bruit électrique du circuit qui les entoure. Par exemple, s’ils sont alimentés via un adaptateur secteur, un signal de “souffle” peut apparaître même en silence complet. De même, sur un Arduino, la sortie 3.3V est connue pour être plus stable que la 5V car plus régulée, mais l’idéal reste de prévoir l’alimentation du micro par pile simple.

Ensuite, l’Amplificateur Opérationnel joue un rôle primordial dans la préservation de la qualité du signal. Prenons pour commencer l’exemple d’un circuit très répandu : le Grove Sound sensor de SeeedStudio.

Il est équipé d’un AO LM358 dont l’inconvénient majeur est d’induire une perte de tension d’environ 1.5V par rapport à la tension d’entrée. Ce qui veut dire que par exemple, pour une tension d’entrée de 5V, la valeur maximale lue sur l’ADC de l’Arduino sera de 750 au lieu de 1024, comme expliqué ici.

Il vaudra mieux donc s’orienter vers un AO possédant une vraie caractéristique “Rail-to-Rail”, c’est à dire fournissant une tension de sortie égale à la tension d’entrée. L’exemple le plus courant est le MAX4466, disponible sur ce circuit d’Adafruit :

Cerise sur le gâteau, ce montage dispose d’un gain ajustable, autrement dit un potentiomètre variable à l’arrière.

Cependant, il faut garder en tête l’usage qui va être fait de ce micro : s’il s’agit de capter par exemple une musique, pas de souci, il suffira de régler le gain en fonction du volume de la source. Mais dans le cas d’une captation de son ambiant, ou environnemental, la difficulté réside dans l’impossibilité de connaître le volume du son qui sera capté. En effet, il n’est pas si évident de pouvoir bien capter à la fois un son faible ou éloigné et un son fort ou proche. Dans ce cas, si le gain est réglé au maximum pour capter les sons faibles, un effet de “clipping” (écrêtage) peut apparaître sur les sons forts, c’est-à-dire qu’ils seront trop amplifiés pour être captés entièrement :

L’idéal est donc de se diriger vers des montages disposant de gain automatiques, capable de passer d’un gain fort dans une ambiance calme à un gain faible dans un environnement bruyant. Cela se trouve également facilement grâce à l’AO MAX9814, disponible sur ce module Adafruit :

En plus de ce réglage automatique, il est possible de configurer un gain par défaut (40dB, 50dB ou 60dB) selon le montage.

Enfin, qui dit réglage automatique, dit réactivité pour adapter le gain. Pour cela, il faut influer sur le ratio Attack:Release (broche “AR” sur le montage), qui détermine la vitesse avec laquelle le gain est diminué (“Attack”) pour atteindre le seuil nécessaire puis augmenté (“Release”) pour revenir en état initial :

J'ai essayé ci-dessous d'illustrer ce concept avec les images d'un oscilloscope :

Pour aller plus loin...

Il reste encore bien des paramètres à prendre en compte au niveau du matériel pour obtenir une captation de bonne qualité.

Pour en citer quelques-uns, on peut commencer par la topologie des micros. En effet, une prise de son stéréo se révèle également bien plus efficace : deux micros permettent de réduire les sources de bruit (ou en tout cas de le soustraire par différentiel) mais également de pallier à l’aspect directif de certains micros.

Cette page explique par exemple comment un couple de micro ORTF permet de reproduire un son entendu par les oreilles humaines, en tenant compte de la distance entre les micros et de leur orientation. Il existe évidemment bien d’autres possibilités selon les situations.

Au niveau logiciel

Le programme à mettre en place joue également un rôle important dans la qualité du signal enregistré. Le défi principal est d’arriver à l’enregistrer de manière continue, sans être interrompu par d’autres tâches. Imaginons que vous vouliez enregistrer votre son dans un fichier WAV (sur une carte SD) : il faut être en mesure de continuer l’enregistrement du son le temps de l’écriture des données sur la carte afin de rester fidèle au signal d’origine et ne pas le hachurer.

Utilité des interruptions

Pour cette raison, il est préférable d’utiliser les interruptions, disponibles sur tous les micro-contrôleurs, notamment ceux équipant les Arduino (AVR ou ARM). Ceci nous permettra de lire à intervalles réguliers les valeurs captées par le micro sans pour autant bloquer les processus d’écriture du fichier WAV (et inversement). Mais nous ne pouvons pas pour autant nous contenter d’écrire sur dans le fichier chaque valeur lue : en effet, le temps d’écriture dans un fichier n’étant pas négligeable, nous perdrions trop d’informations du signal. Pour pallier à ceci, il faut utiliser un tampon, dans lequel nous allons stocker par exemple 512 valeurs, avant de lancer l’écriture sur la carte SD. Mais le problème reste à peu près le même... La solution optimale est d’utiliser 2 tampons en parallèle : une fois que le premier est rempli, on lance l’écriture sur la carte SD tandis que le second se rempli en arrière-plan. Une autre technique similaire est celle du “circular buffer”. Sur les plateformes ARM, il est également possible d’utiliser le Direct Memory Access.

Enfin, les micro-contrôleurs Arduino étant cadencés à plusieurs MHz et un extrait audio ne nécessitant qu’un échantillonage de l’ordre du kHz, nous utiliserons un “prescaler”, c’est-à-dire un diviseur pour limiter la fréquence de déclenchement des interrupts.

Comprendre le convertisseur analogique-numérique

Puisque nous voulons enregistrer un signal analogique, nous allons devoir utiliser la fonction “ADC“ qui permet de convertir une valeur analogique en numérique. Il est à ce stade très important de jeter un oeil à la spécification de l’ATmega2560 : elle indique (page 279) qu’une conversion analogique-digital dure 13 cycles d’horloge. Voyons ce que cela implique pour nous.

Comme évoqué plus haut, le micro-contrôleur de l’Arduino Mega 2560 est cadencé à 16MHz. Commençons donc par réduire la fréquence des interrupts par un prescaler de 32 :

16 MHz / 32 = 500 kHz

Cependant, pour prendre en compte le temps de conversion analogique-numérique, il faut encore diviser cette fréquence :

500 kHz / 13 ≈ 38 kHz 

Nous obtenons donc ici une fréquence d’échantillonage finale de 38 kHz, proche de ce qui se pratique sur un CD audio (44,1 kHz). Pour cet exemple je me suis inspiré de cet Instructable. Vous pourrez donc adapter ce réglage à vos besoins.

Enfin, les échantillons relevés par l’ADC de l’Arduino sont codés sur 10 bits, comme présenté sur cette page. Or la plupart des formats audio le sont sur 8 bits. Il faut donc diminuer la précision des données acquises (“down-sample”) pour respecter les standards. Pour ce faire, il faut paramétrer l’ADC pour ne lire que les 8 bits ADCH via la commande : ADLAR=1

Prendre en compte l’offset

Vous vous souvenez de l’offset abordé en début d’article? Vous remarquerez que sur le dernier montage d’Adafruit, il est indiqué “DC offset : 1.25V”. Ce qui signifie que dans un silence complet, les valeurs retournées ne seront pas de 0 mais d’une valeur légèrement supérieure (dépendant du la tension d’entrée).

Ceci convient très bien à l’enregistrement de fichier WAV 8 bits, qui sont codés uniquement à partir de valeurs positives (unsigned int). Les logiciels de lecture (type Audacity) le savent et s’adaptent en fonction.

Cependant, à des fins de traitements du signal plus poussés, il peut s’avérer nécessaire de posséder à la fois les valeurs positives et négatives. Pour cela, il faudra penser à soustraire la valeur de l’offset (donc du silence) des valeurs mesurées.

A vous de jouer!

Vous avez dorénavant les éléments les plus importants pour vous lancer dans un projet de traitement de signal audio. J’espère que cela vous aura été utile!


Fichier(s) joint(s) :



Arduino Ethernet avec module ENC28J60

J'ai récemment acquis des cartes Ethernet de chez Velleman, dédiées à l'Arduino : VMA04

Elles ont le mérite d'êtres moins chères, de par leur fabrication et aussi par l'absence de slot SD. Mais il n'est pas si simple de trouver une bonne librairie pour l'utiliser, celle disponible dans la distribution Arduino ne fonctionnant qu'avec un module W5100.

Après de longues recherches, mon choix s'est orienté vers UIPEthernet, présentée ici : Arduino et enc28j60 : EtherCard ou UIPEthernet

Et pour la faire fonctionner avec un Arduino Mega, il faut modifier quelque peu le code comme expliqué dans la page liée, mais avec les valeurs suivantes (dans le fichier fichier utility/Enc28J60Network.h) :

#define ENC28J60_CONTROL_CS     10
#define SPI_MOSI        MOSI
#define SPI_MISO        MISO
#define SPI_SCK         SCK
#define SPI_SS          SS

Hope this helps!


Fichier(s) joint(s) :



Un client CoAP simple en C

CoAP est un protocole de communication dédié aux objets connectés : basé sur UDP et REST, il permet de diminuer au maximum le contenu des messages pour économiser les ressources des objets lors des transferts de données.

Nous allons voir ici comment implémenter votre propre client, à l'aide de la spécification du protocole. Le but est d'être capable d'envoyer, de la manière la plus simple possible, des données à un serveur CoAP exposant des services. Niveau matériel, un Arduino Mega avec un shield Ethernet communique avec un serveur Java.

Commençons par la structure des messages. Ce chapitre de la spécification nous explique comment les structurer :

8 bits 8 bits 8 bits 8 bits
Ver T TKL Code Message ID
Token
Options
Terminator Payload

Je vous laisse vous référer à la spécification pour la signification des champs. Un peu plus en détails, les Options suivent un format propre :

8 bits 8 bits 8 bits 8 bits
Ver T TKL Code Message ID
Token
Delta Length Value
Terminator Payload

Ici nous allons construire un message contenant :

  • version : 1
  • type : Request
  • TKL : 0 (pas de Token)
  • Code : POST
  • vers le service : "/sensors"
  • contenu : "test"

Voici ce que cela donne :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
Uri-Path 7 URI segment "sensors"
Terminator "test"

Chaque Option possède un code "Option Number" propre. Par exemple, "Uri-Path" qui permet d'indiquer un segment de l'adresse du service, a pour code 11. Le Delta se calcule alors en additionnant le code de l'option avec celles des précédentes :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
Terminator "test"

Si l'on veut spécifier plusieurs options du même type, il n'est pas nécessaire d'additionner le code. Par exemple, pour pointer sur le service "/sensors/temp" :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
0+11+0 4 "temp"
Terminator "test"

En revanche, pour une autre Option, par exemple "Max-Age" qui a le code 14 :

8 bits 8 bits 8 bits 8 bits
1 Request 0 POST Message ID
 
0+11 7 "sensors"
0+11+14 2 12
Terminator "test"

Revenons à notre exemple et transformons tout en valeurs selon la spec et notre cas :

8 bits 8 bits 8 bits 8 bits
1 1 0 0.02 52942
 
11 7 "sensors"
Terminator "test"

Ce qui nous donne, en binaire en respectant la longueur des champs (pour les champs les plus simples) :

8 bits 8 bits 8 bits 8 bits
01 01 00000 00000010 1100111011001110
 
1011 0111 "sensors"
11 11 1111 "test"

Enfin, pour se faciliter l'envoi via le contrôleur Ethernet, dont la librairie est prévue pour traiter des "char" sur 8 bits, il suffit de transformer le résultat en concaténant les bits par 8 :

8 bits 8 bits 8 bits 8 bits
0x50 0x02 0xCE 0xCE
 
0xB7 "sensors"
0xFF "test"

En pratique, un petit exemple de code Arduino pour envoyer nos données :


#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>

char coapacket[18];
EthernetUDP udp;

void loop() {
  coapacket[0] = 0x50;
  coapacket[1] = 0x02;
  coapacket[2] = 0xce;
  coapacket[3] = 0xce;
  coapacket[4] = 0xB7;
  coapacket[5] = 's';
  coapacket[6] = 'e';
  coapacket[7] = 'n';
  coapacket[8] = 's';
  coapacket[9] = 'o';
  coapacket[10] = 'r';
  coapacket[11] = 's';
  coapacket[12] = 0xff;
  coapacket[13] = 't';
  coapacket[14] = 'e';
  coapacket[15] = 's';
  coapacket[16] = 't';
  coapacket[17] = '\0';
 
  udp.beginPacket(serverIP, 5683);
  udp.write(coapacket);
  udp.endPacket();
}

Voilà tout!


Fichier(s) joint(s) :



Arduino : créer une horloge synchronisée

Dans cet article, nous allons voir comment afficher sur un écran la date et l'heure courantes, avec la possibilité de se synchroniser sur un serveur de temps public (NTP - Network Time Protocol), comme le font tous les PC ou téléphones.

Matériel

Pour cet exemple, il faut vous munir :

  • d'un arduino (ici un DUE)
  • un shield Ethernet
  • un module RTC (Real Time Clock, ici un Gravitech)
  • un écran LCD

Voici un schéma du montage :

L'écran est branché, de manière classique, sur des broches numériques. Le module RTC quant à lui est intégré sur le bus I2C par les broches SDA1 et SCL1.

Logiciel

Nous allons utiliser les librairies :

  • Ethernet
  • Wire1 (pour le bus I2C)
  • LiquidCrystal
  • Time (pour faciliter la décomposition de la date à partir d'un timestamp)

Données importantes :

Selon la spécification du constructeur, l'adresse de communication du module RTC sur le bus I2C est 0x68. Le serveur NTP choisi est celui de l'Observatoire de Paris (ntp-p1.obspm.fr, IP: 145.238.203.14). Le dialogue avec un serveur NTP se fait par échange de paquets par le protocole UDP.

C'est parti!

1. Configuration d'une date par défaut sur le module RTC

Commençons par un premier échange avec ce module :


const int I2C_address = 0x68;
...
Wire1.begin();        // join i2c bus (address optional for master)
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
Wire1.write(0); // sec
Wire1.write(0x11); // min
Wire1.write(0x20); // heure
Wire1.write(6); // jour de la semaine
Wire1.write(0x20); // jour
Wire1.write(9); // mois
Wire1.write(0x14); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C

Ici nous définissons la date "20/09/2014 20:11:00". Remarquez que les valeurs transmises sont au format hexadécimal.

2. Lecture de la date sur le module RTC

Interrogeons notre module pour récupérer la date :


void getTime(char* tempData) {
 byte i = -1;
 byte error;
 
 Wire1.beginTransmission(I2C_address); // Début de transaction I2C
 Wire1.write(0); // Demande les info à partir de l'adresse 0 (soit toutes les info)
 error = Wire1.endTransmission(); // Fin de transaction I2C
 
 if(error==0) {
  Wire1.requestFrom(I2C_address, 7); // Récupère les info (7 octets = 7 valeurs correspondant à l'heure et à la date courante)
 
  while(Wire1.available())        
  {
   tempData[++i] = Wire1.read(); // receive a byte as character
  }
 } else {
  // le composant RTC n'etait pas joignable, on relance le bus I2C pour la prochaine fois
  Wire1.begin();
 }
}

Cette méthode nous permet de récupérer, dans un tableau de 7 char (tempData), les données du module.

3. Affichage de la date sur l'écran


void displayTime()
{
 char tempchar[7] = {'\0'};
 // recuperation de la date
 getTime(tempchar);
 // affichage
 if(tempchar[0]!='\0') {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(tempchar[4],HEX);
  lcd.write("/");
  lcd.print(tempchar[5],HEX);
  lcd.write("/20");
  lcd.print(tempchar[6],HEX);
  lcd.setCursor(0, 1);
  lcd.print(tempchar[2],HEX);
  lcd.write(":");
  lcd.print(tempchar[1],HEX);
  lcd.write(":");
  lcd.print(tempchar[0],HEX);
 }
}

Avec cette méthode, on envoie à l'écran chaque caractère (toujours au format hexadecimal) retourné par l'appel au module RTC.

Il ne vous reste plus qu'à placer l'appel à cette méthode dans une boucle toutes les secondes pour voir défiler le temps. Essayez même de couper l'alimentation du module RTC : l'affichage de l'heure se fige, jusqu'à nouvelle alimentation du module. Elle sera alors mise à jour avec la date qui a continué à défiler grâce à la pile du module.

4. Interrogation du serveur de temps

Le shield Ethernet va nous permettre de contacter le serveur public et de récupérer les informations de date "de référence".


IPAddress timeServer(145, 238, 203, 14); // ntp-p1.obspm.fr (IP: 145.238.203.14)
const int NTP_PACKET_SIZE= 48; // NTP time stamp is in the first 48 bytes of the message
uint8_t packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets
/* Set this to the offset (in seconds) to your local time
   GMT - 2 */
const long timeZoneOffset = -7200L;  
// A UDP instance to let us send and receive packets over UDP
EthernetUDP udpClient;
EthernetClient client;

/**
* Démarrage des librairies
*/
Ethernet.begin(mac);
udpClient.begin(8888); // port UDP local


/**
* Envoi de la requete 
*/
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011;   // LI, Version, Mode
packetBuffer[1] = 0;     // Stratum, or type of clock
packetBuffer[2] = 6;     // Polling Interval
packetBuffer[3] = 0xEC;  // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;

// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:
udpClient.beginPacket(timeServer, 123); //NTP requests are to port 123
udpClient.write(packetBuffer,NTP_PACKET_SIZE);
udpClient.endPacket();


/**
* Parcours de la réponse du serveur
*/
unsigned long epoch = 0;
if ( udpClient.parsePacket() ) {
 // We've received a packet, read the data from it
 udpClient.read(packetBuffer,NTP_PACKET_SIZE);  // read the packet into the buffer

 //the timestamp starts at byte 40 of the received packet and is four bytes,
 // or two words, long. First, esxtract the two words:

 unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
 unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
 // combine the four bytes (two words) into a long integer
 // this is NTP time (seconds since Jan 1 1900):
 unsigned long secsSince1900 = highWord << 16 | lowWord;

 // now convert NTP time into everyday time:
 // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
 const unsigned long seventyYears = 2208988800UL + timeZoneOffset;
 // subtract seventy years:
 epoch = secsSince1900 - seventyYears;
}


/**
* Configuration du module RTC avec la nouvelle date
*/
setTime(epoch); // on affecte le timestamp récupéré à la librairie Time
Wire1.beginTransmission(I2C_address);// Début de transaction I2C
Wire1.write(0); // Arrête l'oscillateur
String strval = String(second(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // sec
strval = String(minute(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // min
strval = String(hour(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // heure
Wire1.write(weekday()-1); // jour de la semaine
strval = String(day(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // jour
strval = String(month(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // mois
strval = String(year(), DEC);
Wire1.write(strtol(strval.c_str(),NULL, HEX)); // annee
Wire1.write(0); // Redémarre l'oscillateur
Wire1.endTransmission(); // Fin de transaction I2C

La librairie Time nous permet de décomposer facilement les données de la date. Mais le serveur NTP nous a envoyé la date au format décimal "20/09/2014 20:11:00". Il faut donc convertir les données au format hexa avant de configurer le module RTC.

Ainsi, après l'appel à ce code (à découper en méthodes pour plus de clarté), l'affichage de la date sera automatiquement actualisé avec la date de référence.

Have fun!

Sources :


Fichier(s) joint(s) :



Utiliser les librairies Arduino dans Atmel Studio

Dans cet article je vais vous présenter comment démarrer un projet simple (exemple Blink) avec un Arduino Due, Atmel Studio 6.2 et les librairies du projet Arduino.

Pour commencer, vous pouvez suivre cet article très clair et très simple pour initier l'environnement : http://www.engblaze.com/tutorial-using-atmel-studio-6-with-arduino-projects/

Datant cependant de 2012, il mérite quelques mises à jour : il vous faudra télécharger la dernière version du software Arduino capable de gérer le Due. Les chemins vers les répertoires à inclure seront donc légèrement différents, puisque spécifiques à la plateforme (par ex ".\hardware\arduino\sam\variants\arduino_due_x"). Il faudra également ajouter dans le Linker un lien vers la librairie libsam_sam3x8e_gcc_rel située sous ".\hardware\arduino\sam\variants\arduino_due_x".

Il se peut que vous rencontriez des petites erreurs de compilation, que vous pourrez résoudre à la main en commentant/déplaçant du code.

Voici maintenant la nouvelle version de l'exemple Blink :


#include "sam.h"
#include "Arduino.h"


int led = 53;

/**
 * \brief SysTick_Handler.
 */
void SysTick_Handler(void)
{
 /* Increment counter necessary in delay(). */
 TimeTick_Increment();
}

/**
 * \brief Application entry point.
 *
 * \return Unused (ANSI-C compatibility).
 */
int main(void)
{
    /* Initialize the SAM system */
    SystemInit();
    SysTick_Config(SystemCoreClock / 1000); //1ms per interrupt
 
    pinMode(led, OUTPUT);

    while (1) 
    {
        digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
        delay(1000);               // wait for a second
        digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
        delay(1000);
    }
}

Pour faire fonctionner la méthode delay, il est nécessaire d'ajouter le code activant l'incrémentation du compteur systeme :


void SysTick_Handler(void)
{
 /* Increment counter necessary in delay(). */
 TimeTick_Increment();
}
...
SysTick_Config(SystemCoreClock / 1000); //1ms per interrupt

Ainsi, la méthode d'incrémentation TimeTick_Increment sera appelée par l'handler interne au processeur SysTick_Handler toutes les 1ms comme indiqué par SysTick_Config

Ne reste plus qu'à indiquer la plateforme cible à la compilation (processeur SAM3X8E) et lancer la programmation!

Edit : il est également possible d'appeler la méthode Arduino TimeTick_Configure(SystemCoreClock)


Fichier(s) joint(s) :



Arduino et après?

Ce titre est un peu raccoleur, surtout que je vais continuer à parler d'Arduino... Le sujet exact ici est plutôt "AVR et après?".

En effet, l'ampleur ou les besoins de certains projets nécessitent d'aborder une solution plus complète que les plateformes Arduino classiques, utilisant des microcontrôleurs AVR 8 bits.

Voici donc comment prendre en main l'Arduino Due, et surtout son microprocesseur ARM 32 bits (ATSAM3X8E), utilisé par exemple dans les smartphones.

Plus de mémoire!

Le point le plus marquant et intéressant de la plateforme est sans doute la quantité de mémoire disponible : 512Ko de Flash (pour le programme) et 96Ko de RAM (pour l'execution et les données).

De plus la vitesse d'exécution de 84Mhz peut apporter un sacré coup de pouce aux projets qui le nécessitent (temps réel...). Enfin, l'utilisation de données sur 32 bits offre une puissance de calcul importante.

Pour une présentation détaillée, je vous laisse lire l'article suivant : [Test] Arduino Due & IDE arduino 1.5.x

Appréhender le nouveau coeur

Un processeur ARM est bien plus complexe que les traditionnels AVR : bus de données, contrôleur avancé d'interruptions, optimisation du code exécuté, timers (watchdog)... Je ne vais pas m'attarder ici sur ce sujet complexe, je vous laisse vous lancer par vous-même en lisant par exemple cette présetation, l'excellent ouvrage "Processeurs arm - architecture et langage d'assemblage" de Jacques Jorda ou le livre "The Definitive Guide to the ARM Cortex-M3, Second Edition" de Joseph Yiu.

Outils de développement

Qui dit nouvelle architecture, dit nouveaux outils... Pour commencer, le compilateur : place à la toolchain ARM-GCC. Maintenue par les développeurs de AVR-GCC, elle permet de faire du bare-metal (oh-yeah!), comprenez du code indépendant de la plateforme. Pour la culture, voici une exlication du nom du compilateur "arm-none-eabi" :

Basically, "arm-none-eabi" means:

- 'arm' we are building for the ARM architecture (ARM, Thumb, Thumb-2, etc).
- 'none' means we are not building for a specific operating system. or 'bare-metal' (...)
- 'eabi' says we're going to use the ARM standard EABI, which describes how binary files (libraries, etc) are stored (the actual file formats), and calling conventions, register usage, etc. This makes libraries from different parties compatible with each other and your code. ABI's and EABI's standardize our compiled code.. (...)

source : http://kunen.org/uC/gnu_tool.html

Qu'en est-il du code lui-même? Là où les plateformes AVR supportent très bien le C++, les processeurs ARM préfèrent nettement le C car ils sont bien plus optimisés pour gérer ce langage. Qui plus est, la plupart des améliorations apportées par le C++ (orienté objet...) ne sont que très peu utilisées dans cet environnement et ne justifient donc pas (pour le moment) son utilisation.

Des librairies et frameworks permettent également de faciliter la prise en main du code : CMSIS, une couche d'abstration de moyen niveau fournie par ARM (donc "vendor-independant"), ou bien Atmel Software Framework, cette fois-ci proposé par le fabricant Atmel dans son environnement de développement, qui apporte notamment une tonne d'exemples et de bonnes pratiques.

Débugger un ARM

La nouvelle gestion de la mémoire du processeur ainsi que les nouvelles possibilités d'observation du code exécuté requièrent forcément de nouveaux outils pour débugger votre programme. Adieu donc notre bel AVR-Dragon vu précédemment. Même s'il est heureusement toujours possible d'utiliser une interface JTAG, il vous faudra investir dans par exemple ce J-Link de Segger :

Pourquoi celui-ci en particulier? En effet, il existe également le SAM-ICE de Atmel. Mais je pense que lorsque vous aurez jeté un oeil aux prix pratiqués, la version Education du JLink fera pencher la balance! Surtout qu'au final les fonctionnalités sont quasi-identiques, l'un n'étant qu'une déclinaison de l'autre...

Pour la commande, je me permets de vous conseiller le site Mouser, très pro et très rapide pour la livraison. Dernier point important, même si l'Arduino Due dispose bien d'une interface JTAG 2x5 broches, elle est de taille réduite! Et les debuggers quant à eux utilisent des interfaces 2x10 broches. Il faudra vous procurer l'adaptateur correspondant : 10-PIN TO 20-PIN JTAG ADAPTER.

Vous voilà bien armés pour démarrer... La suite au prochain épisode! Have fun!

Sources :


Fichier(s) joint(s) :



Arduino : XBee Ethernet data logger

Après plusieurs semaines de galère, j'ai décidé de rassembler ici tous les éléments qui m'ont permis, tant bien que mal, de mettre en place l'installation suivante :

D'un côté, un module XBee transmettant les informations d'un capteur de température (LM35). De l'autre, un second XBee relié à un Seeduino ATMega 2560 (similaire à un Arduino) qui enregistre toutes les mesures reçues sur une carte SD et expose le fichier créé via une connexion Ethernet. Voici le schéma du montage :

Avant d'aller plus loin, il est indispensable de faire un point sur la notion de SPI afin de comprendre le fonctionnement de notre montage.

SPI est un bus de communication basé sur les principes de Maitre et Esclave. Il est composé de 4 canaux. Pour ne pas plagier un très bon cours existant sur le site rocketnumbernine, voici en quelques mots son fonctionnement :

Each end of a SPI connection is acting in one of two roles - Master or Slave. The master is responsible for initiating and controlling the communication.

SPI Master/Slave Connections

The basic mode of operation is very simple: When the master wishes to initiate transfer of data:
  1. It sets the SS (Slave Select - often called CS - chip select) pin low to tell the slave that communication is about to start
  2. The master writes a bit of information onto the MOSI (Master Out Slave In) wire (sets it to 0 or 1) and the slave does the same on the MISO wire (either of these can be omitted if the data transfer is one way)
  3. As the master ticks the clock line SCLK it will read the value of MISO (Master In Slave Out) wire (which the Slave has written) and the slave will read the value of the MOSI wire (whether the data is sampled as the clock rises or falls depends on which mode is in operation)
  4. The process is repeated from (b), transferring a bit of data on each pulse of the clock until all data is transferred

Ce bus SPI est accessible via les broches ICSP (Arduino UNO et Mega 2560) ET les broches 11,12,13 (pour le UNO) ou 50,51,52 (pour le Mega 2560).

Un élément important est le Chip Select : il s'agit de la broche qui permet de choisir le périphérique cible de l'échange. Pour désactiver un périphérique, il faut passer la broche en niveau haut et pour l'activer, en niveau bas. Un seul périphérique ne peut être activé à la fois car les trois broches de communication MOSI, MISO et SCK sont partagées.

Sur l'Arduino UNO (Revision 3), le Mega 2560 (Rev 3) et sur le shield Ethernet (Rev 3), on trouve le CS :

  • sur la broche A4 pour la carte SD
  • sur la broche A10 pour l'Ethernet

C'est grâce à cette correspondance des broches qu'il est possible de simplement empiler les cartes sans avoir à faire de connexions supplémentaires. Il est tout de même possible, si vous ne souhaitez pas empiler les cartes, de relier les broches comme indiqué sur le schéma. Attention cependant, ceci est impossible sur le shield Ethernet car seules les broches CS sont utilisées et reliées directement à l'ICSP. Toutes les autres broches ne servent qu'à transférer les connexions à une éventuelle autre carte supperposée.

Revenons donc à notre projet. La carte Ethernet est apposée sur l'ATMega 2560, le module XBee principal (coordinateur) relié directement au Serial1 : broches RX1 et TX1 (croisées avec les RX/TX du XBee).

Côté logiciel maintenant : l'application doit, au démarrage, aller lire un fichier de configuration sur la carte SD et initialiser une connexion Ethernet. Pour bien démarrer, voici le code de la fonction setup() :


void setup() {
 pinMode(10, OUTPUT);
 pinMode(4, OUTPUT);
 // 
 pinMode(53, OUTPUT);
 digitalWrite(53, HIGH);
 /* disable all slaves */
 // disable Ethernet
 digitalWrite(10, HIGH);
 delay(5);
 // disable SD
 digitalWrite(4, HIGH);
 delay(5);

 /* read config file */
 // init SD
 boolean sd = SD.begin(4);
 delay(5);
 // ... read file ...
 // disable SD
 digitalWrite(4, HIGH);
 delay(5);
}

A noter qu'il est très important de désactiver à la main l'Ethernet après son initialisation car la librairie ne le fait pas! Sur un ATMega 2560, il faut également obligatoirement définir la broche de Hardware SS (53) en sortie.

Ensuite, afin que le programme puisse à la fois recevoir en continu des paquets de données XBee à écrire sur la carte SD et assurer son rôle de serveur web, voici, sous forme algorithmique, le déroulement du code à mettre en place :

void loop() {
 - Vérifier la présence d'un packet XBee
  - si un paquet a été reçu :
   - traiter les informations du paquet 
   - désactiver l'Ethernet : digitalWrite(10, HIGH)
   - activer la carte SD : digitalWrite(4, LOW)
   - écrire sur la carte SD
   - désactiver la carte SD : digitalWrite(4, HIGH)
 - Vérifier la réception d'une requete Ethernet :
  - si une requete a été reçue :
   - désactiver la carte SD : digitalWrite(4, HIGH)
   - activer l'Ethernet : digitalWrite(10, LOW)
   - générer la réponse HTTP
   - fermer la connexion client
   - désactiver l'Ethernet : digitalWrite(10, HIGH)
}

Normalement, la gestion de l'activation/désactivation de chaque périphérique SD ou Ethernet est gérée en interne par les librairies correspondantes, mais je vous conseille de ne pas hésiter à le faire également manuellement afin de renforcer votre programme (on n'est jamais mieux servi que par soi-même!).

Un conseil supplémentaire : il est préférable d'ajouter quelques délais après chaque dés/activation de périphérique (via la méthode delay()), au moins pendant la phase de développement, afin d'éviter tout conflit de communication sur le bus SPI (par les broches partagées) qui pourrait engendrer, par exemple, la destruction de la carte SD (qui arrive plus vite qu'on ne le croit!).

Ci-dessous sont décrites les erreurs les plus communes qui peuvent être rencontrées :

  • Impossible de démarrer la carte SD :
    Si SD.begin() renvoie false, il faut dans un premier temps vérifier les connexions SPI (MOSI, MISO) dans le cas où elles auraient été réalisées autrement que par les broches ICSP et dans un second temps s'assurer que seule sa broche CS est active.
  • Impossible de PING la carte Ethernet :
    Les requêtes ping répondent "Impossible de joindre l'hôte" ("Host unreachable") et la LED TX de la carte ne s'allume jamais : vérifier également que seule la broche CS de la carte Ethernet est active et qu'aucun autre périphérique ne tente d'accéder au bus SPI.

Vous voilà prêts! J'ai essayé de regrouper ici les connaissances de base dont j'ai eu besoin et pour lesquelles j'ai du écumer nombre de blogs et forums. J'espère que tout cela vous sera utile!


Fichier(s) joint(s) :



Présentation de l'AVR Dragon

Comme évoqué dans mon précédent article, la carte AVR Dragon de Atmel permet de débugger et monitorer du code s'exécutant sur les microcontrôleurs AVR.

Mais commençons par le commencement, où se le procurer? En effet, ceci est bien moins simple qu'il n'y parait. Seuls quelques sites proposent de le commander à des prix corrects :

  • Radiospares : le meilleur prix trouvé mais semble-t-il réservé aux professionnels... Impossible de le commander autrement.
  • Digikey : cette fois, c'est plutôt les détails sur la livraison qui font défaut : prix, délais... Reconnu mais pas très clair à mon goût.
  • Enfin, Farnell : un prix de base à peine plus élevé, des frais de transport assez chers, mais une livraison garantie en 24h (j'ai testé).

L'intérêt principal de cet outil est qu'il est largement présenté et décrit sur le net, régulièrement utilisé dans les tutoriels et forcément très bien intégré à l'environnement Atmel, notamment au debugger de Atmel Studio.

Mais avant d'en arriver là, il faudra transpirer encore un peu!

La carte c'est bien, mais comment l'utiliser? Là encore, fouillis sur la toile : ISP, JTAG, HE10... Ouf! Alors pour faire clair, voici ce que j'ai pu en tirer :

Pour un Arduino UNO, la seule interface disponible est l'ISP, composé de 2x3 broches. Malheureusement, ce type de cable est assez difficile à trouver, même sur le net. Et franchement, payer 5€ de frais de port pour un cable à 30cts, non merci! Alors oui, incroyable mais vrai, il va falloir se FABRIQUER le fameux cable...

Voici comment j'y suis parvenu : des connecteurs type HE10 femelle, un nappe de 10 connecteurs, des gaines thermo-rétractables et du courage. Voici ce que cela peut donner (je ne suis pas expert en soudure...) :

Et voici le branchement espéré :

C'est parti? Eh non! Car en l'état, l'interface ISP ne pourra pas reprogrammer le micro-contrôleur : en effet, la carte Arduino possède un capaciteur branché sur le système de RESET, utilisé pour réinitialiser le bootloader lors de la programmation par USB. Et ce capaciteur interfère avec l'usage des modes de debug ISP et DebugWire. Il va donc falloir désactiver ce capaciteur!

Vous trouverez ici une excellente explication de la marche à suivre : Adam Hilltop

Après cette opération, la programmation automatique via la prise USB ne sera plus possible. Il faudra penser à actionner manuellement le bouton RESET de l'Arduino juste avant chaque nouvel upload (ou reconstituer le capaciteur!).

Un autre exemple pour le Duemilanove : Awtfy

Cette fois-ci, normalement, vous avez tout le nécessaire pour commencer à débugger votre code! En attendant mes propres tests, voici encore un peu de lecture :

Go ahead!


Fichier(s) joint(s) :



Debugger avec Arduino

Cet article (un peu long) a pour but de donner un point de vue global sur l'environnement Arduino et les différents outils disponibles, fruit de longues heures de recherches. Aucune vocation à être exhaustif, mais plutôt de permettre de se situer et savoir par où commencer selon vos besoins.

Ayant passé le cap du newbie avec Arduino et ayant donc atteint les limites de l' "IDE" fourni, je cherchais maintenant un moyen d'utiliser un VRAI IDE afin de pouvoir développer de manière plus complexe en C++ (créer des structures, jouer avec le pointeurs) et bénéficier des avantages habituels d'un tel outil : coloration syntaxique, auto-completion, optimisation et... DEBUG!!

Mais tout d'abord, quelles sont les limites de l'outil de développement proposé par la distribution Arduino? En réalité, son intérêt est d' "arranger" le code saisi afin de le rendre compatible à un environnement classique C/C++ (car c'est bien ce qui se cache derrière). Mais on arrive vite au point où cela ne nous "arrange" plus du tout : des erreurs de compilation apparaissent alors que le code est bon (compilable par ailleurs).

De la même façon, l'outil permet de faciliter la programmation de la platine en masquant le travail de la "toolchain". D'ailleurs, petite parenthèse, car souvent sur le net ceci semble inné, mais voici une petite explication de ce qu'est cette AVR Toolchain :

La toolchain AVR — Chaque outil de la chaîne de développement possède un rôle spécifique. Les productions de chaque outil intermédiaire servant à alimenter le suivant:
  • Le compilateur traduit en assembleur les fichiers sources écrit dans un langage de haut niveau;
  • L'assembleur transforme le code assembleur (issu de la compilation ou écrit manuellement par le programmeur) en fichier objet contenant uniquement du code binaire;
  • L'éditeur de liens (linker) rassemble les différents fichiers objets binaires et les bibliothèques en un seul fichier exécutable. L'exécutable peut être directement lancé sur un simulateur ou servir pour le débogueur;
  • Pour la programmation d'un micro-contrôleur, le programme object copy extrait de l'exécutable le code machine sous un format prêt à télécharger sur la cible;
  • Le programmeur est chargé de transférer le code vers la cible.
source : http://www.chicoree.fr/w/USnooBie#GNU_AVR_Toolchain

Mais rentrons dans le vif du sujet, quelles sont les possibilités qui s'offrent à nous pour développer en C/C++ orienté AVR (microcontrôleurs)?

Commençons par le commencement...

Comme nous l'a montré la présentation de la Toolchain, l'outil de base qui va nous permettre de debugger notre projet est donc AVR-GDB (la spécialisation pour AVR du debugger C/C++ nommé GDB).

Après lecture de quelques docs explicatives (pas si faciles à trouver) sur son fonctionnement ici et , on comprend vite qu'il est assez complexe, pouvant être lancé directement à partir d'un fichier compilé ou en mode serveur pour se connecter à un simulateur démarré séparément (voir plus bas).

Les IDEs

Comme tout bon développeur Java, je me suis tout d'abord demandé si mon bon vieux compagnon de route avait de quoi répondre à mes besoins : après quelques recherches sur le net, cette option semble assez complexe. De bonnes connaissances sont à prévoir avant de réussir à bien configurer Eclipse.

Puis, en continuant mes recherches sur le net, je rencontre Code::Blocks, un IDE Open Source, qui semble rencontrer un petit succès auprès des amateurs d'Arduino, surtout grâce à son plugin spécialisé. Après un test rapide, il semble un peu "léger" et je n'ai pas vraiment envie d'essayer de prendre un main un nouvel éditeur.

Continuons les recherches... Voici maintenant Atmel Studio : un IDE entièrement dédié au développement sur microcontrôleur puisque édité par la société qui fabrique un grande majorité des plus usités (notamment la famille ATMega que l'on retrouve sur les Arduino). Génial, oui, mais pas des plus simples à appréhender puisque très spécialisé. De très bon tutoriels un peu partout sur le net donnent heureusement souvent des informations essentielles pour s'en sortir. Gardons le dans un coin et essayons autre chose.

La dernière possibilité restante, Netbeans. Déjà très efficace pour le développement C/C++, sa configuration pour AVR semble plus aisée et largement explicitée comme ici ou ou encore par ici.

Cette piste semblant intéressante, allons plus loin. Après avoir passé quelques heures à m'arracher les cheveux pour faire fonctionner le compilateur C++, le linker, l'auto complétion etc (pourquoi tant de haine?!!!), et alors que la lumière surgissait au bout du tunnel et que j'allais enfin pouvoir commencer à coder, voici que Netbeans semble ne pas être très ami avec AVR-GDB... Quand ce dernier arrive à se lancer, aucun de mes points d'arrêts ne fonctionnent dans Netbeans... Tant pis, essayons d'avancer.

Petit aparté : une alternative aux IDEs classiques est Codebender, un outil de développement collaboratif en ligne. A essayer.

Les simulateurs/émulateurs

Une autre question me vient à l'esprit : plutôt que remettre en place tout mon montage chaque fois que je veux tester mon code, ne serait-il pas possible de simuler le comportement de l'Arduino sur le PC et ainsi gagner du temps?

Sur ce sujet là il existe pléthore de solutions, difficile de s'y retrouver.

Tout d'abord SimulAVR. Il est embarqué avec WinAVR et même présent dans la suite d'outils Arduino. Il se lance à partir d'un fichier ".elf" (produit par la compilation du projet C++) et sert de "serveur" sur lequel peut se connecter AVR-GDB pour exécuter le code, comme expliqué ici. Sa présentation très séduisante (spécialisé AVR...) laisse miroiter des fonctionnalités intéressantes mais très vite après quelques recherches on apprend qu'il n'est en réalité plus maintenu depuis quelques années et très limité...

Nous retrouvons ensuite Atmel Studio : il dispose également d'un simulateur, très souvent décrit et clairement expliqué. Après quelques tests et diverses galères, cette solution semble la plus probante : c'est bien le seul outil qui jusqu'ici m'a permis de dérouler pas à pas mon code. Mais (il en faut bien un), une fois encore les possibilités offertes par ce simulateur sont limitées : impossible de gérer une carte SD ou autre périphérique.

Il existe bien d'autres possibilités, mais hors IDE, comme le simulateur de Virtronics, payant mais pas excessif et très prometteur, ou encore Virtual Breadboard. Mais chacun avec ses inconvénients, souvent trop orienté Arduino ou pas assez, restreignant les possibilités de gérer un gros projet.

Comme vous pouvez le constater, l'écosystème Arduino est très complexe et il est assez difficile de savoir par où commencer et quels sont les bons outils pour des besoins bien précis.

Donc, si vous avez réussi à lire jusqu'ici et pour résumer, que faire? : pour rappel, mon besoin premier étant d'avoir la faculté de suivre l'exécution de mon code pour le débugger, il ne me reste plus qu'une solution, du côté matériel.

Voici donc AVR Dragon! Il s'agit d'une carte créée par la société Atmel elle-même afin d'offrir des fonctionnalités de debug et monitoring du code s'exécutant sur AVR. Toute une histoire!

Pour ne pas faire trop long, je présenterai cette carte dans un prochain article. D'ici là, n'hésitez pas à me faire des retours sur vos diverses expériences avec le développement pour Arduino.


Fichier(s) joint(s) :



Xbee sans Arduino

Comme vous l'aurez remarqué dans mon article précédent, je me lance dans les montages électroniques, basés sur la platine Arduino et tout son écosystème.

Aujourd'hui je vous propose d'aller plus loin : nous allons voir comment établir une communication sans fil grâce aux modules radio Xbee. Le projet ici est de recevoir sur PC les mesures d'un capteur de température distant.

Matériel nécessaire


Module Xbee
Série 2 (x2)
Carte interface Adaptateur USB-miniUSB

Mon petit conseil pour l'achat de tout ceci est de commander sur le site Matlog : un revendeur français très bien équipé garantissant une livraison rapide.

Vous aurez également besoin du logiciel de configuration X-CTU fourni par Digi, fabricant des modules Xbee et d'un petit capteur de température (LM35 ici).

Principes

Les modules XBee permettent de créer des réseaux, du plus simple au plus complexe, Peer-to-peer ou maillé. Pour ce faire, ils peuvent être configurés pour jouer différents rôles : endpoint, routeur ou coordinateur (voici une bonne explication des différents rôles). Dans notre montage, nous allons avoir besoin d'un routeur (qui fera office d'endpoint) et d'un coordinateur.

Le montage

Avant de continuer, voici un aperçu du résultat :

Comme vous le voyez sur la première image, aucun micro-contrôleur n'est nécessaire. Nos deux modules radio communiquent directement et le routeur sera programmé pour envoyer à intervalles régulier la valeur du capteur de température. Le coordinateur est relié au PC directement via un câble USB et identifié par une petite pastille (par commodité). La seconde image montre en détail le montage : le fil blanc permet d'indiquer au module la valeur de référence du courant utilisé, afin qu'il puisse nous donner une valeur relative cohérente du signal reçu sur la broche 20 (première broche analogique du Xbee, fil rouge à droite). Le reste du câblage n'est que de l'alimentation par la pile 9V.

Configuration

L'étape la plus complexe. Nous allons voir comment attribuer chaque rôle aux modules et leur permettre de communiquer entre eux. Les XBee étant des modems radio, ils répondent à des commandes normalisées commençant par "AT". Je ne vais cependant pas m'étendre sur ce point dans cet article, mais plutôt utiliser un logiciel permettant de "masquer" ce niveau.

Commençons par le coordinateur. Une fois le premier module relié au PC, il faudra installer le driver, comme indiqué sur le site de Digi et lancer X-CTU :

Dans le permier onglet "PC Settings", il suffit de sélectionner la ligne correspondant au port COM sur lequel est branché notre XBee. Ensuite, dans l'onglet "Modem Configuration", choisir la configuration présentée : Modem XB24-ZB (pour les modules 2.4GHz Zigbee serie 2) et la dernière version du firmware. Cliquer ensuite sur Write pour envoyer cette première configuration au module.

Cliquer ensuite sur Read pour récupérer les valeurs par défaut définies par le module afin de pouvoir les ajuster à notre besoin. Pour faire simple, nous n'allons ici modifier que l'adresse du réseau (PAN ID ou ID) : n'importe quelle valeur aléatoire suffit. Cliquer de nouveau sur Write après modification pour mémoriser les changements.

Et voilà! Le point principal de notre réseau est configuré! Passons maintenant au routeur, qui va nous transmettre les informations du thermomètre. Même manipulation donc pour ce qui est du raccordement au PC et à la connexion dans X-CTU. Mais cette fois-ci, il faut indiquer la configuration suivante :

On affecte le même identifiant de réseau (ID) que le coordinateur (456) et les adresses de destination (DH et DL) correspondant à celles du coordinateur (0). La section "I/O settings" permet d'indiquer la gestion des entrées/sorties : ici nous choisissons de configurer le broche 20 (DI0) en entrée analogique (mode 2) ce qui donne DI02. Enfin, dans la sous-section "I/O sampling", nous définissons les intervalles de temps auxquels le routeur nous enverra des données : 12C en hexadécimal = 300 millisecondes.

Ensuite... Il ne reste plus qu'à rebrancher le tout! Tout devrait fonctionner. J'avoue tout de même qu'il m'a fallu pas mal d'heures de recherche pour réunir tous ces éléments. Si vous avez des soucis, n'hésitez pas à m'en parler en commentaire j'essaierai de vous aider.

Vous pouvez observer les données reçue via le Terminal de XCTU :

Je passe un peu sur l'analyse de la trame hexadécimale reçue (il y a des documents très complets sur le site de Digi à ce sujet) mais vous pouvez voir que la valeur entourée correspond à ce que nous envoie le capteur de température : D9 = 217. Selon la spécification de notre capteur LM35, pour trouver la valeur réelle de la température, il faut effectuer un calcul tout simple : 10mV par °C donc pour nous 217 / 10 = 21.7°C :D

Pour aller plus loin...

Ne nous arrêtons pas en si bon chemin! Comme vous l'avez vu, notre module capteur de température est autonome et alimenté par une pile. Même si les XBee sont assez peu énergivores, il faut tout de même rapidement penser à la gestion de l'énergie et à la réduction de leur consommation. Pour ce faire, ils disposent d'un mode veille qui augmente considérablement la durée d'utilisation des batteries.

Voici une brève description des différents modes :

  • ATSM 0 : Pas de veille
  • ATSM 1 : "pin wake mode" : la mise en veille ou le réveil sont gérées par l'alimentation ou l'extinction de la broche 9
  • ATSM 2 et 3 : réservés, non utilisables
  • ATSM 4 : Entrée et sortie de veille par intervalle de temps
  • ATSM 5 : combinaison des 1 et 4

A ceci nous allons ajouter un temps défini pendant lequel le module restera allumé avant de retourner en veille (afin de garantir la transmission des paquets).

Dans notre exemple, nous allons paramétrer une veille de 10 secondes et un temps d'attente de 1,5 seconde. Ce qui nous donne dans XCTU :

  • SM 4
  • SP 3E8 (= 1000 multiplié par 10 = 10 secondes)
  • ST 96 (= 150 multiplié par 10 = 1.5 seconde)

Et là... Il se peut que cela ne fonctionne pas! ;) En effet, à chaque sortie de veille, le routeur doit s'identifier de nouveau auprès du coordinateur (qui a considéré qu'au bout de quelques secondes de non réponse il ne faisait plus partie du réseau), ce qui prend normalement quelques secondes, impossible au vu de notre temps d'activité de 1.5 secondes. Pour y remédier, comme indiqué dans cet article, il faut modifier la configuration du coordinateur et augmenter les valeurs de SP et SN afin de couvrir les cycles de veille du routeur.

Cette fois notre matériel est prêt : nous recevons toutes les 10 secondes la température du module distant.

Et la cerise sur le gateau

Tout ceci est bien, mais il serait plus intéressant de recevoir ces informations dans un programme Java, afin de pouvoir s'en servir dans n'importe quel contexte. Pour cela, rien de plus simple : XBEE API. Quelques lignes de code plus tard :


import com.rapplogic.xbee.api.XBee;
import com.rapplogic.xbee.api.XBeeException;
import com.rapplogic.xbee.api.zigbee.ZNetRxIoSampleResponse;

public class TempRead {
 
 public static void main(String[] args) throws XBeeException {
  XBee xbee = new XBee();         
  xbee.open("COM6", 9600);
                          
  while (true) {
   ZNetRxIoSampleResponse ioSample = (ZNetRxIoSampleResponse) xbee.getResponse();
//      System.out.println("We received a sample from " + ioSample.getRemoteAddress16());     
      if (ioSample.containsAnalog()) {
          System.out.println("Temperature : " + ioSample.getAnalog0()/10.0);
      }
  }
 }

}

Ouf, enfin terminé! Avec cette API, nous sommes capables de savoir quel module nous a envoyé des données et de récupérer directement la valeur reçue sur la broche 20.

J'espère vous avoir donné assez d'éléments pour vous lancer de votre côté, bon courage!

Sources


Fichier(s) joint(s) :



Jenuino : Jenkins is watching you

L'intégration continue c'est bien, mais l'Extreme Feedback c'est mieux! :)

Le but de cet article est d'attiser votre curiosité et vous montrer qu'il est assez simple de rendre certains outils plus fun! Nous allons voir comment fabriquer un "totem" nous affichant le résultat des builds programmés sur un serveur Jenkins. J'ai baptisé ce projet : JENUINO

Avant tout, un aperçu du résultat :

Build OKBuild en coursBuild KO

Comme vous pouvez le voir, ce totem se compose :

  • D'un écran LCD affichant un petit message
  • D'une lumière colorée selon l'état du build
  • D'un bouton pour relancer un build

Mais qu'est-ce qui se cache dessous??... Et bien un Arduino!

Cette petite plateforme électronique OpenSource italienne permet de réaliser des montages en un tour de main, sans connaissance particulière en la matière.

Il sera relié au PC via un câble USB. Notre programme lui est configurable via une petite interface "maison" :

Et maintenant, on se lance!

Côté Java

Plusieurs éléments sont nécessaires pour accomplir ce montage : une librairie pour la communication via le port USB, l'API Jenkins pour accéder aux informations des projets et quelques éléments d'interface basiques.

Communication USB

Les ports USB étant des ports série (COM), nous allons utiliser la librairie RXTX. Elle se compose de deux éléments importants : le JAR bien sûr, mais également les librairies système (.dll sous Windows) permettant à la JVM d'utiliser les ports USB. Toutes les informations pour l'installation sont disponibles sur le site officiel. Ensuite, quelques lignes de code :


import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;

public class SerialCom implements SerialPortEventListener {
 SerialPort serialPort;

 private BufferedReader input;
 private OutputStream output;
 private static final int TIME_OUT = 2000;
 private static final int DATA_RATE = 9600;

 public void initialize(String portId) {
  try {
   // open serial port, and use class name for the appName.
   serialPort = (SerialPort) CommPortIdentifier.getPortIdentifier(portId).open(this.getClass().getName(),
     TIME_OUT);

   // set port parameters
   serialPort.setSerialPortParams(DATA_RATE, SerialPort.DATABITS_8,
     SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);

   // open the streams
   input = new BufferedReader(new InputStreamReader(
     serialPort.getInputStream()));
   output = serialPort.getOutputStream();

   // add event listeners
   serialPort.addEventListener(this);
   serialPort.notifyOnDataAvailable(true);
  } catch (Exception e) {
   System.err.println(e.toString());
  }
 }

 /**
  * This should be called when you stop using the port. This will prevent
  * port locking on platforms like Linux.
  */
 public synchronized void close() {
  if (serialPort != null) {
   serialPort.removeEventListener();
   serialPort.close();
  }
 }

 /**
  * Handle an event on the serial port. Read the data and print it.
  */
 public synchronized void serialEvent(SerialPortEvent oEvent) {
  if (oEvent.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
   try {
    int inputLine = input.read();
    // flush com data
    input.ready();
   } catch (Exception e) {
    System.err.println(e.toString());
   }
  }
 }
}

La méthode initialize utilise le nom du port ("COM5" par exemple) pour initialiser les flux entrants et sortants, selon le débit DATA_RATE (9600 kbps par défaut pour l'USB). Cette classe est également capable de réagir aux évènements entrants grâce à l'interface SerialPortEventListener.

Lors du premier lancement, il se peut que vous soyez confrontés à l'erreur :

java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path thrown while loading gnu.io.RXTXCommDriver
Exception in thread "main" java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path
 at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
 at java.lang.Runtime.loadLibrary0(Runtime.java:845)
 at java.lang.System.loadLibrary(System.java:1084)

Cela signifie (sous Windows) qu'il faut copier "rxtxSerial.dll" sous JAVA_HOME/jre/bin. Il sera également nécessaire d'installer le driver Arduino, comme expliqué sur cette page.

Ensuite, il suffira de lire ou d'écrire dans les Stream de la même manière que pour l'écriture/lecture de fichier ou quelconque autre opération basique en Java.

L'interface de configuration

Je ne vais pas m'attarder sur ce point, car il ne s'agit que d'un exemple, basé sur :

  • java.awt.SystemTray : pour ajouter l'icône et son menu contextuel dans la zone de notifications
  • java.util.Timer : pour mettre en place une tâche de fond qui interroge à intervalles réguliers le serveur Jenkins et met à jour le totem

L'API Jenkins

Jenkins fournit la possibilité d'accéder et de manipuler les jobs via JSON et/ou XML et une API relativement simple. Voici donc un client Jenkins en quelque lignes :


import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

public class JenkinsClient {
 
 private final String API_URL = "api/xml";
 private String serverURL;
 private String job;
 
 public JenkinsClient(String url, String jobName) {
  if(url==null) {
   serverURL = "http://localhost:8080/jenkins/";
  } else {
   serverURL = url;
  }
  job = jobName;
 }

 public BuildState findLastBuildState() throws Exception {
  BuildState state = null;
  // every Hudson model object exposes the .../api/xml
  URL url = new URL(serverURL+"job/"+job+"/lastBuild/"+API_URL);
  
  // read it into DOM.
  Document dom = new SAXReader().read(url);
  
  String elementresult = dom.getRootElement().elementText("result");
  if(elementresult!=null) {
   state = BuildState.fromValue(elementresult.toString());
  } else if(dom.getRootElement().elementText("building")!=null) {
   state = BuildState.fromValue("building");
  }
  return state;
 }
 
 public boolean jobExists() {
  boolean exists = false;
  try {
   // every Hudson model object exposes the .../api/xml
   URL url = new URL(serverURL+API_URL);
   
   // read it into DOM.
   Document dom = new SAXReader().read(url);
   
   // scan through the job list and print its status
   for (Element jobElt : (List) dom.getRootElement().elements("job")) {
    exists |= jobElt.elementText("name").equals(job);
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
  return exists;
 }
 
 public void triggerBuild() {
  try {
   URL url = new URL(serverURL+"job/"+job+"/build");
   URLConnection conn = url.openConnection();
   InputStream is = conn.getInputStream();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
}

Avec ceci, nous serons capables de vérifier l'existence d'un projet sur le serveur, de récupérer l'état du dernier build et en déclencher de nouveaux.

Côté Arduino

Pour cette seconde étape, il faut réaliser le montage électronique et programmer le micro-controlleur de la plateforme afin de réagir à nos évènements via le port USB.

Hardware

Ci-dessous un schéma expliquant l'exemple de mon montage :

Inspiré par les montages publiés sur le net, celui-ci est bien moins complexe qu'il n'en a l'air. La présence de la platine d'essai au centre est justifiée par le fait que ce montage n'est que temporaire et il est généralement plus simple d'utiliser ce système que de vouloir raccorder directement les composants entre eux, surtout lorsqu'il faut gérer la "stabilité" sans soudure...

Alors un peu de dextérité et d'ingéniosité, et le tour est joué!

Software

Au niveau logiciel, Arduino se base sur le langage Processing, proche du C mais basé sur un environnement Java, donc très facilement accessible. Un programme de base se compose de deux méthodes clés :

  • setup() : appelée au démarrage de la plateforme (mise sous tension) pour configurer et initialiser le programme.
  • loop() : appelée en boucle et en permanence tout au long de l'exécution.

Voici un bref aperçu de l'environnement de développement, tout aussi simple que le langage :

Mon but ici étant simplement de piquer votre curiosité, je ne vais pas m'étendre plus sur la présentation du langage et de l'environnement, tout ceci étant très facile à trouver et très bien expliqué sur les sites français ou anglais officiels. Je vous invite tout de même à prendre quelques minutes pour regarder cette vidéo de présentation du projet Arduino par un de ses fondateurs. Je suis sûr que cela vous donnera envie!

Pour ceux que cela intéresse, mon projet est disponible sur Sourceforge : Jenuino

Enfin, si vous voulez vous lancer, pour ce qui est de l'achat du matériel je vous conseil le site Deal Extreme, proposant des kits de démarrage assez complets et a bon prix (attention tout de même aux délais de livraison qui peuvent être longs).


Fichier(s) joint(s) :