Zeroconf Low Consumption Network (Capteurs TI eZ430-RF2500)

De Ensiwiki
Aller à : navigation, rechercher


Project schedule.png
Titre du projet Zeroconf Low Consumption Network
Cadre Projets Réseaux Mobiles et Avancés

Équipe Yassine BOUACHRINE, Jeanne MARCADÉ, Antoine MOREL, Antoine MURAT
Encadrants Franck Rousseau


En résumé

L’objectif de ce projet était de construire un réseau sans fil basse consommation à l’aide des modules EZ430-RF2500.

Notre implémentation est disponible sur le GitLab de l'Ensimag.

Dépendances matérielles, logicielles et bibliographiques

Il s’agit d’un “package” produit par TI qui rassemble :

  • Un microcontrôleur msp430f2274 (32Ko flash, 1Ko RAM).
  • Une radio 2.4GHz et son microcontrôleur cc2500.
  • Un launchboard USB permettant l’accès à l’UART et la programmation du msp430f2274.

Nous avons utilisé les outils suivants :

  • mspdebug (0.22), un outil permettant de programmer et déboguer la carte.
  • msp430-gcc (8.3.0), un fork de GCC permettant de compiler à destination de l’architecture msp430.
  • mrfi, une librairie permettant l’utilisation facilité du module radio.
  • addr_flasher, un utilitaire permettant d’écrire dans la mémoire flash des MSP430.

Les resources documentaires nécessaires sont :

Design général

Nous avons fait en sorte de séparer les responsabilités réseau, entrées/sorties et machine à états.

Réseau

L’interface réseau est partagée en 4 responsabilités différentes : - Emission de paquets - Réception de paquets - Gestion des interfaces (adresses) - Gestion de la table de routage

Emission des paquets

Les noeuds du réseau peuvent envoyer des beacons et des paquets de données.

Une queue FIFO permet d’ajouter des paquets à la file d’émission afin qu’ils soient envoyés dans le créneau prévu à cet effet.

Il faut donc construire des paquets, les ajouter en file ou les envoyer directement et enfin vider la file en temps voulu en émettant les paquets.

Les paquets sortants sont transmis à la librairie MRFI, sauf quand ils sont à destination de la machine courante (loopback), dans quel cas ils sont directement délivrés. Un paquet peut aussi être envoyé en broadcast, dans quel cas il n’y a pas de mécanisme de saut et le paquet est simplement envoyé à destination de la machine 0x255.

Réception des paquets

Les paquets entrants sont récupérés à travers le handler MRFI_RxCompleteISR appelé par la librairie MRFI. Cette dernière met en place un système de filtrage par adresse de destination à la fois matériel et logiciel. Nous avons désactivé le filtrage logiciel (qui était basé sur un format d’adresse différent) et activé par ailleurs le filtrage matériel.

Nous castons les paquets dans une structure de données plus adaptées à notre manipulation.

Selon le type de paquet (flag DATA ou BEACON), on transmet le paquet au handler associé.

Les beacons sont interprétés et les paquets de données sont, au choix, transférés par le mécanisme de saut ou délivrés.

Gestion des interfaces

La structure d’un paquet MRFI suppose des adresses de 4 octets. Le reste de la librairie est bâti autour de ces tailles. Nous utilisons une structure différente qui réduit les adresses à 1 octet et avons donc adapté les fichiers fournis.

Aussi, nous avons fait en sorte qu’un noeud puisse enregistrer plusieurs adresses. C’est très utile quand on a briqué le second capteur à notre disposition ou pour réaliser des réseaux élaborés. Le filtrage matériel est désactivé dans ce cas pour être traité de façon logicielle.

Table de routage

Afin de pouvoir transférer les paquets avec des sauts (en passant par des noeuds intermédiaires), on utilise une table de routage. Les beacons contiennent des informations sur les noeuds atteignables. Ils sont interprétés afin de mettre à jour la table de routage.

On prend en compte la disparition d’un noeud connu pour tenir à jour la table.

Entrées/sorties

Les communications sont assurées grâce à des handlers pour le UART et les boutons. Les ports UART permettent de commander le microcontrôleur et d’afficher les sorties.

Nous avons implémenté des fonctions d’écriture classiques (printn, println, printnln) permettant de presque se croire en local et de profiter du formatage des fonctions classiques de sortie comme printf.

