IMA5 2018/2019 P13 : Différence entre versions

De Wiki de Projets IMA
m (Hello World et taille de IP/RPL)
(Hello World et taille de IP/RPL)
Ligne 809 : Ligne 809 :
  
 
J'ai continué à essayer en parallèle de réduire la taille de Contiki. J'ai transformé le driver radio du cc2420 utilisé par le Tmote Sky en driver "vide", car je vais devoir le remplacer de toute façon part un autre driver radio, adapté au cc1101 du cc430. Pour ce faire, j'ai commenté toutes les fonctions, et si à la compilation une fonction est manquante, je l'ai décommentée. J'ai de plus transformé son code en un simple return du style
 
J'ai continué à essayer en parallèle de réduire la taille de Contiki. J'ai transformé le driver radio du cc2420 utilisé par le Tmote Sky en driver "vide", car je vais devoir le remplacer de toute façon part un autre driver radio, adapté au cc1101 du cc430. Pour ce faire, j'ai commenté toutes les fonctions, et si à la compilation une fonction est manquante, je l'ai décommentée. J'ai de plus transformé son code en un simple return du style
<code>return RADIO_RESULT_NOT_SUPPORTED;</code>. Grâce a cette opération, j'ai réussi a déscendre en dessous des 32ko de ROM :  
+
<code>return RADIO_RESULT_NOT_SUPPORTED;</code>. Grâce a cette opération, j'ai réussi a déscendre en dessous des 32ko de ROM :
$ size udp-client.sky
+
 
  text   data     bss     dec    hex filename
+
{| class="wikitable"
  31684     184   3118   34986    88aa udp-client.sky
+
! text !! data !! bss
 +
|-
 +
|31684
 +
|184
 +
|3118
 +
|}
 +
 
  
 
J'ai ensuite déterminé la taille de mon driver radio (simple compilation de toutes les fonction du driver):
 
J'ai ensuite déterminé la taille de mon driver radio (simple compilation de toutes les fonction du driver):
 +
{| class="wikitable"
 +
! text !! data !! bss
 +
|-
 +
|1616
 +
|0
 +
|82
 +
|}
  
$ size test
 
  text    data    bss    dec    hex filename
 
  1616      0      82    1698    6a2 test
 
  
 
On remarque qu'il y a toujours un dépassement de mémoire en ROM.
 
On remarque qu'il y a toujours un dépassement de mémoire en ROM.
Ligne 824 : Ligne 834 :
  
 
J'ai aussi remarqué que le code d'exemple de Contiki pour un simple hello-world prend beaucoup de place :
 
J'ai aussi remarqué que le code d'exemple de Contiki pour un simple hello-world prend beaucoup de place :
$ size hello-world.sky
+
{| class="wikitable"
  text   data     bss     dec    hex filename
+
! text !! data !! bss
  41600     222   6946   48768    be80 hello-world.sky
+
|-
 +
|41600
 +
|222
 +
|6946
 +
|}
 +
 
  
 
ce qui est très étrange sachant que l'exemple n'effectué qu'un simple printf :
 
ce qui est très étrange sachant que l'exemple n'effectué qu'un simple printf :
Ligne 871 : Ligne 886 :
  
 
on obtient la taille suivante
 
on obtient la taille suivante
$ size hello-world.sky
+
{| class="wikitable"
  text   data     bss     dec    hex filename
+
! text !! data !! bss
  11984     86   2588   14658    3942 hello-world.sky
+
|-
 +
|11984
 +
|86
 +
|2588
 +
|}
 +
 
  
 
Cela permet de se rendre compte que toute la couche RPL/IP occupe à elle seul près de 30ko de ROM, soit la quasi totalité de la ROM disponible du cc430.
 
Cela permet de se rendre compte que toute la couche RPL/IP occupe à elle seul près de 30ko de ROM, soit la quasi totalité de la ROM disponible du cc430.
  
 
En combinant ces options de compilation/makefile et mes modifications (retraits), on obtient un programme "vide" (pas de radio, pas de routage, pas de couche IP, pas de gestion des GPIO/UART/SPI/LED...) de la taille anoncé par Contiki :
 
En combinant ces options de compilation/makefile et mes modifications (retraits), on obtient un programme "vide" (pas de radio, pas de routage, pas de couche IP, pas de gestion des GPIO/UART/SPI/LED...) de la taille anoncé par Contiki :
$ size hello-world.sky
+
{| class="wikitable"
  text   data     bss     dec    hex filename
+
! text !! data !! bss
  3258     22     428    3708    e7c hello-world.sky
+
|-
 +
|3258
 +
|22
 +
|422
 +
|}
  
 
==Nouvelle solution : Routage semi statique, semi dynamique==
 
==Nouvelle solution : Routage semi statique, semi dynamique==

Version du 25 février 2019 à 15:06


Présentation générale

Description

La recherche de places de parking est une tâche fastidieuse, consommatrice de temps et polluante.

Objectifs

Pour remédier à ce problème, nous proposons de réaliser un ensemble composé :

  • D'un capteur de détection de voiture :
  • D'un système de transmission basé sur une carte "maison" (µC : CC430) déjà existante ;
  • D'un système de stockage et de visualisation des places libres.

Préparation du projet

Cahier des charges

Choix techniques : matériel et logiciel

Liste des tâches à effectuer

Calendrier prévisionnel

Réalisation du Projet

Semaine du 17/09

En attendant un entretien avec les encadrants de projet pour mettre au point un cahier des charges précis, plusieurs recherches bibliographiques ont été effectuées.

Plusieurs solutions existent déjà pour effectuer la détection de voitures dans un parking, axées autours de trois méthodes :

  • Détection avec un capteur par place
  • Détection avec des capteurs en entrée et en sortie de parking
  • Détection par caméra

Chaque solution présente des avantages et des inconvénients, qu'il faudra analyser afin de choisir la solution adaptée.

Le solution avec un capteur par place est précise et indique facilement et précisément l'occupation des places. Elle est de plus peu coûteuse en énergie par capteur, la détection ne devant pas être effectuée en permanence. En revanche cette méthode est coûteuse car nécessite un capteur par place, et implique une remontée d'informations complexe à mettre en place (sans fil ou filaire).

La solution avec des capteurs en entrée et en sortie est peu coûteuse et permet une remontée d'informations assez simple (peu de données à transmettre). En revanche elle n'est pas précise et indique seulement le nombre de voitures/places restantes dans le parking et non les places précises restantes. Elle est de plus coûteuse en énergie pour les capteurs, devant être actifs très souvent afin de ne pas manquer une voiture.

La solution par caméra est économe en matériel, une caméra pouvant être suffisante pour un parking entier, et permet de détecter précisément les places restantes. Elle n'est cependant pas pratique pour un parking souterrain, consomme beaucoup d'énergie par capteur et nécessite des capacités de traitement d'images hors de la portée d'un cc430. Elle peut cependant utiliser un serveur pour effectuer les calculs.

Des liens vers les études sont disponibles dans la partie Documentation

L'entretien avec les encadrant à de plus été préparer afin de pouvoir déterminer u cahier des charges précis. Les questions suivantes doivent être abordées :

  • Objectifs précis du projet
  • Localisation du parking, type de parking
  • Durée de vie minimale des capteurs
  • Type de détection voulu
  • Budget
  • Design du boiter du capteur
  • Utilisation de RIOT imposée
  • Quel type d'affichage
  • Alimentation des capteurs
  • Utilisation d'un serveur
  • Utilisation du protocole RPL

Semaine du 24/09

