IMA5 2018/2019 P13 : Différence entre versions
(→Semaine du 01/10) |
m (→Documents Rendus) |
||
(24 révisions intermédiaires par le même utilisateur non affichées) | |||
Ligne 163 : | Ligne 163 : | ||
Avec ces même modifications, une compilation avec comme cible le cc430 indique un overflow en RAM de 1.6Ko de RAM. | 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 à : | ||
+ | |||
+ | {| class="wikitable" | ||
+ | ! 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. | ||
+ | |||
+ | {|class="wikitable" | ||
+ | ! 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 <code>process.h</code>. | ||
+ | |||
+ | Toutes ces macros rendent le code lisible dans un contexte de processus lancés en parallèles mais rendent l'extraction plus complexe. | ||
+ | |||
+ | <code>PROCESS</code> permet de simplement déclarer un thread et une structure associer à ce thread. | ||
+ | |||
+ | <code>PROCESS_THREAD</code> permet de définir le thread déclaré par <code>PROCESS_THREAD</code> | ||
+ | |||
+ | 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 <code>-E</code> dans le makefile de Contiki pour que gcc s'arrête après l'étape de précompilation. | ||
+ | |||
+ | Pour exemple, l'option <code>-E</code> 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 <code>#if</code>, mais le problème vient du block <code>switch</code> | ||
+ | |||
+ | En effet, on remarque une structure très étrange du code : nous avons un <code>case 837</code> dans le block même du <code>case 0</code>. De plus, on trouve un <code>do {...} while(0)</code> dans un <code>while(1)</code> | ||
+ | |||
+ | 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 <code>switch</code> 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 <code>switch</code> 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 : | ||
+ | |||
+ | {|class="wikitable" | ||
+ | ! 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 | ||
+ | |||
+ | {|class="wikitable" | ||
+ | ! Fonction !! Taille (octet) | ||
+ | |- | ||
+ | | checksum | ||
+ | | 712 | ||
+ | |- | ||
+ | | IP, ICMP, TCP | ||
+ | | 4452 | ||
+ | |- | ||
+ | | Total | ||
+ | | 5164 | ||
+ | |} | ||
+ | |||
+ | lwIP | ||
+ | |||
+ | {|class="wikitable" | ||
+ | ! 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 : <code>rpl-classic</code> et <code>rpl-lite</code>. | ||
+ | |||
+ | <code>rpl-classic</code> propose une implémentation plus complète et robuste mais <code>rpl-lite</code> 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 <code>contiki-ng/os/net/routing/rpl-lite</code> | ||
+ | |||
+ | 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 <code>sys/ctimer.h/c</code> 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 | ||
+ | <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 : | ||
+ | |||
+ | {| class="wikitable" | ||
+ | ! 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): | ||
+ | {| class="wikitable" | ||
+ | ! 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 : | ||
+ | {| class="wikitable" | ||
+ | ! 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 <code>sky-def.h</code> de | ||
+ | |||
+ | #define NETSTACK_CONF_RADIO cc2420_driver | ||
+ | |||
+ | à | ||
+ | #define NETSTACK_CONF_RADIO nullradio_driver | ||
+ | |||
+ | on obtient la taille suivante | ||
+ | {| class="wikitable" | ||
+ | ! 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 : | ||
+ | {| class="wikitable" | ||
+ | ! 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. | ||
+ | |||
+ | [[Fichier:cc430bvrxtx.jpg|600px]] [[Fichier:tmoterxtx.png|600px]] | ||
+ | |||
+ | 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 le 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 <code>rpl-udp</code> pour les adapter a notre besoin. Avec Cooja, mettre en place un tel réseau est très simple. | ||
+ | |||
+ | [[Fichier:coojaRPL.png|1000px]] | ||
+ | |||
+ | 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 <code>cc2420.c</code>, 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 <code>cc2420.h</code> , 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 <code>platform.c</code> afin de retirer tout ce qui n'est pas utile. | ||
+ | |||
+ | J'ai modifié le fichier <code>Makefile.common</code> 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 <code>MCU</code> 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 <code>cc430</code> contenant les sources de base pour utilisé la radio de manière ad hoc dans le répertoire <code>arch/cpu/msp430</code> | ||
+ | |||
+ | En tentant une première compilation, plusieurs problèmes sont apparus : des noms de registres n'étaient pas trouvés dans les fichiers <code>watchdog.c</code>, <code>flash.c</code> et <code>rom.c</code>, et les fichiers <code>leds.c</code> et <code>leds-arch.c</code> 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) <code>watchdog.c</code> 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 <code>Makefile.msp430</code> afin de faire une sélection plus fine du µP utilisé : | ||
+ | |||
+ | Pour <code>Makefile.msp430</code> ,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 <code>leds.c</code> et <code>leds-arch.c</code> 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 <code>RF_SIDLE</code> 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 <code>platform_init_stage_two</code> du fichier <code>platform.c</code> du Tmote sky, je transmet le caractère <code>0xAA</code>, et le résultat est très étrange : je n'ai pas le même comportement entre chaque reboot : | ||
+ | [[Fichier:uartKo.jpg|400px]][[Fichier:uartOk.jpg|400px]] | ||
+ | |||
+ | J'ai passé beaucoup de temps a essayé de comprendre d'où vient le problème, et j'ai trouvé : | ||
+ | |||
+ | [[Fichier:pbPin.PNG|400px]][[Fichier:uartvsspi.PNG|400px]] | ||
+ | |||
+ | 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= | =Documentation= | ||
=Documents Rendus= | =Documents Rendus= | ||
+ | Rapport intermédiaire : [[Fichier:RapportIntermédiaire.pdf]] | ||
+ | |||
+ | Présentation intermédiaire : [[Fichier:SoutenanceIntermédiareDiapoP13.pdf ]] | ||
+ | |||
+ | Rapport final : [[Fichier:RapportFinalP13.pdf]] |
Version actuelle datée du 26 février 2019 à 17:40
Sommaire
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.
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 le 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 :
J'ai passé beaucoup de temps a essayé de comprendre d'où vient le problème, et j'ai trouvé :
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
Rapport final : Fichier:RapportFinalP13.pdf