Le Yún, l'Uno, et le XBee

par gsavin

L’idée de ce projet est tout d’abord d’aborder la communication entre deux Arduinos via des modules XBee de série 1, ainsi que la cohabitation entre le module XBee et le Arduino Yún. Un Arduino Uno est équipé d’un capteur (de lumière en l’occurrence, mais cela peut être n’importe quoi) dont il va transmettre régulièrement la valeur à un Arduino Yún grâce aux modules XBee. Le Yún rendra ensuite la valeur accessible via WiFi à ceux qui souhaiteront y accèder !

Une chose à savoir : le port série du Yún, utilisé normalement par le XBee, est ici utilisé pour la communication entre les deux processeurs (le 32U4 et le AR9331 utilisé pour la partie Linux). Il faut donc router les ports Rx/Tx du XBee vers d’autres pins, et utilisé un port série logiciel.

Préliminaires

Nous allons créer deux projets : le capticiole qui correspondra au capteur, et le coordiciole qui recevra les données du premier pour les retransmettre via WiFi. Pour ma part, je gère les projets via PlatformIO. Cet outil permet aussi de gérer les bibliothèques annexes, et nous en aurons besoin de deux :

  • AltSoftSerial, que nous allons utiliser pour émuler un port série logiciel et qui est plus performant que SoftSerial. L’identifiant de cette bibliothèque est “73” (on la récupère via la commande platformio lib search "altsoftserial") ;
  • xbee-arduino, qui permet de contrôler le module xbee en mode api depuis le Arduino. Son identifiant est “6”.

Pour créer les projets (Linux inside) :

platformio lib install 6
platformio lib install 73
for projet in capticiole coordiciole; do
mkdir $projet
pushd $projet
platformio init
popd
done

PlatformIO génère un fichier platformio.ini dans chaque projet qui lui permet de définir comment le projet sera construit. Les configurations des deux projets seront similaires, la différence étant sur le nom de la board.

Pour le capticiole :

[env:uno]
platform = atmelavr
framework = arduino
board = uno
build_flags = -DBEE_ADDRESS="5678"

Pour le coordiciole :

[env:yun]
platform = atmelavr
framework = arduino
board = yun
build_flags = -DBEE_ADDRESS="1874"

Capticiole : le capteur sur le Uno

Pour ce prototype, nous utilisons donc un Arduino Uno sur lequel nous ajoutons une shield “Wireless SD” qui viendra accueillir le module XBee. À noter que l’on peut très bien se passer de cette shield mais qu’il vous faudra un adaptateur (de ce type par exemple) si vous souhaitez l’insérer directement sur votre breadboard, l’espacement entre les pins n’étant pas le même.

En complément du capteur photosensible, on ajoute deux petites leds qui nous aideront à suivre le fonctionnement du prototype.

Circuit

Circuit

Code

Dans la première partie du code, on commence par inclure les entêtes nécessaire à PlatformIO pour compiler correctement. Puis, il faut inclure la bibliothèque XBee que nous avons installé précédemment et qui nous permettra de communiquer. On définit les leds rouge et verte sur les pins 10 et 9 tel que présenté sur le circuit, ainsi que la sonde connectée sur l’entrée analogique A0.

//
// Entête pour PlatformIO
//
#ifdef ENERGIA
  #include "Energia.h"
#else
  #include "Arduino.h"
#endif

#include <XBee.h>

//
// Les pins des leds verte et rouge
//
#define R_LED 10
#define G_LED  9

//
// La sonde reliée au capteur
//
#define SONDE A0

On charge ensuite les fonctionnalitées XBee.

XBee xbee = XBee();

//
// Prototypes des fonctions
//
void led_flash(int, uint32_t, uint32_t);

void xbee_configure();
void xbee_transmit();

Dans le “setup” Arduino, on démarre la connexion série qui sera utilisée pour faire communiquer le module XBee avec le Arduino, puis on configure le module. On démarre ensuite notre objet xbee en le nourrissant du port logiciel que nous avons créé. Sans oublier des configurer les deux pins de nos leds en sortie !