Suite au rendez-vous du 21/09, les questions suivantes ont été écartées :

  • Durée de vie minimale des capteurs
  • Type de détection voulu
  • Budget
  • Design du boitier du capteur
  • Quel type d'affichage
  • Alimentation des capteurs
  • Utilisation d'un serveur

En effets ces questions ne sont pas prioritaires et n'entreront dans l'équations que si le projet avance très vite.

  • Objectifs précis du projet

L'objectif principal du projet est de déployer un réseau d'objets sans fils possédant des capacités de routage dynamique.

  • Localisation du parking, type de parking

Le parking d'étude sera le parking de l'IRCICA, mais des essais en condition réel seront intéressant mais ne sont pas une priorité

  • Utilisation du protocole RPL

Le routage dynamique devra préférentiellement être mis en place en respectant le protocole RPL

  • Utilisation de RIOT imposée

RIOT OS n'est en aucun cas imposé, et l'utilisation d'un autre OS (Contiki a été évoqué) est parfaitement envisageable, voir même tenter une appproche sans OS.

Ayant déjà travaillé avec RIOTOS et le CC430, je sais qu'il peut y avoir des problème concernant la taille et l'occupation en RAM des OS. Ma première mission est donc de déterminer si le CC430 et ses 32ko de ROM et 4ko de RAM permettent l'utilisation des implémentations du protocoles RPL des différents OS de l'embarqués, ou si d'autres options plus simple (routage statique voir simple broadcast) sont à privilégier.

Semaine du 01/10

Durant cette semaine j'ai étudié l'impact en mémoire (ROM et RAM) des deux OS les plus adaptés aux premiers abord pour le projet : Contiki et RIOT. Les deux OS vantent une utilisation en ROM et RAM faible (environ 10ko de ROM et 2 ko de RAM), ce qui conviendrait parfaitement au besoin du projet. Malheureusement, ces tailles annoncées ne sont vrai que pour l'OS seul dans la plus grande majorité des cas, que ce soit pour RIOT ou Contiki. Le cc430 n'est pas supporté par Contiki, mais le Tmote Sky l'est, et est basé sur un msp430 de chez TI, comme le cc430. Utiliser le Tmote Sky afin d'établir la taille en ROM et RAM prise par les sources semble une option valide. Lors de la compilation des sources d'exemples RPL pour Contiki avec le Tmote Sky, sans modification au préalable de l'exemple, on option (grâce à la commande size sur linux) :

text data bss
43380 310 6958

soit 43380 octets de ROM utilisé et 310+6958=7268 octets de RAM utilisés, ce qui est bien supérieur à ce qui est disponible.

Du coté de RIOT, je savais que le programme d'exemple ne tenait pas dans le cc430, en effet il y a une surcharge de RAM de 2426 octets et de ROM de 32956 octets.

Dans les deux cas il est possible de réduire la taille de ce programme en retirant ce qui ne nous concerne pas.

De plus, afin d'être sur que le compilateur optimise la taille de l'exécutable, j'ai regardé la commande final des makefile de RIOT et Contiki en ajoutant

SHELL="sh -x"

à la fin de la commande make.

L'une des option de gcc pour optimisé en taille est l'option -0s, et cette option est bien présente pour les deux OS. Les gains en places ne peuvent donc pas être effectués grâce aux options du compilateur, mais doivent être effectués auprès du code source.

Les optimisations possibles chez Contiki se font en ajoutant un fichier project-conf.h dans le répertoire de l'exemple et d'y ajouter des #define pour restreindre certaines fonctionnalités.

Du coté de RIOT, il faut modifier le makefile et le code d'exemple.

Semaine du 08/10

Cette semaine a été consacrée à l'exploration des possibilités de réductions de taille des sources.

Contiki

Le programme d'exemple étant assez simple, il n'y à pas beaucoup de chose à modifier pour en réduire la taille. Il faut donc jouer sur les paramètres du fichier project-conf.h. Les paramètrs impactant la taille que nous pouvons modifier sont les suivants :

#define QUEUEBUF_CONF_NUM 4
#define NBR_TABLE_CONF_MAX_NEIGHBORS 8
#define NETSTACK_MAX_ROUTE_ENTRIES 0
#define UIP_CONF_BUFFER_SIZE 100
#define SICSLOWPAN_CONF_FRAG 0
#define PROCESS_CONF_NO_PROCESS_NAMES 1
#define UIP_CONF_TCP 0

avec QUEUEBUF_CONF_NUM le nombre de message que doit pouvoir contenir le buffer de message. Plus cette varaible est faible, moins le programme prend de place en RAM mais plus il y a un risque d'engorgement. NBR_TABLE_CONF_MAX_NEIGHBORS correspond aux nombres de voisins que le nodes peut gérer. Plus cette valeur est importante, plus le réseau est flexible, mais plus cela prend de la place en RAM. NETSTACK_MAX_ROUTE_ENTRIES correspond au nombre de route sauvegarder dans la table rde routage. Plus cette valeur est importante, plus le réseau est réactif, mais comme nous sommes en mode "non storing mode", cette valeur doit être mise à zéro. UIP_CONF_BUFFER_SIZE correspond à la taille en octet du buffer de message IPv6. La taille minimal pour l'intéropérabilité est de 1280 octets, mais si le réseau n'a pas vocation à être mis en relation avec d'autre réseau IPv6, comme dans notre cas, la taille de ce buffer peut être réduit. Comme un paquet RPL à une taille maximale d'environ 32 octets et l'entête IPv6 d'envoron 40 octets, une taille de buffer de 100 est suffisante. SICSLOWPAN_CONF_FRAG correspond à l'utiliksation ou non du méchanisme de fragmentation des messages. Comme nos message seront normalement très court (moins de 10 octets), ce mechanisme ne devrait pas être utile et donc peut être retirer. Cela gagne de la place en RAM et en ROM. PROCESS_CONF_NO_PROCESS_NAMES correspond au mechanisme de nommage des processus créés dans Contiki. Comme il n'y aura pas beaucoup de processus et qu'ils ne seront pas lu par un humain lors du fonctionnement, désactiver ce mechanisme permet de gagner de l'espace en ROM principalement. UIP_CONF_TCP correspond à l'utilisation du mechanisme TCP. Comme le protocole UDP correspond à nos besoin, il n'est pas nécessaire d'intégrer les mechanismes de TCP.

De plus, dans le programme d'exemple, il y a de plus un mechanisme qu'il est possible de désactiver : les logs. En effet, Contiki propose un système avancé de log avec plusieurs niveaux permettant d'observer finement ce qu'il se passe durant le fonctionnement de l'OS. Passer de

#define LOG_LEVEL LOG_LEVEL_INFO

à

#define LOG_LEVEL LOG_LEVEL_NONE

permet aussi d'économiser un peu d'espace.

Avec ces modification, on arrive à


text data bss
41174 296 3492

On arrive donc en dessous des 4ko de RAM utilisée, mais il reste encore environ 10ko de ROM en trop.


RIOT

Comme je savais depuis mon projet IMA4 que RIOT occupait trop de place sur le cc430, j'ai pu rapidement tester des modifications afin de déterminer la taille prise en nmémoire. J'ai donc compilé le projet d'exemple gnrc_networking avec une carte proche du cc430 mais ayant plus de ROM et de RAM, le Tmote sky, ou telosB. Le resultat, sans modification préalable n'est pas encourageant : la compilation échoue car il y a un overflow de ROM de 14ko, sur les 48k disponible. La compilation n'indique pas d'overflow en RAM utilisé, mais le Tmote Sky ayant 10ko de RAM, ce n'est pas une chose à retenir car le cc430 n'en a que 4ko.