Nous avons implémenté un système qui émule quasiment un terminal. Chaque caractère entré est retourné à travers le UART pour avoir un retour clavier. Les commandes entrées sont parsées pour effectuer différentes actions.

Les commandes sont :

  • s[dest][message] : met dans la file d’émission le paquet de données [message] à destination de [dest].
  • x[message] : met dans la file d’émission le paquet de données [message] en broadcast.
  • i : émule une pression sur le bouton.

Les commandes de déboguage sont :

  • n : afficher la table de routage.
  • b : émet un beacon.
  • a[adresse] : ajoute une adresse à l’interface.
  • l : active ou désactive le loopback.
  • u : émet le premier message de la file et l’en enlève.
  • t : simule le timeout d’un scan.

Machine à états

Le programme est, à instant t, dans un état donné. La réception de paquets, un timeout dans un état ou encore une entrée utilisateur peut faire passer le microcontrôleur dans un autre état.

A chaque tick d’horloge, on incrémente un compteur de cycles et vérifie si les conditions de sortie d’état sont vérifiées. Si c’est le cas, on entre dans l’état souhaité et réalise les actions liées à l’entrée dans cet état.

Protocoles

Etats

L’ensemble des états et leurs transitions communes sont présentés dans le diagramme suivant :

Machine à états
Machine à états

Les états correspondent à :

  • NonInit : Le capteur vient d’être démarré et ne timeout pas encore.
  • Scan : Le capteur recherche s’il existe déjà un réseau auquel il pourrait se joindre. S’il n’en trouve pas après 2 périodes, il lance son propre réseau et passe dans l’état Beaconing. S’il trouve reçoit le un beacon de leader, il le rejoint et passe en Beaconing.
  • Beaconing : Cet état est divisé en MAX_NUM_NODES + 1 sous-états. Le premier est réservé au leader et permet la synchronistation. Si on ne reçoit pas de beacon du leader, on repasse dans l’état Scan. On attend le slot MON_ADRESSE + 1 pour émettre son propre beacon. Si aucun beacon n’est reçu à la fin d’un slot et que l’on connaissait le noeud qui a disparu, on oublie les chemins qui passent par lui. On passe successivement dans chacun des sous-états avant de passer à l’état Active.
  • Active : Les noeuds envoient les messages dans la FIFO. Après un certain nombre de ticks d’horloge, on passe dans l’état Sleep.
  • Sleep : En entrant dans cet état, un capteur passe en mode d’économie d’énergie 3. Il désactive alors toutes les horloges sauf la VCO (qui permettra de sortir de cet état) ainsi que le module radio. Après un certain nombre de ticks d’horloge, le capteur retourne dans l’état Beaconing pour se re-synchroniser sur le leader.

Si un leader reçoit un beacon appartenant à un numéro de réseau plus élevé que le sien, il envoie un beacon asynchrone pour l’inviter à aller dans l’état Scan pour se joindre à lui.

Trames

Nos trames réseau utilisent le même principe d’encapsulation que la pile OSI avec une frame de base et des payloads selon le flag.

Trames
Trames

Payload de données

Le pemier octet du paquet est le véritable destinataire du message afin de pouvoir transférer les messages avec sauts.

Si le destinataire est le noeud qui reçoit le message, alors il est transmis à la couche applicative en appelant le handler asocié aux données. Si ce n’est pas le cas, il le transmet au “next_hop”. Si le chemin vers le destinataire n’existe pas/plus, le paquet est abandonné.

Payload de beacon

Le beacon contient le numéro du réseau auquel appartient le noeud qui l’envoie, s’il est synchronisé ou non, ainsi que la distance de l’émetteur à chacun des autres noeuds (en nombre d’intermédiaires).

Lors de la réception d’un beacon, si son numéro de réseau est supérieur à celui du noeud récepteur, il est ignoré à moins que le récepteur soit le leader et qu’il lui envoie un beacon asynchrone. Si le numéro est inféreur, on fusionne vers ce réseau. Si le numéro est égal, on met à jour notre table de routage.

On note d_n_ notre distance a priori au noeud n. On note d_x,n_ la distance du noeud x au noeud n que l’on apprend du beacon. On note nh_n_ le next hop, noeud intermédiaire, le noeud par lequel on fait transiter un paquet qui doit arriver au noeuf n. On note s la force du beacon reçu. On note s_x_ la force du précedent beacon reçu émis par le noeud x. La mise à jour de la table de routage suit le protocole suivant :