void setup() {
	Serial.begin(9600);
	xbee_configure();
	xbee.begin(Serial);

	pinMode(G_LED, OUTPUT);
	pinMode(R_LED, OUTPUT);
}

Dans la fonction “loop”, on se contente d’emettre un signal sur la led verte, qui nous permettra de suivre le bon fonctionnement du prototype, et de transmettre la valeur de la sonde.

void loop() {
	led_flash(G_LED, 500, 1);
	xbee_transmit();
}

Vient désormais une partie un peu sombre, la configuration du module XBee, à grand coup de commandes AT. Comme nous utilisons ici des série 1, il nous faut un coordinateur et un (ou plusieurs) terminaux. Notre capticiole sera bien sûr un appareil terminal, tandis que le coordiciole sera la coordinateur (l’idée étant à terme d’avoir plusieurs capticioles envoyant les informations, mais ceci est une autre histoire…). Les commandes AT sont directement écrites sur le port série. Elles se composent d’un préfixe “AT” (whaou), suivi directement de la commande (“RE”, “AP”…), suivi éventuellement d’une valeur lorsque la commande nécessite un paramètre.

Par défaut, la configuration est ré-initialisée au démarrage, mais il est possible de la sauvegarder dans la mémoire non-volatile du XBee grâce à la commande AT “WR”. Toutes ces infos proviennent de la partie configuration du site de la bibliothèque xbee-arduino.

void xbee_configure() {
	char thisByte = 0;

	Serial.print("+++");

	while (thisByte != '\r' && thisByte != '\n') {
		if (Serial.available() > 0)
			thisByte = Serial.read();
	}

	// On restaure les paramètres par défaut
	Serial.print("ATRE\r");
	// Active le mode API. Sans cela, la bibliothèque xbee ne fonctionnera pas,
	// ou du moins très mal !
	Serial.print("ATAP2\r");
	// Configure le xbee en mode "terminal"
	Serial.print("ATCE0\r");
	// Configure l'identifiant du réseau (0x1111).
	Serial.print("ATID1111\r");
	// On configure ici l'adresse du module, qui doit être unique.
	// Le paramètre BEE_ADDRESS est définit à la compilation,
	// on peut l'apercevoir dans le platformio.ini.
	Serial.print("ATMY");
	Serial.print(BEE_ADDRESS);
	Serial.print("\r");
	// Configure le canal de transmission 0x0C
	Serial.print("ATCH0C\r");
	// On quitte le mode de configuration
	Serial.print("ATCN\r");
}

Enfin, la fonction de transmission des données est une adaptation de l’exemple disponible sur le site de xbee-arduino. Les données créées contiennent la valeur de la sonde sur deux octets, puis un libellé indiquant le type de capteur.

void xbee_transmit() {
	uint8_t label[] = "LIGHT";
	uint8_t payload[sizeof(label)+2];
	uint16_t sonde = analogRead(SONDE);

	payload[0] = sonde >> 8 & 0xFF;
	payload[1] = sonde & 0xFF;
	payload[2] = sizeof(label);

	for (int i=0; i<sizeof(label); i++)
		payload[i+2] = label[i];

	Tx16Request tx;
	TxStatusResponse txStatus = TxStatusResponse();

	tx = Tx16Request(0x1874, payload, sizeof(payload));
	xbee.send(tx);

	if (xbee.readPacket(500)) {
		if (xbee.getResponse().getApiId() == TX_STATUS_RESPONSE) {
			xbee.getResponse().getZBTxStatusResponse(txStatus);
			//
			// Get the delivery status, the fifth byte
			//
			if (txStatus.getStatus() == SUCCESS) {
				//
				// Success. time to celebrate
				//
				led_flash(G_LED, 500, 10);
			} else {
				//
				// The remote XBee did not receive our packet.
				// Is it powered on?
				//
				led_flash(R_LED, 2000, 50);
			}
		}
	} else if (xbee.getResponse().isError()) {
		//
		// nss.print("Error reading packet. Error code: ");
		// nss.println(xbee.getResponse().getErrorCode());
		// or flash error led
		//
		led_flash(R_LED, 2000, 50);
	} else {
		//
		// Local XBee did not provide a timely TX Status Response.
		// Radio is not configured properly or connected
		//
		led_flash(R_LED, 1000, 50);
	}
}