En modifiant le programme principale pour qu'il ne fasse rien, et en retirant dans le makefile l'inclusion de sources non nécessaire à notre projet (comme un shell), on arrive à un overflow de 7ko de ROM.

Avec ces même modifications, une compilation avec comme cible le cc430 indique un overflow en RAM de 1.6Ko de RAM.

Semaine du 15/10

J'ai décidé d'orienter principalement mes efforts vers l'OS Contiki, car je trouve que leurs sources sont plus facilement lisible que celles de RIOT, et leur programme d'exemple pour former un réseau RPL est plus facilement réutilisable, n'utilisant pas une couche shell contrairement à RIOT.

En étudiant les sources du Tmote Sky que j'utilise comme références, j'ai trouvé que le port était assez fourni, permettant de gérer beaucoup d'aspect du microcontroleur, avec entre autre des modules pour les GPIO, l'UART, le SPI, l'I2C et les capteurs de la carte. Le Tmote Sky possède aussi un module d'indentification unique, le ds2411. Ce module permet d'obtenir de manière matériel un identifiant unique pour chaque node. Pour le début du projet, il n'y a pas besoin de ces modules, donc il est possible de les retirer du makefile et des différentes sources les utilisant.

Ce faisant, on arrive à :

text data bss
35762 210 3296

On arrive proche des 32ko de ROM du cc430. Afin d'avoir de la marge, j'estime à 2ko de ROM nécessaire pour utiliser les sources du cc430 disponible, donc je pense qu'il faut réduire l'utilisation en ROM a 30ko.

En retirant la gestion propre à Contiki des LED et le calcul du chiffrement AES de manière logiciel, on descend à 35370 octets de ROM.

Les paramètres du fichiers projet-conf.h que l'on peut encore modifier sont :

QUEUEBUF_CONF_NUM
NBR_TABLE_CONF_MAX_NEIGHBORS

Malheureusement, ces variables n'ont qu'un impact fort sur la RAM, et leur impact est très faible sur la ROM.

A partir de ce point je n'ai pas d'autre pistes pour réduire de manière significative l'utilisation en ROM. En accord avec mes encadrants, il a été décidé que si l'on veut utiliser les cc430 disponibles, il va falloir se passer d'OS et extraire les parties de la couches réseau de Contiki ou de RIOT dont nous avons besoin (principalement MAC, UDP et RPL).

Contiki met en place des méchanismes très pratique afin d'analyser la mémoire utilisé par les sources graces aux commandes (expliquée ici https://github.com/contiki-ng/contiki-ng/wiki/Tutorial:-RAM-and-ROM-usage) :

make <nom-du-projet>.ramprof

pour observer la taille de chaque variable en RAM. Cette commande effectue en fait la commande

msp430-nm -S -td --size-sort <nom-du-projet>.<cible> | grep -i " [abdrw] " | cut -d' ' -f2,4

(ici

msp430-nm -S -td --size-sort udp-client.sky | grep -i " [abdrw] " | cut -d' ' -f2,4

)

et

make <nom-du-projet>.flashprof

pour observer la taille de chaque fonction en ROM. Cette commande exécute en fait la commande :

msp430-nm -S -td --size-sort <nom-du-projet>.<cible> | grep -i " [t] " | cut -d' ' -f2,4

(ici

msp430-nm -S -td --size-sort udp-client.sky | grep -i " [t] " | cut -d' ' -f2,4

)

Un extrait du resultat de la seconde commande donne

{...}
00000384 nbr_table_add_lladdr
00000384 uip_icmp6_error_output
00000396 frame802154_parse
00000432 rpl_process_dio
00000476 transmit_from_queue
00000558 rpl_icmp6_dio_output
00000612 ns_input
00000680 dio_input
00000808 rpl_ext_header_update
00001196 uip_process
00001540 input
00001810 output

On remarque que les deux plus grosses fonctions sont "input" et "output".

On peut déterminer leur origine avec les commandes suivantes :

$ nm -oStd obj_sky/*.o | grep " output$"
obj_sky/sicslowpan.o:00000000 00001810 t output
$ nm -oStd obj_sky/*.o | grep " input$"
obj_sky/sicslowpan.o:00000000 00001540 t input

On remarque que ces fonctions proviennent du module "sicslowpan" (6LoWPAN). 6LoWPAN (IPv6 over Low-Power Wireless Personal Area Networks) définit des méchanismes d'encapsulation et de compression des headers IPv6 pour objets contraints, et donc on ne peut se séparer de ce module. 6LoWPAN sert de plus de base pour le protocole de routage RPL, et est donc indispensable.

Afin d'être sur que les sources de Contiki en rapport avec la couche MAC, avec 6LoWPAN et RPL (entre autres) pouvaient effectivement tenir dans les 32ko du cc430, j'ai décidé d'estimer la taille totale de ces sources.

Pour ce faire, j'ai d'abord conçu des scripts se basant sur les commandes précedemment citées. J'ai enregistré le résultat de la commande

make udp-client.flashprof

dans un fichier, puis ajouté à chaque ligne correspondant à une fonction la localisation de cete fonction grâce au script suivant :

#!/bin/bash

obj=~/Documents/PFE/baseSource/Contiki-NG/contiki-ng/examples/rpl-udp/obj_sky/*.o

file=~/Documents/PFE/Redaction/outputFlash.txt

file2=~/Documents/PFE/Redaction/funcLocations.txt

rm $file2

touch $file2

cptline=0

while IFS= read -r cmd; do
   if [ $cptline -ge 2 ]; then
      nom=$(echo $cmd |cut -d' ' -f2)
      line=
      taille=$(echo $cmd |cut -d' ' -f1)
      location=$(nm -oStd $obj | grep " $nom$")
      location=$(echo $location |cut -d' ' -f1)
      line=$taille' '$nom$'\t'$'\t'$location
      echo ' '$taille' '$nom$'\t'$'\t'$location >> $file2
   else
       cptline=$(($cptline + 1))
   fi
done < "$file"

Cela donne une résultat du genre :

00000002 drop_route        /home/nenth/Documents/PFE/baseSource/Contiki-NG/contiki-ng/examples/rpl-udp/obj_sky/rpl.o:00000000
00000002 energest_flush        /home/nenth/Documents/PFE/baseSource/Contiki-NG/contiki-ng/examples/rpl-udp/obj_sky/clock.o:
{...}

J'ai ensuite ajouté à chaque ligne un champs au debut de celle ci contenant 1 ou 0 en fonction de la localisation de la fonction, 1 si la fonction semble être dans un module réseau, 0 sinon.

Ensuite j'ai exécuté le script suivant qui en, fonction du premier champs à 1 ou 0 calcul la taille totale de toutes les fonctions :

#!/bin/bash

file=~/Documents/PFE/Redaction/funcLocationsAppend.txt 

total=0

while IFS= read -r cmd; do
   isAddable=$(echo $cmd |cut -d' ' -f1)
   if [ $isAddable -eq 1 ]; then
     total=$(($total + $(echo $cmd |cut -d' ' -f2 | sed -e 's/^0*//')))
   fi
   echo $total
done < "$file"

Ici le sed permet de transformer le champs de type

00000384

en

384

car pour le shell un nombre commençant par 0 est considéré comme en base 8, et donc les chiffres 8 et 9 font planter l'addition.

Le résultat de ce script donne une taille totale de 28564 octets. A première vue, il reste environ 3k pour le reste du projet, ce qui est relativement peu, mais cela doit être possible.