Pour chaque noeud n du réseau, faire :
    Si d_x,n_ >= nombre_maximum_de_noeuds, alors :
        // x ne connaît pas de chemin vallable jusqu'à n
        // ou il s'agit d'une boucle.
        Si nh_n_ = x, alors :
            d_n_ <- nombre_maximum_de_noeuds
            s_n_ <- force_minimum
        Fin si
    Sinon :
        // On améliore notre route si elle est plus courte ou propose un meilleur RSSI
        // ou alors si on passait déjà par x.
        Si (d_x,n_ + 1 < d_n_) ou (d_x,n_ + 1 = d_n_ et s > s_n_) ou (nh_n_ = x), alors :
            d_x,n <- d_n_ + 1
            nh_n_ <- x
            s_n_ <- s
        Fin si
    Fin si
Fin faire

Utilisation

Installation

You should not try to install gcc-msp430 from your package manager as its gcc version will certainly be outdated and incompatible with the mrfi lib.

Download the TI MSP430 toolchain archive for your OS on TI website.

Extract its content in /opt/msp430/.

Download the GCC support files archive on TI website.

Extract its include folder in /opt/msp430/.

Check if the paths in eZ430.mk are correct.

Install the mspdebug package otherwise you won’t be able to flash the executables:

sudo apt install mspdebug on debian-based systems.

Build

You can build the main project by running make in the project root directory.

Run make clean to remove files generated by gcc.

Run make flashall_zclcn to flash all connected LaunchPads.

You can similarly run the examples in the examples folder.

Connecting to the MSP430

MSP430 have UART I/O making it possible to communicate with them using a serial terminal emulator.

The recommended one is minicom.

Install it by running sudo apt install minicom on debian-based systems.

The connected LaunchPads will probably appear on your system under the files /dev/ttyACM0, /dev/ttyACM1, etc. You can list them by checking the kernel ring messages with dmesg | grep tty. If they don’t appear, unplug and replug the LaunchPads as this should emit a new kernel message. Anyway, you can blindly assume they will be named ttyACMX.

Run minicom -D /dev/ttyACMX (replacing X with 0, 1, 2, etc.) to connect to one of them.

Written characters will be transmitted (received on RX on the MSP430 byte after byte) and your terminal will take and print whatever is in the TX buffer on the MSP430 (freeing the buffer and making the board able to write a new byte).

Problèmes rencontrés

Synchronicité

Le MSP430 dispose de plusieurs horloge et le système global des timers peut être difficile à appréhender. Le guide utilisateur permet néanmoins de comprendre petit à petit comment l’ensemble fonctionne. Les horloges sont plus ou moins calibrées. Celles qui consomment le plus sont les plus précises. Or, si l’on souhaite aller dans un état dormant avancé (sleep_mode_3) et consommer très peu, il faut alors utiliser la seule horloge interne peu précise qui fonctionne encore, l’horloge VLO. D’un capteur à l’autre, cette horloge varie énormément. Il faut ainsi mettre en place un système de correction du décalage, de compensation. Nous ne l’avons pas fait et avons donc fait en sorte que les capteurs s’attendent longtemps en sortie de veille afin de se resynchroniser. Plus on dort longtemps et plus les capteurs seront décalés, et plus il faudra prévoir une fenêtre importante pour les resynchroniser. On peut aussi améliorer les performances en reposant sur une horloge fiable lorsque le système est totalement éveillé.

L’impact majeur d’une telle solution est qu’elle demande à ce que tous les noeuds soit à portée du leader, ce qui contraint énormément la topologie du réseau.

addr_bricker

Nous avons utilisé un outil, addr_flasher, afin d’écrire dans la mémoire flash des capteurs un octet qui reste d’un démarrage à l’autre. La mémoire flash est un espace sensible puisqu’elle stocke les données de calibration de l’horloge DCO entre autres capteurs de température. Ces données sont calibrées en usine par TI et diffèrent d’un appareil à l’autre au sein de la même série. Une fois qu’elles sont effacées, il n’y aucun moyen de retrouver les données perdues. Or, addr_flasher va écrire dans cette zone mémoire et la réinitialise. La démarche n’est visiblement pas sans faille puisque nous avons réussi à perdre les données de calibration de 2 capteurs alors que nous avons fait en sorte de respecter le protocole. La raison est en réalité assez simple : une fois le programme de modification chargé sur le msp430, il sera exécuté à chaque mise sous tension de l'appareil. Ainsi, si une fois la manipulation finie vous le rebranchez, vous risquez de corrompre la mémoire. Cela ne devrait pas arriver en cas normal puisque le script de flash prévoit de venir écrire un nouveau programme qui fait clignoter les leds une fois l'opération finie... programme qui n'était pas chargé dans notre cas. Ce programme n'est donc pas uniquement là pour faire joli et est nécessaire. Vérifiez donc en amont qu'il compile et que vous parvenez à le charger sur le msp (en fait, essayez de faire tourner le script à blanc).