Coordiciole : le coordinateur sur le Yún

Ce second prototype utilise lui un Arduino Yún qui comporte un second processeur permettant de faire tourner Linux et d’avoir un accès WiFi au prototype. Son rôle sera de récupérer les informations diffusées sur le réseau XBee et de les rendre accessible sur son réseau WiFi.

Du fait que le port série matériel du Yún soit indisponible, il nous faut router les pins Tx/Rx du XBee vers les pins 5 et 13 qui seront utilisés par AltSoftSerial. Si vous utilisez la shield Arduino “wireless”, il ne faut pas que les pins 0 et 1 de la shield soient connectés au Yún, il faut donc les plier très légèrement pour les empêcher de se connecter, ou déporter la shield comme sur le circuit qui suit.

Pour rendre les choses plus amusantes, on ajoute un registre à décalage (un 74HC595), qui va nous permettre de contrôler huit leds : une rouge et une verte pour suivre l’exécution du prototype, six jaunes afin d’afficher la valeur reçue.

Circuit

Circuit

Code

Comme dans la première partie, on commence par inclure les différentes entêtes, puis à définir les pins. DATA, LATCH, et CLOCK sont utilisés par le 74HC595. Pour allumer les leds, nous allons envoyer un entier codé sur 8bits, via le pin DATA, dont chaque bit correspondra à l’état d’une led. Les LED_* nous aideront dans cette tâche.

#ifdef ENERGIA
  #include "Energia.h"
#else
  #include "Arduino.h"
#endif

#include <AltSoftSerial.h>
#include <XBee.h>
#include <Bridge.h>

#define DATA 6
#define LATCH 7
#define CLOCK 10

#define XBEE_RX_PIN 13
#define XBEE_TX_PIN 5

#define LED_NONE 0x00
#define LED_G 0x01
#define LED_R 0x02
#define LED_1 0x04
#define LED_2 0x08
#define LED_3 0x10
#define LED_4 0x20
#define LED_5 0x40
#define LED_6 0x80

XBee xbee = XBee();
Rx16Response rx16 = Rx16Response();

AltSoftSerial altSerial;

int leds;

void shift_led(int);
void led_flash(int, int, int);

void xbee_configure();
void xbee_receive();
void xbee_decode_message(RxResponse&);

Lors de l’initialisation de l’Arduino, on commence par démarrer le XBee sur le port série logiciel définit par altSerial. On active ensuite les pins du 74HC595 en sortie. La dernière partie est la plus intéressante : Bridge correspond au lien entre les deux processeurs du Arduino, si l’on souhaite que les deux communiquent, il faut donc le démarrer. Cette dernière étape peut prendre quelques secondes, c’est pourquoi j’utilise les leds rouge et verte pour indiquer l’évolution du processus (les deux sont allumées en permanence pendant le démarrage du pont, puis clignote pendant quelques temps une fois que l’initialisation est terminée).

void setup() {
	pinMode(XBEE_RX_PIN, INPUT);
	pinMode(XBEE_TX_PIN, OUTPUT);
	altSerial.begin(9600);
	xbee_configure();
	xbee.begin(altSerial);

	pinMode(LATCH, OUTPUT);
	pinMode(CLOCK, OUTPUT);
	pinMode(DATA,  OUTPUT);

	shift_led(LED_G | LED_R);
	Bridge.begin();
	shift_led(LED_NONE);
	led_flash(LED_G | LED_R, 250, 10);
}

La boucle est relativement simple : on tente de lire depuis le xbee, puis après une petite pause, on recommence. Boulot, duino, boulot.

void loop() {
	xbee_receive();
	delay(100);
}

Les fonctions d’allumage des leds sont un peu différentes du fait de l’utilisation du 74HC595. shift_led() permet d’envoyer les informations au registre, tandis que led_flash() permet de faire clignoter ceraines leds tout en laissant tel quel l’état des autres.

void shift_led(int value) {
	digitalWrite(LATCH, LOW);
	shiftOut(DATA, CLOCK, MSBFIRST, value);
	digitalWrite(LATCH, HIGH);

	leds = value;
}