Extraction

A partir des fichiers extraits, on trouve 9 fichier avec le mot clé PROCESS qui sert identifier tous ce qui se rapporte aux processus. Par exemple, PROCESS permet de déclarer des pocessus qui seront lancés :

#if PROCESS_CONF_NO_PROCESS_NAMES
#define PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct process name = { NULL,             \
                          process_thread_##name }
#else
#define PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct process name = { NULL, strname,        \
                          process_thread_##name }
#endif

Comme déterminé auparavant, un processus avec un nom est plus gourmand en mémoire qu'un processus ssans, on ne retient que

#define PROCESS(name, strname)              \
  PROCESS_THREAD(name, ev, data);           \
  struct process name = { NULL,             \
                          process_thread_##name }

Les fichiers à modifier a première vue sont

netstack.c
netstach.h
resolv.c
resolv.h
simple-udp.c
tcpip.c
tcpip.h
udp-client.c
udp-socket.c

J'ai commencé à explorer les occurence et l'utilité de chaque macro dans chaque fichier.

Dans netstack.c, on rencontre 3 fois le mot clé PROCESS, mais ne fait partie que de nom de variables.

Dans resolv.c, on rencontre 1 fois le mot clé PROCESS, mais ne fait partie que de nom de variables.

Fichier Nombre d'occurence de "PROCESS" Importance de "PROCESS"
netstack.c 3 Aucune
netstack.h 1 aucune
resolv.c 18 forte
resolv.h 1 aucune
simple-udp.c 10 forte
tcpip.c 18 forte
tcpip.h 1 aucune
udp-client.c 6 moyenne
udp-socket.c 11 forte

Les principales macro rencontrés sont les suivants :

PROCESS
PROCESS_THREAD
PROCESS_BEGIN
PROCESS_WAIT_EVENT_UNTIL
PROCESS_END
PROCESS_CONTEXT_BEGIN
PROCESS_WAIT_EVENT
PROCESS_CURRENT

Il faut de comprendre ce que font ces macro avant d'entamer l'extraction des sources.

Toutes ces macro sont définis dans le fichier process.h.

Toutes ces macros rendent le code lisible dans un contexte de processus lancés en parallèles mais rendent l'extraction plus complexe.

PROCESS permet de simplement déclarer un thread et une structure associer à ce thread.

PROCESS_THREAD permet de définir le thread déclaré par PROCESS_THREAD

La fonction des autres macro est pour le moment moins clair.

Pour essayer de voir plus clair, j'ai ajouté l'option de compilation de gcc -E dans le makefile de Contiki pour que gcc s'arrête après l'étape de précompilation.

Pour exemple, l'option -E transforme la définition du thread de tcpip (localisé dans tcpip.c)

PROCESS_THREAD(tcpip_process, ev, data)
{
  PROCESS_BEGIN();

#if UIP_TCP
  memset(s.listenports, 0, UIP_LISTENPORTS*sizeof(*(s.listenports)));
  s.p = PROCESS_CURRENT();
#endif

   tcpip_event = process_alloc_event();
#if UIP_CONF_ICMP6
  tcpip_icmp6_event = process_alloc_event();
#endif /* UIP_CONF_ICMP6 */
  etimer_set(&periodic, CLOCK_SECOND / 2);

  uip_init();
#ifdef UIP_FALLBACK_INTERFACE
  UIP_FALLBACK_INTERFACE.init();
#endif
  /* Initialize routing protocol */
  NETSTACK_ROUTING.init(); 

  while(1) {
    PROCESS_YIELD();
    eventhandler(ev, data);
  } 

  PROCESS_END();
}

en

static char process_thread_tcpip_process(struct pt *process_pt, process_event_t ev, process_data_t data)
{
  { char PT_YIELD_FLAG = 1; if (PT_YIELD_FLAG) {;} switch((process_pt)->lc) { case 0:;

  tcpip_event = process_alloc_event();

  etimer_set(&periodic, 128UL / 2);

  uip_init();

  rpl_lite_driver.init();
 
  while(1) {
    do { PT_YIELD_FLAG = 0; (process_pt)->lc = 837; case 837:; if(PT_YIELD_FLAG == 0) { return 1; } } while(0);
    eventhandler(ev, data);
  }
 
  }; PT_YIELD_FLAG = 0; (process_pt)->lc = 0;; return 3; };
}

On remarque bien la disparition des blocks #if, mais le problème vient du block switch

En effet, on remarque une structure très étrange du code : nous avons un case 837 dans le block même du case 0. De plus, on trouve un do {...} while(0) dans un while(1)

Afin de continuer je dois comprendre le fonctionnement de cette structure.

Protothread

Après avoir passer un certain temps a essayer de comprendre le fonctionnement de la précedente structure, j'ai compris plus en détail son fonctionnement et surtout le fonctionnement de Contiki.

Contrairement aux OS traditionnels et à RIOT, Contiki n'utilise pas de threads classiques, mais utilise des "protothreads".

J'ai compris le fonctionnement des protothreads grâce au document publié par Adam Dunkels, Oliver Schmidt, Thiemo Voigt et Muneeb Ali : "Protothreads : Simplifying Event-Driven Programming of Memory-Constrained Embedded Systems".

Les protothreads n'utilisent pas de mechanisme de sauvegarde de contexte, mais stockent simplement l'endroit où le thread s'est arrété. En effet, tous les protothreads tournent dans une et unique pile, contrairement aux threads classiques qui utilisent une pile par thread. L'avantage des protothreads est que du coup la consommation en RAM est très faible, ne stockant que 2 octets en RAM en plus, peut importe la taille du thread.

Les protothreads sont basés sur un fonctionnement similaire à ce qui est appelé "La machine de Duff". Ils ont été développés afin de faciliter le développement d'application sur des systèmes très contraint en mémoire, comme notre cc430. L'objectif est de réduire, voir supprimer, les machines à états très courantes dans la programmation orienté évennement très courante dans les systèmes contraints, mais aussi très complexe.

Grâce à l'utilisation peu évidente du block switch il est possible de continuer l'exécution d'un protothread à partir d'un point précis de celui ci : c'est le méchanisme appelé de "local continuation".

L'inconvéniant majeur des protothreads est l'augmentation de l'utilisation en ROM.

Selon le document de Adam Dunkels & co, l'augmentation en ROM est variable en fonction de la complexité, pouvant atteindre plus de 70% d'augmentation dans les pires cas, et environ 15% dans les cas moyens. En revanche, il y a un gain énorme en RAM, passant de 18 octets utilisés en RAM pour un thread classique à 2 octets en protothread, soit une réduction de 89%.

L'utilisation des protothread est donc un compromis entre occupation en RAM, occupation en ROM et complexité de code.

Il est a noté que les protothread empêche l'utilisation du switch dans un thread et que l'utilisation de variable locale est très limité, ces variables étant perdu au changement de "contexte".


L'idéal est que j'arrive à retirer la couche des protothreads pour gagner de la place en ROM, mais cela risque d'être complexe de repasser sur une programmation orienté évènement avec une machine a états complexe.

uIP

En parrallèle des recherches effectuées sur le fonctionnement des protothreads, j'ai continué à chercher des informations sur la couche IP de Contiki, qui a apparemment été extraite plusieurs fois. Je n'ai pas réussi a trouver des informations à ce sujet, mais je trouvé la source de la couche IP de Contiki : une implémentation réduite de la couche IP par Adam Dunkels.

Cette implémentation a été par la suite implémentée dans Contiki, ce qui est pratique car cela est la base de mon travail.

Adam Dunkels propose deux version de son implémentation : uIP (microIP) et lwIP (lightweightIP).

Liste des features implémentées par uIP et lwIP :

Feature uIP lwIP
IP and TCP checksums x x
IP fragment reassembly x x
IP options
Multiple interfaces x
UDP x
Multiple TCP connections x x
TCP options x x
Variable TCP MSS x x
RTT estimation x x
TCP flow control x x
Sliding TCP window x
TCP congestion control x
Out-of-sequence TCP data x
TCP urgent data x x
Data buffered for rexmit x

Comparatif des tailles :

uIP

Fonction Taille (octet)
checksum 712
IP, ICMP, TCP 4452
Total 5164

lwIP

Fonction Taille (octet)
Gestion de la mémoire 3142
Interfaces Réseaux 458
checksum 1116
IP, ICMP, TCP 14840
Total 21756

Comme l'application que l'on souhaite réalisé ne nécessite pas beaucoup d'interfaces (une suffit), avec des paquet courts, on peut se passer de controle de congestion si besoin entre autre, l'implémentation uIP peut nous convenir.

Partir sur uIP et les sources déjà existantes de Contiki parait une bonne option. Malheureusement uIP et lwIP fonctionnenet sur le principe des protothreads aussi, ce qui risque d'être compliqué.

RPL

Avant de continuer à explorer la couche IP de Contiki et l'implémentation de uIP, j'ai voulu voir comment fonctionnait la couche RPL de Contiki.

Contiki propose 2 versions de RPL : rpl-classic et rpl-lite.

rpl-classic propose une implémentation plus complète et robuste mais rpl-lite propose une implémentation plus légère. Comme nous sommes contraint par la taille, je suis parti sur l'utilisation de RPL-lite.

Toute la bibliothèque RPL de Contiki se trouve dans le répertoire contiki-ng/os/net/routing/rpl-lite

Cette bibliothèque comprend les fichiers suivants :

rpl.c
rpl-conf.h
rpl-const.h
rpl-dag.c
rpl-dag.h
rpl-dag-root.c
rpl-dag-root.h
rpl-ext-header.c
rpl-ext-header.h
rpl.h
rpl-icmp6.c
rpl-icmp6.h
rpl-mrhof.c
rpl-nbr-policy.c
rpl-neighbor.c
rpl-neighbor.h
rpl-of0.c
rpl-timers.c
rpl-timers.h
rpl-types.h

J'ai extrait la liste de toutes les fonctions extérieures à ces fichiers appelées par les fonctions de la bibliothèque RPL-lite et j'ai commencé à les explorer pour voir si elles étaient reliées aux protothreads. Il s'avère que plusieurs fonctions appellent des fonctions définies dans sys/ctimer.h/c qui lancent des protothreads.

Comme uIP et RPL utilisent les protothreads, je penses que le mieux est de garder au final les protothread et conserver un semblant d'architecture de Contiki.

Afin de mieux de déterminer la marche à suivre afin d'extraire les sources de RPL, j'ai établis la liste des dépendances de RPL.

Les fichiers headers importés par les différents fichiers sources de RPL :

#include "contiki.h"
#include "contiki-net.h"
#include "net/ipv6/uip-ds6-route.h"
#include "net/ipv6/uip-sr.h"
#include "net/nbr-table.h"
#include "net/link-stats.h"
#include "net/routing/routing.h"
#include "net/packetbuf.h"
#include "lib/random.h"
#include <limits.h>
#include "uip.h"
#include "uip-ds6.h"
#include "uip-ds6-nbr.h"
#include "net/ipv6/uiplib.h"
#include "lib/list.h"
#include "net/ipv6/uip.h"
#include "net/ipv6/uip-ds6.h"
#include "sys/ctimer.h"

J'ai ensuite procédé à faire l'état des lieux de toutes les fonctions externes utilisées par RPL, si elles étaient dans uIP et si elles étaient nécessaire.

rpl.c
   ok
rpl-conf.h
   ok
rpl-const.h
   ok
rpl-dag.c
   rpl_dag_get_root_ipaddr
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> ok, in uip
   rpl_dag_leave
       link_stats_reset => os/net/link-stats.c/h ---> needed
       uip_sr_free_all => os/net/ipv6/uip-sr.c/h ---> needed, but not in uip
   rpl_is_addr_in_our_dag
       uip_ipaddr_prefixcmp => os/net/ipv6/uip.h ---> needed, not in uip
   rpl_dag_update_state
       nbr_table_head => os/net/nbr-table.c/h ---> needed, not uip
       clock_time => arch/cpu/msp430/....
       nbr_table_next => os/net/nbr-table.c/h ---> needed, nopt in uip
       ABS => os/sys/cc.h ---> valeur absolu, easy to cc
   update_nbr_from_dio
       uip_ds6_nbr_lladdr_from_ipaddr => os/net/ipv6/uip-ds6-nbr.c/h ---> needed, not in uip
       nbr_table_add_lladdr => os/net/nbr-table.c/h ---> needed, not in uip
   rpl_process_dio
       uip_ipaddr_cmp => os/net/ipv6/uip.h ---> ok, in uip
   rpl_process_dao
       uip_sr_expire_parent => os/net/ipv6/uip-sr.c/h ---> needed, not in uip
       uip_sr_update_node => os/net/ipv6/uip-sr.c/h ---> needed, not in uip
   rpl_dag_init_root
       uip_ipaddr_cmp => os/net/ipv6/uip.h --> deja vu
rpl-dag.h
   #include os/net/ipv6/uip.h => types ---> différence de structures, mais même noms
rpl-dag-root.c
   rpl_dag_root_print_links
       uip_sr_num_nodes => os/net/ipv6/uip-sr.c/h ---> needed, not in uip
       uip_sr_node_head => os/net/ipv6/uip-sr.c/h ---> needed, not in uip
       uip_sr_link_snprint => os/net/ipv6/uip-sr.c/h ---> not needed
       uip_sr_node_next => os/net/ipv6/uip-sr.c/h ---> needed, not in uip
   set_global_address
       uip_ip6addr => os/net/ipv6/uip.h ---> ok, in uip
       uip_ds6_set_addr_iid => os/net/ipv6/uip-ds6.c/h ---> needed, not in uip
       uip_ds6_addr_add => os/net/ipv6/uip-ds6.c/h ---> needed
   rpl_dag_root_start
       uip_is_addr_linklocal => os/net/ipv6/uip.h ---> needed, not in uip
       uip_ds6_addr_lookup => os/net/ipv6/uip-ds6.c/h ---> needed, not in uip
rpl-dag-root.h
   ok
rpl-ext-header.c
   rpl_ext_header_srh_get_next_hop
       uip_sr_get_node => os/net/ipv6/uip-sr.c/h --->needed, not in uip
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> seen
       uip_create_linklocal_prefix => os/net/ipv6/uip.h ---> needed
   rpl_ext_header_srh_update
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> seen
   insert_srh_header
       uip_sr_get_node => os/net/ipv6/uip-sr.c/h ---> seen
       uip_sr_is_addr_reachable => os/net/ipv6/uip-sr.c/h
       MIN => os/sys/cc.h
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> seen
   rpl_ext_header_hbh_update
       UIP_HTONS => os/net/ipv6/uip.h
       nbr_table_get_from_lladdr => os/net/nbr-table.c/h
       packetbuf_addr => os/net/packetbuf.c/h
   update_hbh_header
       UIP_HTONS => os/net/ipv6/uip.h
   insert_hbh_header
       UIP_HTONS => os/net/ipv6/uip.h
   rpl_ext_header_update
       uip_is_addr_linklocal => os/net/ipv6/uip.h
       uip_is_addr_mcast => os/net/ipv6/uip.h
       uip_ds6_is_my_addr => os/net/ipv6/uip-ds6.c/h
rpl-ext-header.h
   ok
rpl.h

rpl-icmp6.c
   UIP_ICMP6_HANDLER => os/net/ipv6/uip-icmp6.h
   rpl_icmp6_update_nbr_table
       uip_ds6_nbr_lookup => os/net/ipv6/uip-ds6-nbr.c/h
       uip_ds6_nbr_add => os/net/ipv6/uip-ds6-nbr.c/h
       packetbuf_addr => os/net/packetbuf.c/h
   dis_input
       uip_is_addr_mcast => os/net/ipv6/uip.h
       uip_clear_buf => os/net/ipv6/uip.h
   rpl_icmp6_dis_output
       uip_icmp6_send => os/net/ipv6/uip-icmp6.h
   dio_input
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> seen
       uip_clear_buf => os/net/ipv6/uip.h
   rpl_icmp6_dio_output
       uip_icmp6_send => os/net/ipv6/uip-icmp6.c/h
   dao_input
       uip_ipaddr_copy => os/net/ipv6/uip.h ---> seen
       uip_clear_buf => os/net/ipv6/uip.h
   rpl_icmp6_dao_output
       uip_icmp6_send => os/net/ipv6/uip-icmp6.c/h
   rpl_icmp6_init
       uip_icmp6_register_input_handler => os/net/ipv6/uip-icmp6.h
rpl-icmp6.h
   type
rpl-mrhof.c
   ok
rpl-nbr-policy.c
   ok
rpl-neighbor.c
   NBR_TABLE_GLOBAL => os/net/nbr-table.c/h
   rpl_neighbor_snprint
       uiplib_ipaddr_snprint => os/net/ipv6/uiplib.c/h
       snprintf => os/lib/dbg-io/snprintf.c
       link_stats_is_fresh => os/net/link-stats.c/h
   rpl_neighbor_set_preferred_parent
       nbr_table_unlock => os/net/nbr-table.c/h
       nbr_table_lock => os/net/nbr-table.c/h
       uip_ds6_defrt_rm => os/net/ipv6/uip-ds6-route.c/h
       uip_ds6_defrt_lookup => os/net/ipv6/uip-ds6-route.c/h
       uip_ds6_defrt_add => os/net/ipv6/uip-ds6-route.c/h
rpl-neighbor.h
   ok
rpl-of0.c
   ok
rpl-timers.c
   /!\ Utilisation des timers de contiki !
rpl-timers.h
   /!\ utilisation des tilers de contiki !
rpl-types.h
   ok

On remarque que la très grande majorité des fonctions appelées sont nécessaire et ne sont pas présentes dans uIP de base. Il s'agit de fonction liées à la gestion des voisins ou de liaison entre le protocole de routage et la couche IP. Retirer une de ces fonctions entrainera au mieux une instabilité du protocole RPL, au pire son non fonctionnement.

Hello World et taille de IP/RPL

J'ai continué à essayer en parallèle de réduire la taille de Contiki. J'ai transformé le driver radio du cc2420 utilisé par le Tmote Sky en driver "vide", car je vais devoir le remplacer de toute façon part un autre driver radio, adapté au cc1101 du cc430. Pour ce faire, j'ai commenté toutes les fonctions, et si à la compilation une fonction est manquante, je l'ai décommentée. J'ai de plus transformé son code en un simple return du style return RADIO_RESULT_NOT_SUPPORTED;. Grâce a cette opération, j'ai réussi a déscendre en dessous des 32ko de ROM :

text data bss
31684 184 3118


J'ai ensuite déterminé la taille de mon driver radio (simple compilation de toutes les fonction du driver):

text data bss
1616 0 82


On remarque qu'il y a toujours un dépassement de mémoire en ROM. Je ne vois pas comment réduire plus que cela l'empreinte mémoire du code.

J'ai aussi remarqué que le code d'exemple de Contiki pour un simple hello-world prend beaucoup de place :

text data bss
41600 222 6946


ce qui est très étrange sachant que l'exemple n'effectué qu'un simple printf :

PROCESS(hello_world_process, "Hello world process");
AUTOSTART_PROCESSES(&hello_world_process);
/*---------------------------------------------------------------------------*/
PROCESS_THREAD(hello_world_process, ev, data)
{
 static struct etimer timer;

 PROCESS_BEGIN();

 /* Setup a periodic timer that expires after 10 seconds. */
 etimer_set(&timer, CLOCK_SECOND * 10);

 while(1) {
   printf("Hello, world\n");

   /* Wait for the periodic timer to expire and then restart the timer. */
   PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer));
   etimer_reset(&timer);
 }

 PROCESS_END();
}