Sans ces données de calibration, le baudrate de l’UART n’est plus assuré et vous ne parviendrez pas à communiquer raisonnablement avec la carte (or mspdebug).

Nous recommanderions sans aucun doute, avant d’essayer d’écrire dans cette mémoire, d’en réaliser une copie et de l’exporter sur un autre appareil. L’espace mémoire est lisible sans action nécessaire au préalable. Nous vous recommandons de scanner tout l’espace concerné et de l’écrire (en représentation décimale par exemple) sur la sortie UART. Copiez cette information en lieu sûr. Si vous veniez à effacer malencontreusement les données de calibration, construisez un petit exécutable qui écrira les données sauvegardées dans la mémoire flash.

Si jamais c’est trop tard, il est théoriquement possible de recalibrer à la main le capteur à l’aide d’un quartz externe de 32kHz en le câblant sut le port approprié et en flashant le programme présent dans calibrate. Néanmoins, le port prévu n’est pas disponible sur le eZ430 qui l’utilise pour communiquer avec le module radio. Certaines personnes disent tout de même avoir réussi à l’aide d’oscilloscope.

RAM corrompue

Pour une raison que nous n’avons pas réussi à identifier, la RAM de nos capteurs, après un certain temps, est ré-écrite avec des données incorrectes sans que nous n’accédions aux structures de données qui y sont stockées. On pense alors à un stack-overflow, les capteurs ne disposant que d’1Ko de RAM. Les mesures ne semblent pas aller dans cette direction, sauf à alouer d’énormes quantités de mémoire. Il semblerait que le phénomène soit lié à l’utilisation de la radio puisque nous le constatons, parfois, à l’émission ou à la réception d’un beacon. Il se pourrait que des paquets mal formés dépassent de l’espace de destination (memcpy ou boucle for non vérifiés). Nous n’en savons pas plus.

Afin de détecter ces problèmes, il suffit d’allouer un grand espace de mémoire et de l’initialiser avec memset à un pattern connu (au hasard, 0xCA). A chaque tick d’horloge, vérifiez que tab[i] == 0xCA. Si ce n’est pas le cas, il y a eu overflow. L’horloge étant rapide et afin de ne pas bloquer toute l’exécution, vous pouvez vous contenter de vérifier une case différente par tick.

Pistes d’amélioration

Structure d’arbre.

Afin de pouvoir étendre la topologie du réseau, il est bien plus intelligent de construire une structure d’arbre. Chaque noeud a n fils et un parent. Il se synchronise sur son parent qui a alors le rôle de leader (en attendant son beacon) et synchronise ses fils en prenant alors lui-même le rôle de leader.

Il y a alors de nouvelles questions à se poser : - Comment construire l’arbre de façon autonome ? - Comment redécouper la machine à états et redéfinir le protocole afin de faire en sorte que chaque noeud dispose de 2 phases de synchronisation ? Il semble raisonnable d’utiliser le découpage suivant : Beaconing 0 (en tant que fils) -> Active 0 -> Beaconing 1 (en tant que leader) -> Active (1) -> Sleep -> Beaconing 0, etc. Il reste à vérifier si cela fonctionne dans les faits.

Fine-tuning des cycles d’horloge

Les nombres de cycles sont pour l’instant très grossiers et s’assurent simplement que le tout fonctionne. Il est possible d’ajuster la vitesse de l’horloge ainsi que le nombre de tick passés dans chaque état afin d’obtenir de meilleures performances. Aussi, l’algorithme pourrait éventuellement devenir auto-adaptatif.

Trucs et astuces

Trouver la valeur du pointeur de stack (SP)

Il suffit d’allouer une variable et d’afficher son pointeur.

uint8_p stack;
println("[DEBUG] SP: %p", &stack);

Détecter les stack overflows

Déclarez un tableau de grande taille. Initialisez-le avec un pattern. Vérifiez à chaque tick d’horloge que le pattern est toujours présent.