void led_flash(int led, int length, int count) {
	int step = length / ( 2 * count );

	for (int i=0; i<count; i++) {
		shift_led(leds | led);
		delay(length / 2);
		shift_led(leds & (0xFF ^ led));
		delay(length / 2);
	}
}

La configuration du module XBee via les commandes AT est très similaire à la précédente. On active simplement ce module ci en tant que coordinateur (via la commande “CE” avec la paramètre 1 cette fois), et on change l’adresse du module (rappelez-vous, chaque module doit avoir sa propre adresse, mais l’identifiant du réseau doit être le même).

void xbee_configure() {
	char thisByte = 0;

	altSerial.print("+++");

	while (thisByte != '\r' && thisByte != '\n') {
		if (altSerial.available() > 0)
			thisByte = altSerial.read();
	}

	altSerial.print("ATRE\r");
	altSerial.print("ATAP2\r");
	altSerial.print("ATCE1\r");
	altSerial.print("ATMY");
	altSerial.print(BEE_ADDRESS);
	altSerial.print("\r");
	altSerial.print("ATID1111\r");
	altSerial.print("ATCH0C\r");
	altSerial.print("ATCN\r");
}

Pas de révélation ici, ce morceau est aussi basé sur l’exemple de xbee-arduino. On fait clignoter la led verte une fois au préalable, histoire de montrer que tout se passe bien. Puis on tente de lire une réponse xbee, et s’il y en a une de disponible, on l’en occupe. En cas d’erreur, la led rouge clignotera.

void xbee_receive() {
	led_flash(LED_G, 500, 1);
	xbee.readPacket();

	if (xbee.getResponse().isAvailable()) {
		//		
		// Got something
		//
		if (xbee.getResponse().getApiId() == RX_16_RESPONSE) {
			//
			// Got a rx packet
			//
			xbee.getResponse().getRx16Response(rx16);
			xbee_decode_message(rx16);
		} else {
			// not something we were expecting
			led_flash(LED_R | LED_1, 100, 50);
		}
	} else if (xbee.getResponse().isError()) {
		led_flash(LED_R | LED_2, 100, 50);
	}
}

Enfin, la gestion des données. On récupère la valeur transmise, ainsi que le libellé. On le stocke sur la mémoire du second processeur grâce au Bridge, puis on allume les leds de manière à refléter la valeur réçue. Une fois la valeur stockée dans la mémoire du AR9331, on peut y accéder directement en se connectant au réseau WiFi généré par le Yun et en accédant à l’adresse http://arduino.local/data/get/. Vous obtiendrez un tableau au format json contenant l’ensemble des données stockées sur le Yun.

void xbee_decode_message(Rx16Response& r) {
	uint8_t* data = r.getData();
	uint16_t value = data[0] << 8 | data[1];
	uint8_t label_length = data[1];
	uint8_t level = 6 - map(value, 0, 1023, 0, 6);
	uint8_t level_led = leds & (LED_G | LED_R);

	char message[label_length];

	for (int i=0; i<label_length; i++)
		message[i] = static_cast<char>(data[i+2]);

	switch(level) {
	case 6: level_led |= LED_6;
	case 5: level_led |= LED_5;
	case 4: level_led |= LED_4;
	case 3: level_led |= LED_3;
	case 2: level_led |= LED_2;
	case 1: level_led |= LED_1;
	default: break;
	}

	Bridge.put(message, String(value));

	shift_led(level_led);
	led_flash(LED_G, 100, 5);
}

Lancement

Il faut désormais compiler et téléverser les programmes sur les Arduinos. Pour cela, on branche on utilise la commande platformio run, en se plaçant dans le repertoire correspondant au prototype connecté, qui va compiler votre code, puis si tout se passe bien, et on relance la commande avec la cible “upload”, platformio run --target upload. Ne pas oublier de passer la shield wireless du capticiole en mode usb pendant le transfère, puis de la repasser en mode micro par la suite.

Liens

Nous contacter

Vous pouvez nous contacter à cette adresse ou utiliser le formulaire ci-dessous.