Je me suis rendu compte en fait que même si cette exmple n'effectue qu'un simple printf, Contiki fait en sorte de mettre en place tout un réseau RPL et IPv6 en arrière plan. En modifiant le makefile, en ajoutant les lignes suivantes

#MAKE_MAC = MAKE_MAC_NULLMAC
#MAKE_NET = MAKE_NET_NULLNET
#MAKE_ROUTING = MAKE_ROUTING_NULLROUTING

et en ajoutant les paramètres de configurations suivants

#define ROUTING_CONF_NULLROUTING 1

et en utilisant non pas le driver radio du cc2420 mais le driver nullradio_driver en modifiant le fichier sky-def.h de

#define NETSTACK_CONF_RADIO   cc2420_driver

à

#define NETSTACK_CONF_RADIO   nullradio_driver

on obtient la taille suivante

text data bss
11984 86 2588


Cela permet de se rendre compte que toute la couche RPL/IP occupe à elle seul près de 30ko de ROM, soit la quasi totalité de la ROM disponible du cc430.

En combinant ces options de compilation/makefile et mes modifications (retraits), on obtient un programme "vide" (pas de radio, pas de routage, pas de couche IP, pas de gestion des GPIO/UART/SPI/LED...) de la taille anoncé par Contiki :

text data bss
3258 22 422

