Communication entre deux nœuds DW1001 avec le code Ranger
I. Introduction
Ce TP se focalise sur la communication entre deux nœuds DW1001 grâce à la bibliothèque Ranger.
Objectifs du TP
Le but de ce TP est de vous familiariser avec l’utilisation de la bibliothèque Ranger pour rĂ©aliser des communications Ă courte portĂ©e en utilisant la technologie UWB (Ultra-Wideband). Vous serez amenĂ© Ă utiliser les modules DecaDuino pour Ă©tablir des communications entre diffĂ©rents nĹ“uds et mesurer les distances entre eux.
Matériel Utilisé
- Un ordinateur avec une connexion internet
- Des nœuds DWM1001 disponibles sur la plateforme LOCURA4IoT
II. Présentation du Matériel et des Bibliothèques
Les Cartes DW1001
Les cartes DW1001 sont des cartes sans fil basées sur la technologie UWB (Ultra Wideband), qui permet de mesurer avec précision les distances entre les différents nœuds.
La Technologie UWB (Ultra-Wideband)
a. Concepts de base de l’UWB
Dans cette section, nous explorerons les concepts fondamentaux de la technologie UWB, qui joue un rôle clé dans les communications sans fil et la localisation précise.
- Principes de fonctionnement : L’UWB utilise des impulsions brèves et Ă©nergĂ©tiques pour transmettre des donnĂ©es sur une large gamme de frĂ©quences.
- Avantages en communication : L’UWB offre une haute rĂ©solution en distance, une faible consommation d’Ă©nergie et une immunitĂ© aux interfĂ©rences.
b. Protocoles UWB
Dans cette partie, nous aborderons les protocoles UWB qui déterminent comment les nœuds communiquent et mesurent les distances. Notre focus sera sur le protocole Two-Way Ranging (TWR).
- Présentation du Two-Way Ranging (TWR) : Le TWR permet des mesures précises de distance en utilisant des échanges bidirectionnels et des mesures de temps de vol.
- Utilisation du TWR avec les nœuds DW1001 : Les nœuds DWM1001 exploitent la puissance du TWR pour la localisation précise et la communication fiable à courte portée.
Bibliothèque Ranger
La bibliothèque Ranger est une bibliothèque logicielle spécialement conçue pour simplifier la communication UWB en utilisant les modules DecaDuino. Elle est conçue pour configurer les modules, établir des communications et effectuer des mesures de distances.
III. Configuration de l’Environnement de DĂ©veloppement
PrĂ©paration de l’Environnement Arduino IDE
Pour commencer, nous devons configurer l’environnement de dĂ©veloppement Arduino IDE pour travailler avec les nĹ“uds DW1001. Suivez ces Ă©tapes :
- TĂ©lĂ©charger et installer l’IDE Arduino sur votre ordinateur, si ce n’est pas dĂ©jĂ fait. L’IDE Arduino est disponible gratuitement sur le site officiel d’Arduino.
Installez la chaîne de compilation en suivant les instructions sur cette page.
Installation des Bibliothèques Requises
Pour pouvoir flacher le code Ranger sur les nœuds DW1001, nous devons installer les bibliothèques nécessaires. Suivez ces étapes :
- Téléchargez le fichier ranger_dependencies.zip.
- Extrayez son contenu dans le dossiez “libraries” de votre installation Arduino
- Astuce : vous trouverez le chemin oĂą se situe de ce dossier dans l’IDE Arduino : PrĂ©fĂ©rences > Emplacement du carnet de croquis
- Note : il faut que les dossiers suivants se retrouvent directement dans le dossier “libraries”
- base64
- DecaDuino
- Ranger
- DWM100x_id/
- printfserial
Ces bibliothèques fournissent les fonctionnalités nécessaires pour la communication UWB avec les nœuds DW1001.
Configuration de la Carte DW1001
Maintenant que les bibliothèques sont installées, nous devons configurer Arduino IDE pour reconnaître la carte DW1001. Suivez ces étapes :
- Allez dans le menu “Outils” (Tools).
- SĂ©lectionnez “Type de carte” (Board) et choisissez “DecaWave DWM1001 Module Development Board”.
IV. Téléversement du Code sur les Nœuds DW1001
Maintenant que l’environnement de dĂ©veloppement est configurĂ©, vous pouvez tĂ©lĂ©verser le code Ranger sur les nĹ“uds DW1001. Voici comment procĂ©der :
- Ouvrez le fichier > Exemples > ranger > RangerInitTest dans Arduino IDE.
Cliquez pour afficher le code complet
#define CLIENT_RANGING_PERIOD 500 //500 //ms. Client performs a ranging cycle every x ms
//#define CLIENT_ADDRESS -1 // all nodes are clients (performs n*(n-1) rangings...)
#define CLIENT_ADDRESS 183 // this node is the client
int delayBetweenAnchors = 20; //50; //ms
#include <SPI.h>
#include <DecaDuino.h>
#include <Ranger.h>
#include <printfToSerial.h>
#ifdef UWB_MODULE_DWM1001 // DecaDev, Yahu, Nomi and others DWM1001 based boards
#include <DWM100x_id.h>
#include <stdio.h>
#else
#include <WiNoIO.h>
#include <EEPROM.h>
#endif
#ifdef __MK20DX256__ // PJRC Teensy based boards aka DecaWiNo
#include <WiNoIO.h>
#include <EEPROM.h>
WiNoIO wino;
DecaDuino decaduino;
#else
DecaDuino decaduino(SS1,DW_IRQ);
#endif
uint32_t rangingNumber;
uint8_t txData[1024];
uint8_t rxData[1024];
uint8_t sqn = 0;
uint16_t rxLen;
uint32_t softwareInterruptDelay;
uint32_t prevSoftwareInterrupt = 0;
uint16_t nodeAddress;
// Expérience de démo classique
//uint16_t anchors[] = {170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 182, 184};
// Noeuds du côté nord du labo (rail 7m)
//uint16_t anchors[] = {100, 101, 102, 103, 104, 105, 106, 107, 109, 110, 111, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 184};
uint16_t anchors[] = {182, 184, 170, 171, 172, 175, 176, 177, 178, 100, 101};
//uint16_t anchors[] = {182, 184, 170, 171, 172, 175, 176, 177, 178};
// Noeuds en GIM
//uint16_t anchors[] = {274, 275, 276, 277, 278, 279, 280, 281};
//uint16_t anchors[] = {115, 185, 186, 187, 188, 189};
//uint16_t anchors[] = {254, 257, 258, 261, 262, 263, 264, 265, 266};
//uint16_t anchors[] = {255, 256, 257, 258, 259, 260, 261};
//uint16_t anchors[] = {119, 115, 186};
// uint16_t anchors[] = {183, 184, 100, 101, 104, 105, 106, 107, 109, 110, 111, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 182, 142, 143, 145, 146, 147, 148, 149, 150, 151, 152, 153};
// Noeuds du côté sud du labo
//uint16_t anchors[] = {142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153};
//uint16_t anchors[] = {115, 123, 188, 156, 259, 267};
int nbAnchors = sizeof(anchors)/sizeof(uint16_t);
int rangingPeriod = -1;
bool messageReceived = false;
Ranger ranger;
Ranger::getRangingRequestStatus_e rangingStatus;
Ranger::ranging_t ranging;
Ranger::message_t txMsg, rxMsg;
void prepareAndSendMessage ( Ranger::message_t *txmsg )
{
int i, l=0;
// handle TX power
// store previous value
bool previousPowerSettingIsManual = decaduino.isTxPowerManual();
uint32_t previousPowerSettingValue = decaduino.getTX_POWER();
// sets requested value
if (txMsg.manualTxPower){
decaduino.setManualTxPowerRaw(txMsg.manualTxPowerValue);
}
else {
decaduino.setSmartTxPower();
}
txMsg.trxTemperatureBegin = decaduino.getTemperature();
txMsg.trxVoltageBegin = decaduino.getVoltage();
decaduino.plmeRxDisableRequest();
decaduino.encodeUint16(nodeAddress, &txData[l]); l+=2;
decaduino.encodeUint16(txMsg.destination, &txData[l]); l+=2;
txData[l++] = ++sqn;
txData[l++] = txMsg.protocol;
for (i=0; i<txMsg.payloadLen; i++) txData[l+i] = txMsg.payload[i];
decaduino.pdDataRequest(txData, l+i);
while (!decaduino.hasTxSucceeded()) { decaduino.engine(); };
txMsg.tsTx = decaduino.getLastTxTimestamp();
txMsg.trxTemperatureEnd = decaduino.getTemperature();
txMsg.trxVoltageEnd = decaduino.getVoltage();
// resets TX power to previous value
if ( previousPowerSettingIsManual ){
uint8_t value = (previousPowerSettingValue & 0xff00) >> 8; // gets the value of TXPOWPHR register.
decaduino.setManualTxPowerRaw(txMsg.manualTxPowerValue);
}
else {
decaduino.setSmartTxPower();
}
}
void processHal()
{
// This function processes the Hardware Abstraction Layer (HAL) between DecaDuino and Ranger.
// Call the Decaduino Engine
decaduino.engine();
// Call the Ranger Engine
ranger.engine();
// Is there a message to send?
if ( ranger.getTxBufferStatus() )
{
// A new message has to be sent: disable RX, make packet, send and wait. DOES NOT RE-ENABLE RX.
int i, l=0;
prepareAndSendMessage(&txMsg);
ranger.setTxBufferStatus(Ranger::TX_STATUS_DONE);
}
// Enable RX ?
if ( ranger.getRxRequestStatus() )
{
rxMsg.trxTemperatureBegin = decaduino.getTemperature();
decaduino.plmeRxEnableRequest();
ranger.rxEnableRequest(false);
}
// Does a message has been received?
if ( decaduino.rxFrameAvailable() )
{
// A new message has been received: analyse-it
uint16_t sourceAddress = decaduino.decodeUint16(&rxData[0]);
uint16_t destinationAddress = decaduino.decodeUint16(&rxData[2]);
uint8_t sqn = rxData[4];
uint8_t msgType = rxData[5];
if ( (destinationAddress == nodeAddress) || (destinationAddress == 0xffff) )
{
// This message is for me or broadcast
rxMsg.source = sourceAddress;
rxMsg.destination = destinationAddress;
rxMsg.tsRx = decaduino.getLastRxTimestamp();
rxMsg.skew = decaduino.getLastRxSkew();
rxMsg.nlosIndication = decaduino.getNLOSIndication();
rxMsg.protocol = (Ranger::ranger_protocols_e)msgType;
rxMsg.RSSI = decaduino.getRSSI();
rxMsg.FP_POWER = decaduino.getFpPower();
rxMsg.PA_POWER = decaduino.getPeakPower();
for (int i=0; i<rxLen; i++) rxMsg.payload[i] = rxData[6+i];
rxMsg.trxTemperatureEnd = decaduino.getTemperature();
if ( rxMsg.protocol == Ranger::DATA_PROTOCOL )
{
messageReceived = true;
decaduino.plmeRxEnableRequest();
}
else ranger.newRxMessage();
}
else
{
// This message is not for me. Re-enable RX
decaduino.plmeRxEnableRequest();
}
}
}
void initBoard(void)
{
#ifdef __MK20DX256__ // PJRC Teensy based boards aka DecaWiNo
if (!wino.loadConfig() ) while (1);
SPI.setSCK(wino.getTrxSckPin());
wino.rgbDraw(0,0,0);
nodeAddress = wino.getLabel();
#endif
#ifdef ARDUINO_DWM1001_DEV // DecaDev board
pinMode(LED_RED_BOT,OUTPUT);
pinMode(LED_RED_TOP,OUTPUT);
pinMode(LED_BLUE,OUTPUT);
pinMode(LED_GREEN,OUTPUT);
digitalWrite(LED_GREEN,HIGH); // for ARDUINO_DWM1001_DEV, HIGH == LED off
digitalWrite(LED_BLUE,HIGH); // for ARDUINO_DWM1001_DEV, HIGH == LED off
digitalWrite(LED_RED_TOP,HIGH); // for ARDUINO_DWM1001_DEV, HIGH == LED off
digitalWrite(LED_RED_BOT,HIGH); // for ARDUINO_DWM1001_DEV, HIGH == LED off
nodeAddress = toNodeID(getDwMacAddrUint64());
#endif
#ifdef ARDUINO_YAHU
pinMode(LED_RED,OUTPUT);
pinMode(LED_GREEN,OUTPUT);
pinMode(LED_BLUE,OUTPUT);
nodeAddress = toNodeID(getDwMacAddrUint64());
#endif
}
void rgbDraw(int r, int g, int b)
{
#ifdef __MK20DX256__ // PJRC Teensy based boards aka DecaWiNo
wino.rgbDraw(r,g,b);
#endif
#ifdef ARDUINO_DWM1001_DEV // DecaDev board
if (r<16) digitalWrite(LED_RED_TOP, LOW); else digitalWrite(LED_RED_TOP, HIGH);
if (g<16) digitalWrite(LED_GREEN, LOW); else digitalWrite(LED_GREEN, HIGH);
if (b<16) digitalWrite(LED_BLUE, LOW); else digitalWrite(LED_BLUE, HIGH);
#endif
#ifdef ARDUINO_YAHU // Yahu and Nomi boards
analogWrite(LED_RED, r);
analogWrite(LED_GREEN, g);
analogWrite(LED_BLUE, b);
#endif
}
void setup()
{
Serial.begin(115200);
while ( !decaduino.init())
{
Serial.println("DecaDuino init failed... retry...");
#ifndef ARDUINO_DWM1001_DEV
wino.rgbDraw(255,0,0); // Red
#else
digitalWrite(LED_RED_BOT, HIGH);delay(100);digitalWrite(LED_RED_BOT, LOW);
#endif
delay(100);
}
if ( !ranger.init() )
{
Serial.println("Ranger init failed. Freeze.");
while(1) {
#ifndef ARDUINO_DWM1001_DEV
wino.rgbDraw(255,0,0); delay(50); wino.rgbDraw(0,0,0); delay(50);
#else
digitalWrite(LED_RED_TOP,HIGH); digitalWrite(LED_RED_BOT,LOW); delay(50); digitalWrite(LED_RED_TOP,LOW); digitalWrite(LED_RED_BOT,HIGH); delay(50);
#endif
}
}
#ifndef ARDUINO_DWM1001_DEV
nodeAddress = wino.getLabel();
#else
nodeAddress = toNodeID(getDwMacAddrUint64());
#endif
printf("My nodeAddress is %d\n", nodeAddress);
// Configure DecaDuino
decaduino.setRxBuffer(rxData, &rxLen);
/*
// Best PHY config for NLOS and long range
decaduino.setChannel(4);
decaduino.setPrf(64);
decaduino.setPcode(17);
decaduino.setDataRate(DW1000_DATARATE_850KBPS);
decaduino.setDecaWaveSFD();
decaduino.setPreambleLength(4096);
decaduino.enableNLOSTunings();
*/
// Configure Ranger
ranger.setDebug(false);
ranger.setTxBuffer(&txMsg);
ranger.setRxBuffer(&rxMsg);
ranger.setNodeAddress(nodeAddress);
ranger.enableTwrServer(true);
rgbDraw(255,0,255);
decaduino.setManualTxPower(COARSE_POWER_SETTING::COARSE_POWER_GAIN_0db,0);
// Various
if ( ( nodeAddress == CLIENT_ADDRESS ) || ( CLIENT_ADDRESS == -1) )
{
rangingPeriod = CLIENT_RANGING_PERIOD;
}
softwareInterruptDelay = rangingPeriod;
prevSoftwareInterrupt = millis();
rangingNumber = 0;
}
void loop()
{
processHal(); // Call the HAL for incoming/outcoming messages
// Periodic action (non blocking instructions)
if ( (millis() - prevSoftwareInterrupt) > softwareInterruptDelay)
{
prevSoftwareInterrupt = millis();
// This is the client node
if ( rangingPeriod != -1 )
{
if ( rangingNumber%nbAnchors == nbAnchors-1 )
{
// End of a ranging cycle: plan next cycle and sleep transceiver
softwareInterruptDelay = rangingPeriod;
}
else
{
// Next anchor
softwareInterruptDelay = delayBetweenAnchors;
}
rgbDraw(0,0,255);
ranging.distanceOfOneTofStep = RANGING_UNIT;
ranging.other = anchors[rangingNumber%nbAnchors];
ranging.protocol = Ranger::TWR_PROTOCOL;
ranger.rangingRequest(&ranging);
}
}
// Returns after a ranging process by Ranger
if ( ranger.rangingIndication(&rangingStatus) )
{
Serial.flush();
switch ( rangingStatus )
{
case Ranger::RANGING_STATUS_ERROR:
printf("Error while ranging with %d\n", ranging.other);
rgbDraw(255,0,0);
break;
case Ranger::RANGING_STATUS_DONE:
//printf("Result for this ranging with %04x:\n", ranging.other);
//ranger.printRanging(&ranging);
ranger.printRangingShortAsJson(&ranging);
printf("\r\n");
//ranger.printRangingShortAsJson(&ranging); printf("\n"); // printf("\n") is required to flush the data.
rgbDraw(0,0,0);
break;
default:
break;
}
Serial.flush();
rangingNumber++;
#ifndef ARDUINO_DWM1001_DEV
wino.rgbDraw(0,0,0);
#else
digitalWrite(LED_BLUE,HIGH);
digitalWrite(LED_GREEN,HIGH);
digitalWrite(LED_RED_TOP,HIGH);
digitalWrite(LED_RED_BOT,HIGH);
#endif
}
// If a DATA message has been received
if ( messageReceived )
{
uint16_t from = rxMsg.source;
// Do what you want with this message
// ...
messageReceived = false; // and clean the flag
}
}
Ce code est conçu pour utiliser la bibliothèque Ranger et permettre la communication entre nœuds DW1001 en utilisant le protocole Two-Way Ranging (TWR) sur la radio UWB.
Voici un résumé des points clés de ce code :
-
Le code est conçu pour être exécuté sur un nœud DW1001 configuré en tant que serveur TWR (
ranger.enableTwrServer(true);
). -
Il est destinĂ© Ă ĂŞtre utilisĂ© avec un client dont l’adresse est dĂ©finie par
CLIENT_ADDRESS
. -
La liste des ancres (serveurs) est répertoriée dans le tableau
anchors
.
Assurez-vous que toutes les configurations spĂ©cifiques Ă votre environnement telle que l’adresse du client, les adresses des nĹ“uds, les dĂ©lais, etc.
#define CLIENT_ADDRESS 0x0D81 // adresse du noeud client 6
uint16_t anchors[] = {0xCC6C}; //adresse du noeud 5
Flasher le code sur la carte DWM1001
- Connectez vous sur IoT-Lab Fit.
- Créer une nouvelle expérience sur IoT-Lab Fit et sélectionner les nœuds sur lesquels vous souhaitez flasher le code.
Remarque : Vous pouvez choisir les nœuds en fonction de leur emplacement, de leur disponibilité et des besoins de votre projet.
-
SĂ©lectionnez l’icĂ´ne de tĂ©lĂ©versement et spĂ©cifiez le chemin d’accès vers le fichier elf gĂ©nĂ©rĂ© lors de la compilation du code Blink avec l’IDE Arduino, Ă partir de l’emplacement suivant : \Users\username\AppData\Local\Temp\arduino_build\ . Vous devrez modifier le nom du fichier en supprimant le point pour qu’il soit acceptĂ©.
-
Les nœuds DW1001 devraient maintenant exécuter le code.
-
Une fois que vous avez rĂ©ussi Ă tĂ©lĂ©verser et Ă exĂ©cuter le code sur la plateforme Fit Lab et Ă observer la communication entre les nĹ“uds DW1001, On peut visualiser ces messages il suffit de vous connecter sur le serveur toulouse.iot-lab.info avec votre nom d’utilisateur et votre mot de passe, en entrant la commande suivante
iotlab-auth -u username
. Ensuite, vous pouvez taper la commandeserial_aggregator
pour afficher les messages.
Le résultat affiché dans le Serial Aggregator représente les données de communication entre les nœuds DW1001 lors de l'exécution du code Ranger. Voici une explication des informations fournies dans ces données :
-
Les lignes, comme dwm1001-5;My nodeAddress is 52332, indiquent le nĹ“ud Ă©metteur et son adresse. Par exemple, “dwm1001-5” est le nĹ“ud Ă©metteur avec l’adresse “52332”, et “dwm1001-6” est le nĹ“ud destinataire avec l’adresse “3457”.
-
Les données suivantes sont au format JSON et contiennent des informations sur la communication entre les nœuds. Ces informations sont divisées en plusieurs catégories :
“initiator” et “target” indiquent respectivement le nĹ“ud Ă©metteur et le nĹ“ud destinataire. -
“protocol” spĂ©cifie le protocole utilisĂ©, ici “TWR” pour Two-Way Ranging.
-
Les valeurs comme “t1”, “t2”, “t3” et “t4” reprĂ©sentent les temps de vol mesurĂ©s entre les nĹ“uds.
-
“skew”, “skewRequest”, “skewAck” et “skewData” sont des valeurs de synchronisation entre les nĹ“uds.
-
“nlosIndicator” donne un indicateur sur la visibilitĂ© directe entre les nĹ“uds.
-
“tof” reprĂ©sente le temps de vol total.
-
“range” est la distance mesurĂ©e entre les nĹ“uds.
-
“rssiRequest”, “rssiData” et “rssiAck” sont les niveaux de puissance du signal dans diffĂ©rentes phases de la communication.
-
Les autres valeurs telles que “fp_powerRequest”, “fp_powerAck”, “fp_powerData”, “pa_powerRequest”, “pa_powerAck”, “pa_powerData”, “voltage”, “temperature”, et “distantTemperature” sont des donnĂ©es supplĂ©mentaires liĂ©es Ă la communication.
Ces informations permettent de suivre la communication entre les nĹ“uds DW1001, de mesurer les distances et d’Ă©valuer la qualitĂ© de la communication en termes de synchronisation, de force de signal, etc.