Nouvelle solution : Routage semi statique, semi dynamique

Concept

Après constatation que RPL ne pouvait tenir dans les cc430, j'ai proposé une autre solution afin de déployer un réseau de capteur basé sur RPL et en partie les cc430 : des "nuages" de cc430 communiqueront leurs informations en lien direct avec un Tmote sky dédié, et ce sont les Tmote sky qui se chargeront de la remontée d’information par RPL. RPL étant nativement supporté pour les Tmote sky, cette partie est donc très facile a mettre en place.

Les Tmote sky utilisent un cc2420 comme module radio, utilisant la bande des 2.4GHz, tandis que les cc430 utilisent un cc1101 intégré utilisant la bande des 860MHz. Il faut donc trouver une solution afin de faire communiquer les deux cartes. Les Tmote sky et les cc430 ont les pins RX/TX de leur module UART0 de facilement accessible, et à première vue, la liaison série est aussi supportée par COntiki. J'ai proposé d'utiliser un cc430 comme passerelle, connecté au Tmote sky par liaison série, entre les capteurs et les relais RPL.

Cc430bvrxtx.jpg Tmoterxtx.png

Suite à l'entretien avec M. Vantroys du vendredi 1er février, cette solution a été acceptée. Je vais donc travaillé sur cette nouvelle version.

Il a été décidé que les cc430 et les Tmote Sky doivent tous fonctionner sous Contiki. Les cc430 transmettrons leurs informations par radio avec el protocole Alohah de base (c'est à dire émission quoi qu'il arrive), mais si possible avec au minimum du CSMA (Carrier Sense Medium Access), c'est à dire émettre que la la fréquence porteuse est disponible.

RPL et Tmote Sky

J'ai commencé par utiliser l'outil d’émulation de Contiki, Cooja. Le tutoriel (https://github.com/contiki-ng/contiki-ng/wiki/Tutorial:-Running-Contiki%E2%80%90NG-in-Cooja) est assez clair et Cooja est rapide à prendre en main. J'ai pu vérifié comment modifier les sources de l'exemple rpl-udp pour les adapter a notre besoin. Avec Cooja, mettre en place un tel réseau est très simple.

J'ai ensuite essayé de faire un test réel et de le déployer avec des Tmote sky : aucun message de remontait à la racine. Grâce à l'option de debug présente dans cc2420.c, j'ai pu déterminé que le problème était le suivant : peu importe la situation, le cc2420 déterminait que le CCA (Clear Channel Assessement, vérification que le canal est disponible) échouait à chaque transmission. En modifiant l'option

#define WITH_SEND_CCA 1

en

#define WITH_SEND_CCA 0

de cc2420.h , les transmissions se faisait correctement. Je laisse cette option comme ça pour le moment car ça me permet de continuer travailler sur d'autre partie du projet plus importantes, mais cela doit être corrigé à l'avenir si possible, cela peut saturer le réseau si trop de nœuds sont déployés.

Port du cc430

J'ai ensuite commencé un début de port basique pour le cc430. Comme le cc430 et le Tmote Sky sont tous les deux basés sur un msp430, un port basique devrait être assez facile. Contiki supporte les msp430f1xxx, msp430f2xxx et msp430f5xxx, et notre cc430 est basé sur un msp430f5 (msp430f5137). J'ai donc simplement copié/collé le dossier du Tmote sky en renommant toutes les occurrence du mot "sky" en "cc430BV". J'ai aussi modifié le fichier platform.c afin de retirer tout ce qui n'est pas utile.

J'ai modifié le fichier Makefile.common afin de prendre en compte le bon linker (passage de link430f1611 à link430f5137) et changement de la définition du µP utilisé (passage de -D__CC430F1611__=1 à -D__CC430F5137__=1) De même, changement de la variable de makefile MCU en cc430f5137. Il est important de noter que pour notre µP, il faut utiliser cc430f5137 et non msp430f5137 car il ne s'agit pas tout à fait du même fichier de header à inclure.

J'ai ajouté un dossier cc430 contenant les sources de base pour utilisé la radio de manière ad hoc dans le répertoire arch/cpu/msp430

En tentant une première compilation, plusieurs problèmes sont apparus : des noms de registres n'étaient pas trouvés dans les fichiers watchdog.c, flash.c et rom.c, et les fichiers leds.c et leds-arch.c généraient des erreurs car le mapping des LED n'était pas gérée.

Suite à une discussion sur le Gitter de Contiki-NG avec l'un des développeurs, j'ai appris que le port pour le msp430 était l'un des premiers à avoir été réalisé, et donc contenait un certain nombre d'erreur ou d'approximation. Par exemple, dans le fichier du module cpu (dont sensé être indépendant de la board) watchdog.c il y une référence à une board qui n'est plus supportée par Contiki

#if CONTIKI_TARGET_WISMOTE

Il y a de plus des noms de registre qui ne correspondent pas à tous les msp430 (par exemple IE1 et SFRIE1 qui sont pourtant le même registre), ou encore des msp430 qui n'ont pas le registre IE2 alors que les sources de Contiki le réclame.

Pour le moment j'ai corrigé ces erreurs fichier par fichier, mais je devrais faire plus tard un fichier header afin de regrouper toutes les différences. Pour le moment, j'ai modifié le fichier Makefile.msp430 afin de faire une sélection plus fine du µP utilisé :

Pour Makefile.msp430 ,passage de

ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f1,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f1xxx
 endif
endif
ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f5,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f5xxx
 endif
endif
ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f2,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f2xxx f1xxx
 endif
endif

à

ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f1,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f1xxx
  CFLAGS += -DMSP430FAM=661
 endif
endif
ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f5,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f5xxx
  CFLAGS += -DMSP430FAM=665
 endif
endif
ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring msp430f2,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f2xxx f1xxx
  CFLAGS += -DMSP430FAM=662
 endif
endif
ifndef CONTIKI_CPU_FAM_DIR
 ifneq (,$(findstring cc430f5,$(MCU)))
  CONTIKI_CPU_FAM_DIR = f5xxx cc430
  CFLAGS += -DMSP430FAM=665
 endif
endif

Modification des fichiers posant problèmes en remplaçant toutes les occurances des *IEx par un nom générique GIEx :

#if MSP430FAM == 665
#define GIE1  SFRIE1
#define GIFG1 SFRIFG1
#else
#define GIE1 IE1
#define GIFG1 IFG1
#endif

J'ai de plus retiré les sources leds.c et leds-arch.c du makefile du µP msp430, car cela est en rapport avec la board et non le µP

Changement :

MSP430     += msp430.c flash.c clock.c leds.c leds-arch.c\
              watchdog.c lpm.c rtimer-arch.c int-master.c

en

MSP430     += msp430.c flash.c clock.c \
              watchdog.c lpm.c rtimer-arch.c int-master.c

Un fois ce "port" effectué, j'ai récupéré auprès de M. Vantroys des sources afin de contrôler les LED sur les boards cc430BV. Mon premier objectif sera de schéduler un clignotement avec Contiki afin de vérifier que l'OS fonctionne réellement.

Utilisées seules, les sources pour les LED fonctionnent sans problème, il est facile de faire clignoter les LED, mais dès que je les intègre à Contiki, elles ne fonctionnent plus : les LED s'allument une fois puis restent allumées.

Il s'agit en fait de la manière dont les LED sont commandées : il faut leur envoyer un signal PWM avec la couleur voulus pour les allumer, et une des LED utilise le TimerA1 pour gérer sa PWM. TimerA1 est utilisé par Contiki pour gérer le temps, et donc il est impératif de laisser ce timer de coté. J'ai retiré toutes les références à ce timer et cette fois il est possible d'utiliser une des LED. L'autre n'est pas utilisable tel quel, mais cela n'est pas important pour le projet donc je la laisse inutilisée.

L'étape suivante est de transmettre et recevoir des messages par radio entre cc430BV. Je commence par une transmission utilisant les source ad hoc sans contrôle de l'utilisation de la fréquence d'émission (Alohah et pas de CSMA).

En utilisant la configuration suivante :

WDTCTL = WDTPW + WDTHOLD;
port_mapping();
SetVCore(3);

cc430_radio_reset_radio_core();

PMMCTL0_H = 0xA5;
PMMCTL0_L |= PMMHPMRE_L;
PMMCTL0_H = 0x00;

cc430_radio_write_burst_reg(IOCFG2, (unsigned char*)RF1A_REGISTER_CONFIG, CONF_REG_SIZE);
cc430_radio_write_pa_table(cc430_radio_POWER_OUTPUT_10DBM);

cc430_radio_strobe( RF_SIDLE );
cc430_radio_strobe( RF_SFRX);

_BIS_SR(GIE);
//cc430_radio_receive_off(); /*off si emetteur */
cc430_radio_receive_on(); /*on si recepteur */

j'arrive a schéduler des envois de messages radio et a les recevoir. Il est important de noter qu'il faut absolument attendre la fin d'une transmission avant de se mettre en état RF_SIDLE ou de flush le buffer de transmission.

Liaison série des Tmote Sky

L'étape suivante est de gérer la liaison UART du cc430 et du Tmote sky.

A première vu, uart0 est géré par Contiki pour les msp430f5xxx mais pas pour les msp430f1xxx, mais uart1 l'est. D'après la documentation de TI, il n'y pas de différence majeur entre uart0 et uart1 a part les noms des registres associés. Utiliser la liaison série devrait être simple.

Utilisée sans Contiki, uart0 fonctionne sans problème, mais dès que j'ai utilisé la liaison série avec l'OS, elle ne fonctionne plus.

Juste après l'initialisation de l'uart0, dans la fonction platform_init_stage_two du fichier platform.c du Tmote sky, je transmet le caractère 0xAA, et le résultat est très étrange : je n'ai pas le même comportement entre chaque reboot : UartKo.jpgUartOk.jpg

J'ai passé beaucoup de temps a essayé de comprendre d'où vient le problème, et j'ai trouvé :

PbPin.PNGUartvsspi.PNG

Le module radio cc2420 du Tmote sky utilise la liaison SPI géré par UART0 pour communiquer avec le msp430f1611, et le module UART0 ne peut fonctionner en même temps en liaison série et en SPI. Cela pose un très gros problème, la liaison série et la radio sont obligatoire pour le bon fonctionnement du projet.

J'ai trouvé une solution au problème de l'uart0 pour le Tmote sky. Comme Contiki est un OS à scheduler participatif et non préamptif, une boucle d'attente bloquera l'OS. Comme le rafraîchissement des données n'a pas a être instantanée, une récupération de la valeurs des capteurs toutes les minutes par exemple est suffisant. On peut donc lancer un thread du genre pour un Tmote sky "nœud"

PROCESS_THREAD(skyUart_process, ev, data)
{
  static struct etimer timer;

  PROCESS_BEGIN();
  printf("Process start uart\n"); /* affiché qu'une seul fois au boot */

  /* Setup a periodic timer that expires after 60 seconds. */
  etimer_set(&timer, CLOCK_SECOND * 60);

  while(1) {

    /* Wait for the periodic timer to expire and then restart the timer. */
    PROCESS_WAIT_EVENT_UNTIL(etimer_expired(&timer));
    cc2420_off(); /* On éteint la radio, évite de recevoir un message qui ne sera pas lu */
    uart0_init(115200); /* initialisation de uart0 */
    uart0_writeb(0xAA); /* Envoi d'un token au cc430 afin qu'il transmette les informations reçues */
    while(!rcvCompltete){ /* Tant que l'on a pas tout reçu on attend */
      watchdog_periodic(); /* Afin d'éviter un reboot */
    }
    printf("%d datas recu\n", bufferrcv[0]); /* On affiche les données reçues */
    for(cpt = 1; cpt < bufferrcv[0]; cpt++)
    {
      printf("capteur %d statut % d\n",bufferrcv[cpt], bufferrcv[cpt+1] );
      bufferrcv[cpt] = 0;
      cpt++;
    }

    rcvCompltete = 0; /* Reception uart traitée */
    bufferrcv[0] = 0; /* Reset du nombre d'octet reçu */
    cc2420_init(); /* on réinitialise le cc2420 */
    etimer_reset(&timer); /* on attent de nouveau 60 seconde */
 }

 PROCESS_END();
}

La réception des caractères sur la liaison série se fait par interruption avec la routine suivante :

ISR(UART0RX, uart0_rx_interrupt)
{
  uint8_t c;
  static uint8_t state = 0;
  static int cptbuf = 0;
  static int cptbuftarget;

  if(!(URXIFG0 & IFG1)) {
    /* Edge detect if IFG not set? */
    U0TCTL &= ~URXSE; /* Clear the URXS signal */
    U0TCTL |= URXSE;  /* Re-enable URXS - needed here?*/
    LPM4_EXIT;
  } else {
    /* Check status register for receive errors. */
    if(URCTL0 & RXERR) {
      c = RXBUF0;   /* Clear error flags by forcing a dummy read. */
    } else {
      c = RXBUF0;
      if(state == 0)
      {

        bufferrcv[0] = c;
        if (c != 0)
        {
          state++;
          bufferptr = 1;
          cptbuf = 0;
          cptbuftarget = c;
        }else
        {
          rcvCompltete = 1;
        }
       
      }else if(state == 1)
      {
        cptbuf++;
        bufferrcv[bufferptr++] = c;
        if(cptbuf == cptbuftarget)
        {
           state = 0;
           rcvCompltete = 1;
        }
      }
      LPM4_EXIT;
     
    }
  }
}

Pour tester cette méthode, un autre Tmote sky "passerelle" a été utilisée sans utilisation de la radio pour ne pas parasiter la liaison série :

char buf[] = {42 , 0 , 13 , 1, 8 , 1 };
PROCESS_THREAD(skyUartRecv_process, ev, data)
{
  PROCESS_BEGIN();
  printf("Debut d'ecoute\n");

  while(1) {
     PROCESS_YIELD(); /* On attent la réception du token */
     printf("Envoi des infos\n");
     uart0_init(115200); /*ici au cas ou, ne devrait pas être nécessaire */

     uart0_writeb(6); /*nombre d'octects à lire */
     for (cptsend = 0; cptsend < 6; cptsend++)
     {
       uart0_writeb(buf[cptsend]);
       printf("%d transmi\n", buf[cptsend]);
     }
     printf("infos envoyes\n");
     buf[1]++; /*modification pour tester la reception */
     buf[3]++;
     buf[5]++;

  }

  PROCESS_END();
}


ISR(UART0RX, uart0_rx_interrupt)
{
  uint8_t c;
 
  if(!(URXIFG0 & IFG1)) {
    c = RXBUF0;
    /* Edge detect if IFG not set? */
    U0TCTL &= ~URXSE; /* Clear the URXS signal */
    U0TCTL |= URXSE;  /* Re-enable URXS - needed here?*/
    LPM4_EXIT;
 } else {
    /* Check status register for receive errors. */
    if(URCTL0 & RXERR) {
      c = RXBUF0;   /* Clear error flags by forcing a dummy read. */
    } else {
      c = RXBUF0;
      if(c == 0xAA)
      {
        process_poll(&skyUartRecv_process);
      }
    }
  }
}

Cette solution a le mérite de fonctionner en théorie mais bloque les réception radio durant un temps plus ou moins long. Cela peut perturber le routage, mais comme on stop la radio, on ne recevra aucun message, donc n'enverra aucun ack, donc en théorie le protocole RPL devrait trouver un autre chemin pour transmettre les informations.

Malheureusement, le Tmote sky "passerelle" reçoit bien le token, et transmet bien les informations via la liaison série, mais le Tmote sky "nœud" ne réagit pas à la réception des messages.

Après un certains temps a essayé de comprendre pourquoi ma solution ne fonctionnait pas, je me suis rendu compte qu'elle fonctionnait en fait, mais la réception des messages était retardée de plus de 10 secondes. Je ne trouve pas d'où peut venir ce bug, pas très gênant pour mes tests, mais pouvant être très gênant en condition réelle, retardant de beaucoup la gestion de la radio.

Il ne me reste plus qu'à gérer la liaison série sur le cc430BV passerelle et un prototype de réseau de capteur devrait être déployable.

Documentation

Documents Rendus

Rapport intermédiaire : Fichier:RapportIntermédiaire.pdf

Présentation intermédiaire : Fichier:SoutenanceIntermédiareDiapoP13.pdf