IMA4 2018/2019 P2

De Wiki de Projets IMA
Révision datée du 25 novembre 2018 à 21:24 par Mcreteur (discussion | contributions) (Semaine 10)


NumWorks et robot

Présentation générale

Description

Le projet consiste à commander un robot classique par une calculatrice NumWorks avec un programme Python. Après avoir conçu un robot classique à base d'Arduino, moteurs, sonar ultrason et suiveurs de lignes, il faudra modifier le logiciel d'une calculatrice NumWorks afin que cette dernière soit capable de communiquer avec le robot. Une fois la communication établie, nous développerons une API afin de contrôler le robot. Finalement, nous écrirons un programme python utilisant l'API pour transformer le robot en un robot suiveur de ligne avec arrêt en cas d'obstacle et redémarrage automatique lors de la disparition de l'obstacle.


L’intérêt de ce projet serait de permettre l'initiation à la robotique aux lycéens à moindre coût. Les lycéens ayant tous à acheter eux-mêmes une calculatrice, en leur imposant la Numworks, il n'y aurait que les robots à acheter pour un peu moins de 50 euros. Ils pourraient alors découvrir le monde de la programmation robotique en utilisant l'API développée sur la Numworks qui permet la programmation Python.

Objectifs

  • Conception d'un robot classique
  • Modification du logiciel d'une calculatrice NumWorks pour établir une communication entre le robot et la calculatrice
  • Développer une API permettant le contrôle du robot
  • Développer un programme Python transformant le robot en un robot suiveur de ligne avec arrêt en cas d'obstacle et redémarrage automatique lors de la disparition de l'obstacle

Préparation du projet

Cahier des charges

Robot :

  • Concevoir un robot mobile à partir d'un Arduino Uno, capable de détecter des obstacles grâce à un capteur de distance à ultrason et de suivre une ligne grâce à des capteurs suiveurs de ligne

Communication :

  • Établir une communication entre le robot et la calculatrice NumWorks en tentant de passer le micro-contrôleur en hôte USB et en utilisant le FTDI de l'Arduino

API :

  • Développer une API permettant de contrôler le robot :
    • fonctions pour contrôler les moteurs
    • fonctions pour lire la distance du sonar
    • fonctions pour lire l'information des suiveurs de ligne

Programme Python :

  • Développer un programme Python se servant de l'API pour transformer le robot en un suiveur de ligne. Il doit permettre au robot de s'arrêter en cas d'obstacle et redémarrer lorsque l'obstacle disparaît

Choix techniques : matériel et logiciel

- Robot :

- Calculatrice NumWorks

- Shield pour Arduino : Réalisation sur Eagle

- Communication : Modification de l'OS Numworks en C++ / C

- API et programme suivi de ligne : Développés en Python


Liste des tâches à effectuer

  • Assembler le robot
  • Réaliser un shield pour l'Arduino
  • Modifier le logiciel de la calculatrice NumWorks pour établir la communication
  • Développer l'API
  • Développer le programme Python


Réalisation du Projet

Feuille d'heures

Tâche Prélude Heures S1 Heures S2 Heures S3 Heures S4 Heures S5 Heures S6 Heures S7 Heures S8 Heures S9 Heures S10 Heures S11 Heures S12 Heures S13 Total
Analyse du projet 5H
Recherches / documentation 12H 6H
Conception du robot 30min 1H
Réalisation du shield 16H30 2H30 7H 2H 2H
Établissement de la communication 3H 6H 6H 9H 3H 8H
Développement de l'API 11H 15H
Développement du programme Python

Prologue

Semaine 1

  • Acquisition du matériel :

Le matériel nécessaire à la conception du robot était déjà disponible. Récupération d'un kit à assembler pour construire un robot à deux roues, d'un Arduino Uno, d'un sonar ultrason, d'un contrôleur de moteur ainsi que de trois suiveurs de ligne.

  • Installation du SDK NumWorks :

Ayant à modifier le logiciel de la calculatrice NumWorks afin de transformer la calculatrice en un périphérique USB hôte pour pouvoir contrôler le robot, j'ai installé le SDK fourni par l'éditeur qui offre un simulateur.

On récupère dans un premier temps le code :

  git clone https://github.com/numworks/epsilon.git


Avant de pouvoir lancer le simulateur, il faut installer de nombreuses dépendances :

  apt-get install bison build-essential dfu-util flex gcc-arm-none-eabi git libfltk1.3-dev libfreetype6-dev libpng12-dev

Une fois les dépendances installées, on peut compiler :

  make PLATFORM=simulator clean
  make PLATFORM=simulator

Puis on lance le simulateur par la commande :

  ./epsilon.elf
Simulateur NumWorks


  • Recherches / Documentation  :

L'USB On-The-Go (OTG) est une spécification qui permet à un périphérique USB de se comporter comme un hôte ou un esclave selon la situation. Par exemple, cela permet à une tablette (qui est de base un périphérique esclave) de pouvoir communiquer avec un autre périphérique USB tel qu'une souris, un clavier en agissant comme périphérique hôte puis d'être esclave lorsque connecté à un ordinateur. Cette spécification OTG introduit un 5ème pin, le pin "ID". Lorsque ce dernier est connecté à la masse, le périphérique est défini comme hôte par défaut. Si le pin n'est pas connecté, le périphérique est esclave par défaut.

Le port USB de la calculatrice NumWorks n'étant censé être utilisé que pour recharger la calculatrice ou la mettre à jour, la calculatrice est un périphérique esclave. Cela est confirmé par le Schematics de la calculatrice sur lequel on voit que le pin ID de l'USB n'est pas connecté :


Schematics du port USB de la NumWorks


Le micro-contrôleur STM32F412VGT6 utilisé est cependant compatible avec l'OTG d'après sa Datasheet et l'acronyme "OTG" apparaît à de nombreuses reprises dans le code du logiciel de la calculatrice NumWorks. Il faudra donc creuser dans ce sens pour faire de la calculatrice un hôte USB.

Semaine 2

  • Recherches / Documentation  :

Recherche et lecture de documentation pouvant aider à l'établissement de la communication entre la calculatrice et le robot. On retiendra trois documents qui pourront nous aider:

- Manuel d'utilisation de la librairie USB host du logiciel de développement des STM32 STM32Cube

- Manuel d'utilisation de la librairie USB On-The-Go des STM32F

- Manuel de référence du STM32F412


  • Établissement de la communication entre la calculatrice et le robot :

Début de modification du logiciel de la calculatrice NumWorks afin d'en faire un hôte USB pour pouvoir contrôler le robot. Pour cela, le manuel de référence du STM32F412 est une aide précieuse.

Dans un premier temps, nous modifions les différents registres d'initialisation de l'USB OTG du micro-contrôleur. Cela se passe dans la fonction initOTG() du fichier /ion/src/device/usb.cpp. | | | Dans ce fichier, on s'aperçoit d'une ligne qui force l'USB à agir en tant qu'esclave :

  OTG.GUSBCFG()->setFDMOD(true);

Pour forcer la calculatrice à apparaître comme un hôte USB, on modifie la ligne de la façon suivante :

  OTG.GUSBCFG()->setFHMOD(true);

Le manuel de référence du STM32F412 nous propose une démarche pour initialiser l'USB de la calculatrice en tant qu'hôte, nous la suivons donc.


Il faut dans un premier temps initialiser le cœur. On active différents masques et interruptions à travers différents registres ainsi que les modes :

- Host negotiation protocol (HNP) qui permet au cœur de changer dynamiquement son rôle de périphérique A-Host en A-esclave et inversement ou B-host en B-esclave.

- Session request protocol (SRP) qui permet, lorsque la calculatrice est un hôte USB, d'économiser de l'énergie en éteignant le Vbus power si la session USB est suspendue


On procède ensuite à l'initialisation du mode hôte. On active le port de contrôle et le registre de statut, on sélectionne le mode de vitesse supportée. Si un périphérique USB est détecté, on active le processus de reset sur le périphérique, on se règle sur sa vitesse et on configure la taille des queues FIFO pour l'échange des données.


Il faudra ensuite procéder à l'initialisation d'un canal de communication pour que la calculatrice puisse échanger avec le robot.

Semaine 3

  • Établissement de la communication entre la calculatrice et le robot :

Initialisation des channels : afin de pouvoir communiquer avec les périphériques connectés à l'hôte, en l’occurrence ici, l'Arduino à la calculatrice, il faut initialiser des canaux de communication. Pour cela, on active les canaux voulus à travers les registres, on active certaines interruptions et masques. Il faut finalement donner une taille au canal en fonction des paquets attendus ainsi que donner les caractéristiques du endpoint que l'Arduino présentera.

Maintenant que l'initialisation a été modifié pour que la calculatrice apparaisse comme un hôte USB, il faut adapter les fonctions de communication. J'ai alors passé pas mal de temps à essayer de comprendre l'architecture du logiciel en terme de communication USB et chercher ce qu'il y a à modifier.

Semaine 4

  • Établissement de la communication entre la calculatrice et le robot :

Après avoir passé du temps à essayer de trouver les modifications à apporter aux fonctions de communication USB du logiciel Numworks, j'ai découvert que STMicroelectronics propose des bibliothèques pour aider à la programmation de leurs microcontrôleurs. On trouve dans ces dernières des fonctions toutes faites pour la configuration de l'USB pour un mode hôte ou esclave. En plus de proposer des fonctions réalisant l'initialisation du core, du mode hôte et des canaux de communication comme j'ai pu le faire au cours des deux dernières semaines, ils proposent des fonctions de communication ainsi qu'une interface d'abstraction matérielle permettant de faciliter la portabilité. J'ai donc choisi d'utiliser ces bibliothèques plutôt que de continuer à réaliser les fonctions moi-même et de risquer de faire des erreurs qui seraient sûrement difficiles à trouver au moment des tests. Cela me permettra aussi probablement de gagner du temps.

Après avoir programmé le logiciel Numworks pour utiliser la fonction d'initialisation de l'USB fournie par les bibliothèques STM, je suis confronté à un problème d'intégration au vu des erreurs apparaissant à la compilation.

Semaine 5

  • Établissement de la communication entre la calculatrice et le robot :

- Résolution du problème de la semaine 4. La semaine dernière, en voulant intégrer les bibliothèques que propose STMicroelectronics j'ai été confronté à un problème à la compilation. Après avoir utilisé un type défini par les bibliothèques intégrées dans le code du logiciel Numworks afin de faire de la calculatrice un hôte USB, j'avais une erreur à la compilation qui m'indiquait que le type utilisé n'était pas défini bien que le fichier .h était lié. J'ai alors, dans un premier temps, pensé qu'il s'agissait d'un problème au niveau des makefile suite à l'ajout des bibliothèques. Finalement, en examinant le code des .h des bibliothèques STM, je me suis rendu compte qu'ils contenaient presque tous le code suivant :

  #if defined(STM32F405xx) || defined(STM32F415xx) || defined(STM32F407xx) || defined(STM32F417xx) || \
   defined(STM32F427xx) || defined(STM32F437xx) || defined(STM32F429xx) || defined(STM32F439xx) || \
   defined(STM32F401xC) || defined(STM32F401xE) || defined(STM32F411xE) || defined(STM32F446xx) || \
   defined(STM32F469xx) || defined(STM32F479xx) || defined(STM32F412Zx) || defined(STM32F412Vx) || \
   defined(STM32F412Rx) || defined(STM32F412Cx) || defined(STM32F413xx) || defined(STM32F423xx)

Les bibliothèques proposées par STMicroelectronics sont communes à chacun de leurs microcontrôleurs. Cependant, ces derniers étant tous différents, il existe des différences au niveau des registres. Afin de pouvoir utiliser les bibliothèques, il faut donc définir le modèle de microcontrôleur utilisé dans un .h bien particulier. Sans cela, le if defined étant présent en tête de fichier et ne se fermant qu'à la fin, tout le contenu était ignoré. Après avoir défini l'utilisation du STM32F412VGT6 dans le .h adéquat, le problème de typage non reconnu à la compilation a été résolu.

Après avoir inclus les nombreux .h différents nécessaires pour l'utilisation des fonctions d'initialisation du périphérique USB, j'ai été confronté à un autre problème :


Compilation suite à l'ajout des bibliothèques STM


De nombreuses variables sont définies sous le même nom dans le logiciel Numworks et dans les bibliothèques STM mais sont utilisées différemment et dans deux langages (C++ pour Numworks, C pour bibliothèques STM). Des problèmes de collision apparaissent donc parmi d'autres à la compilation. Les variables en question étant utilisées dans de nombreux fichiers différents dont le code n'est pas forcément simple à comprendre sans l'étudier et y passer du temps, réadapter le code des bibliothèques à celui du logiciel demanderait beaucoup de temps. J'ai donc choisi de revenir sur la solution de départ et de réaliser les fonctions nécessaires moi-même en prenant appui sur ces bibliothèques.

- J'ai donc dans un second temps, repris les fonctions d'initialisation créées au cours des semaines dernières pour les réutiliser et je les ai ajusté en fonction de l'implémentation que STMicroelectronics propose dans ses bibliothèques.

Semaine 6

  • Conception du robot  :

Après avoir passé pas mal de temps sur l'établissement de la communication, j'ai décidé de laisser cela de côté pour cette semaine, j'y reviendrai plus tard. J'ai dans un premier temps assemblé le châssis du robot, chose relativement simple et rapide avec la notice fournie au kit.


Châssis assemblé
  • Réalisation du shield :

Afin de réaliser un travail propre et qu'il n'y ait pas de fils dans tous les sens, il a été demandé de réaliser un shield pour l'arduino en y intégrant les différents composants utilisés. On intégrera à ce shield :

- Le contrôleur de moteur TB6612FNG

- Des headers permettant de connecter les deux moteurs au contrôleur

- Le capteur de distance HC SR04

La batterie sera connectée au robot par le shield avec des fils.

Les capteurs optiques suiveurs de lignes devant se trouver sous le robot pour être utiles, nous réaliserons un autre PCB pour ces derniers.


J'ai décidé de réaliser ces cartes électroniques avec le logiciel Eagle, notamment parce qu'il est disponible sous Linux contrairement à Altium. Après avoir procédé à l'installation du logiciel, j'ai recherché des librairies Eagle proposant les composants dont j'ai besoin :

- Librairie SparkFun-Boards pour le template de shield Arduino Uno

- Librairie diy-modules pour le capteur de distance HC SR04

- Librairie TB6612FNG,C,8,EL pour le contrôleur de moteur TB6612FNG

- Librairie con-headers-jp pour les headers femelles

- Librairie SparkFun-PowerSymbols pour le 5V et la masse



J'ai ensuite cherché à savoir comment connecter les différents composants entre eux puis procédé à la réalisation du schematics et du PCB associé :


Schematic du shield


PCB du shield

Semaine 7

  • Réalisation du shield :

Les capteurs de ligne devant se trouver en dessous du robot pour être utilisables, j'ai réalisé une seconde carte électronique afin de minimiser le nombre de fils de connexion sur le robot. Cette carte ne comporte que des capteurs suiveurs de ligne ainsi qu'un header pour lier cette dernière au shield.

Pour réaliser cette carte, j'ai utilisé la librairie QRE1113GR qui fourni la footprint du capteur optique QRE1113GR utilisé :


Footprint du capteur optique QRE1113GR


Le schematic de la carte est le suivant :

Schematic de la carte de capteurs optiques pour le suivi de ligne


Finalement, le PCB associé :


PCB de la carte de capteurs optiques pour le suivi de ligne



  • Retour de soutenance intermédiaire  :

Suite à la soutenance intermédiaire, les objectifs du projet ont évolué. La modification du logiciel de la calculatrice Numworks étant possible mais demandant beaucoup de temps, cette tâche est abandonnée. A la place, nous utiliserons une solution déjà existante de communication en pur série avec l'Arduino : https://zardam.github.io/post/numworks-uart-over-usb/

Les objectifs sont donc désormais les suivants :

- Robot comme demandé initialement

- Mettre en place un protocole de communication série pour que la calculatrice puisse utiliser les capteurs et les actionneurs du robot

- Réaliser un câble propre pour la connexion entre la calculatrice NumWorks (port micro USB) et l'Arduino (E/S 0 et 1, masse)

- Développer une API python simple pour qu'un lycéen puisse utiliser le robot

- Développer un exemple de programme python sur la NumWorks pour utiliser le robot comme un suiveur de ligne avec arrêt en présence d'obstacles (sujet original)

- Réaliser un démonstrateur de programmation de l'Arduino par la NumWorks : Ajout d'un capteur de température DHT22 sur le robot, saisie de codes assembleur ATMega328p (hexa) sur la Numworks pour la gestion du capteur (un code d'initialisation et un code de récupération de valeur), envoi des deux codes par série sur l'Arduino puis utilisation de ceux-ci.


  • Établissement de la communication entre la calculatrice et le robot :

La communication entre la calculatrice Numworks et l'Arduino doit donc désormais se faire en série grâce à la solution https://zardam.github.io/post/numworks-uart-over-usb/

L'auteur de cette solution a mappé un UART sur les ports D+ et D- de l'USB en modifiant le logiciel Numworks. La communication série est ainsi possible en utilisant le port USB de la calculatrice.

Semaine 8

  • Réalisation du shield :

Suite à l'évolution des objectifs, j'ai été amené à réaliser quelques modifications sur le PCB. Le capteur de température DHT22 a été ajouté, une réorganisation des connexions a été mené afin de permettre un routage plus simple et plus propre et enfin, étant donné que deux digital pins étaient libres sur l'Arduino, deux LEDs ont été ajouté et pourront notamment servir au débogage.

Étant donné que le capteur de température est plus une option qu'autre chose sur le robot, plutôt que d'utiliser directement sa footprint et le souder par la suite, nous utiliserons un header. Ainsi, il pourra être enlevé et remis à souhait.

Le nouveau schématic est le suivant :


Schematic du shield Arduino


Le PCB associé :

PCB du shield Arduino


  • Établissement de la communication entre la calculatrice et le robot :

Afin que la calculatrice et l'Arduino puissent se comprendre lors de l'échange des messages, il faut définir un protocole de communication. La calculatrice doit pouvoir faire comprendre à l'Arduino les différentes commandes qu'elle veut faire exécuter, par exemple, ordonner au robot de tourner sur la gauche. Dans l'autre sens, la calculatrice doit pouvoir comprendre et interpréter les valeurs des capteurs que l'Arduino lui enverra.

Nous pensons définir un message comme suit :

- Caractère de début de message

- Taille du message

- Commande représentée par un chiffre (Par exemple, lorsque l'on enverra 1, le robot devra avancer tout droit). OU chiffre signifiant réponse de capteur

- Paramètre de commande si nécessaire OU valeur de capteur

- Checksum pour vérifier la bonne réception du message

La taille d'un message et le type de données échangées n'a pas encore été décidé. D'ailleurs, concernant le type de données échangées, une API de communication série Python a été développé par l'auteur de la modification du logiciel Numworks. Cette dernière propose deux fonctions pour l'envoi et la réception qui ont été implémenté comme suit :

  mp_obj_t writeLine(mp_obj_t a) {
    Ion::Console::writeLine(mp_obj_str_get_str(a));
    return mp_const_none;
  }
  mp_obj_t readLine() {
    Ion::Console::readLine(line, 64);
    return mp_obj_new_str(line, strlen(line), false);
  }

Ces deux dernières ayant été implémenté pour réaliser un tchat entre deux calculatrices Numworks, elles travaillent avec des String. Nous verrons si nous décidons d'utiliser ces dernières ou si il est plus judicieux de modifier les types utilisés.

Semaine 9

  • Réalisation du shield :

Modifications sur les cartes compte tenue des remarques des encadrants. Principalement :

- Connexion des AO1/AO1, BO1/BO1, AO2/AO2, BO2/BO2 du contrôleur moteur afin de limiter le courant dans chacune des broches du circuit

- Ajout d'un plan de masse sur chacune des cartes (améliorant le routage)

- Déplacement/rotation de certains composants

- Élargissement des pistes


  • Établissement de la communication entre la calculatrice et le robot :

La communication entre la calculatrice et le robot se fera par échange de string. Une trame se composera comme suit :

Information

Char de début de trame Commande Paramètres Checksum Char de fin de trame

Nombre de Char

1 2 3 3 1

Cette trame a plutôt été pensé pour l'envoi de commande de la calculatrice vers l'Arduino (implémentée cette semaine), nous nous préoccuperons de la réponse lors de la semaine 10. Le champ paramètre n'est là qu'au cas ou et sera peut-être amené à disparaître pour l'envoi de commande. Pour la réponse, nous pourrions préciser le type de capteur à la place dans le champs Commande et la valeur du capteur en question dans le champs Paramètre ou alors constituer une trame contenant la valeur de tous les capteurs (à réfléchir).


Une commande sera donc représentée par deux Char. La liste des commandes avec l'expression en Char est la suivante :

- LED1 : "01" (Allumer la LED1)

- LED2 : "02" (Allumer la LED2)

- MOVE_FORWARD : "10" (Avancer)

- MOVE_BACKWARD : "11" (Reculer)

- MOVE_RIGHT : "12" (Tourner à droite)

- MOVE_LEFT : "13" (Tourner à gauche)

- STOP : "05" (Arrêter le robot)

- GET_DISTANCE : "06" (Demander la valeur indiquée par le capteur de distance)

- GET_LINEFOLLOWER : "07" (Demander les valeurs indiquées par les capteurs optiques)

- GET_TEMP : "08" (Demander la valeur indiquée par le capteur de température)


  • Développement de l'API  :

La calculatrice permet le développement Python grâce à MicroPython qui est une version de Python pour l'embarqué. Le développement d'une API se fait directement dans le code source MicroPython en C/C++. En plus d'écrire l'API dans un fichier .c ou .cpp comme on le fait traditionnellement, il faut modifier différents fichiers afin que MicroPython puisse reconnaître l'API et les différentes fonctions implémentées ainsi que permettre l'import du module développé.


J'ai choisi de nommer l'API "robot". Il faut écrire un fichier .c définissant la structure du nouveau module :

  STATIC MP_DEFINE_CONST_FUN_OBJ_0(led1_obj, led1);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(led2_obj, led2);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(moveForward_obj, moveForward);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(moveBackward_obj, moveBackward);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(moveLeft_obj, moveLeft);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(moveRight_obj, moveRight);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(stop_obj, stop);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(getDistanceSensorValue_obj, getDistanceSensorValue);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(getTemperatureSensorValue_obj, getTemperatureSensorValue);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(getLineFollowerSensor1Value_obj, getLineFollowerSensor1Value);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(getLineFollowerSensor2Value_obj, getLineFollowerSensor2Value);
  STATIC MP_DEFINE_CONST_FUN_OBJ_0(getLineFollowerSensor3Value_obj, getLineFollowerSensor3Value);
  STATIC const mp_rom_map_elem_t robot_module_globals_table[] = {
    { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_robot) },
    { MP_ROM_QSTR(MP_QSTR_led1), (mp_obj_t)&led1_obj },
    { MP_ROM_QSTR(MP_QSTR_led2), (mp_obj_t)&led2_obj },
    { MP_ROM_QSTR(MP_QSTR_moveForward), (mp_obj_t)&moveForward_obj },
    { MP_ROM_QSTR(MP_QSTR_moveBackward), (mp_obj_t)&moveBackward_obj },
    { MP_ROM_QSTR(MP_QSTR_moveLeft), (mp_obj_t)&moveLeft_obj },
    { MP_ROM_QSTR(MP_QSTR_moveRight), (mp_obj_t)&moveRight_obj },
    { MP_ROM_QSTR(MP_QSTR_stop), (mp_obj_t)&stop_obj },
    { MP_ROM_QSTR(MP_QSTR_getDistanceSensorValue), (mp_obj_t)&getDistanceSensorValue_obj },
    { MP_ROM_QSTR(MP_QSTR_getTemperatureSensorValue), (mp_obj_t)&getTemperatureSensorValue_obj },
    { MP_ROM_QSTR(MP_QSTR_getLineFollowerSensor1Value), (mp_obj_t)&getLineFollowerSensor1Value_obj },
    { MP_ROM_QSTR(MP_QSTR_getLineFollowerSensor2Value), (mp_obj_t)&getLineFollowerSensor2Value_obj },
    { MP_ROM_QSTR(MP_QSTR_getLineFollowerSensor3Value), (mp_obj_t)&getLineFollowerSensor3Value_obj }
  };
  STATIC MP_DEFINE_CONST_DICT(robot_module_globals, robot_module_globals_table);
  const mp_obj_module_t robot_module = {
      .base = { &mp_type_module },
      .globals = (mp_obj_dict_t*)&robot_module_globals,
  };


Ce fichier permet de définir le module MicroPython à travers une structure de type mp_obj_module_t. On définit d'abord les différentes fonctions du module sous forme d'objet. On remplit ensuite un dictionnaire avec les différentes fonctions implémentées. Finalement, on initialise les différents champs de la structure qui représentera l'API en lui précisant notamment le dictionnaire contenant les objets de fonction créés.

Afin de permettre l'import de l'API dans un futur code d'utilisation Python, on modifie le fichier mpconfigport.h en y ajoutant la ligne suivante dans la définition des MICROPY_PORT_BUILTIN_MODULES :

  { MP_ROM_QSTR(MP_QSTR_robot), MP_ROM_PTR(&robot_module) }

Afin d'ajouter la reconnaissance par MicroPython des fonctions implémentées dans notre API et de son nom, il faut modifier le fichier qstrdefsport.h. Dans notre cas, on y ajoute les lignes suivantes :

  Q(robot)
  Q(led1)
  Q(led2)
  Q(moveForward)
  Q(moveBackward)
  Q(moveLeft)
  Q(moveRight)
  Q(stop)
  Q(getDistanceSensorValue)
  Q(getTemperatureSensorValue)
  Q(getLineFollowerSensor1Value)
  Q(getLineFollowerSensor2Value)
  Q(getLineFollowerSensor3Value)

MicroPython sera alors capable de reconnaître par exemple que led1 correspond à une fonction définie.

Finalement, on implémente l'API dans un fichier .cpp ou .c comme on le fait traditionnellement.

Cette semaine, j'ai écrit une fonction pour le calcul du checksum qui réalise la somme de tous les chars de la trame. Le checksum sera représenté par 3 chars.

J'ai ensuite écrit une fonction de formation de la trame à envoyer. Cette dernière prend en paramètre la string de deux chars représentant la commande à réaliser ainsi qu'une string de 3 chars représentant les paramètres de commande à associer. Elle réalise alors une string en plaçant en première position un 'S' indiquant le début de trame, en plaçant ensuite la commande, les paramètres, le checksum en faisant appel à la fonction de calcul de checksum puis termine par le caractère de fin de trame 'E'.

J'ai finalement défini les fonctions de commande led1(), led2(), moveForward(), moveBackward(), moveRight(), moveLeft() et stop() qui font appel à la fonction de construction de trame en précisant la commande associée et écrivent sur le port série grâce à la fonction writeLine(mp_objet_t objet) implémentée par Zardam.

Ainsi, pour demander au robot d'avancer, la string envoyée sera la suivante : S13000417E


Lors de l'écriture des différentes fonctions, j'ai été confronté à un des principaux enjeux de l'embarqué : l'optimisation des ressources. La calculatrice présentant un stockage disponible assez faible, les fonctions des librairies standards du langage C non utilisées ont été supprimé afin de d'occuper le moins d'espace possible. Par exemple, la fonction strcat de la librairie <string.h> n'est pas disponible, j'ai alors dû trouver des alternatives.

Semaine 10

  • Réalisation du shield :

Les cartes n'ont pas pu être gravées cette semaine. J'ai eu un retour de Monsieur Flamen vendredi qui m'a indiqué qu'il était impossible de les graver du fait d'une isolation trop faible des pistes par rapport au plan de masse. Si elles avaient été gravé, il y aurait eu des courts circuits partout. J'ai alors modifié ces dernières et j'ai fixé l'isolation des pistes par rapport au plan de masse à 12 mil qui est le minimum à respecter. J'ai été confronté à un problème suite à cette modification sur la carte du shield. Avec une isolation de 12 mil, le plan de masse ne peut pas recouvrir toute la carte et certaines zones de masse sont des zones "orphelines" c'est à dire, non reliées au reste du plan de masse. Ne voyant pas d'autre solution et avec les conseils de Monsieur Flamen, j'ai ajouté des vias pour réaliser la connexion entre les différentes zones de masse. Une fois la carte gravée, ces zones seront alors connectées par un fil sur la face top.


  • Conception du robot :

Ayant avancé sur la partie programmation de la calculatrice et de l'Arduino, il me fallait réaliser le câble de connexion pour relier les deux afin de pouvoir réaliser des tests et voir si la programmation réalisée est fonctionnelle ou non. Utilisant une communication série, il me fallait un câble qui puisse être branché d'un côté sur le port micro-USB de la Numworks et de l'autre, sur le port série de l'Arduino. Ce type de câble n'existant pas à ma connaissance, j'ai pris un câble USB - micro-USB dont j'ai coupé la partie USB puis j'ai extrait les quatre fils (GND, VCC, Data+ et Data-) en dénudant.


Fils d'un câble USB


J'ai ensuite soudé un fil de connexion mâle-mâle sur chacun des fils afin de pouvoir les connecter à l'Arduino plus aisément. Enfin, j'ai ajouté de la gaine thermorétractable afin d'avoir un rendu plus propre et plus solide. Je peux désormais établir une connexion série entre la Numworks et l'Arduino avec ce câble en connectant :

- Le fil vert D- sur Tx

- Le fil blanc D+ sur Rx

- Le fil noir GND sur GND

Le fil rouge Vcc ne sera pas utilisé car la calculatrice ne nécessite pas d'alimentation.


  • Établissement de la communication entre la calculatrice et le robot :

Ayant écrit les fonctions de l'API Numworks permettant de contrôler les leds et les moteurs la semaine dernière, je me suis occupé cette semaine de la partie Arduino afin de pouvoir commencer à réaliser des tests et voir si l'on arrive bien à communiquer entre l'Arduino et la calculatrice.

- Setup() : Indication Output ou Input pour chaque pin selon le schematic du shield. Baudrate à 115200 car c'est celui utilisé par la Numworks.

- Réalisation d'une fonction de lecture de trame reçue. Tant que l'on reçoit des caractères et que l'on a pas lu le caractère de fin de trame 'E', on les stocke dans un tableau de Char.

- Écriture d'une fonction de découpage de la trame. Afin de faciliter son implémentation, j'ai modifié le format des trames en ajoutant un caractère de séparation '-' entre chaque type de donnée. Les trames sont désormais de la forme : S-01-000-573-E. On utilise alors la fonction strtok pour stocker la commande reçue (dans la trame précedente : "01"), les paramètres ("000") et le checksum ("573") en ayant pris soin de vérifier que le premier caractère de la trame est bien 'S'.

- Écriture d'une fonction de vérification du checksum. On renvoie True ou False selon que la somme des caractères de la trame (sans le checksum) est égale au checksum de la trame ou non

- Écriture d'une fonction de traitement de la commande reçue. Si le checksum de la trame est correct, on exécute la commande associée à l'ordre reçu.

- Écriture de la fonction principale loop(). On lit la trame, on la découpe et on exécute la commande demandée.


Une fois le programme Arduino écrit, j'ai ajouté un script de test dans la Numworks pour ne pas avoir à le réécrire à chaque fois. Pour cela, il y a plusieurs fichiers de l'OS epsilon à modifier.

On définit le script dans le fichier epsilon/apps/code/script_template.cpp :

  const ScriptTemplate * ScriptTemplate::TestRobot() {
   return &testRobotScriptTemplate;
  }
  constexpr ScriptTemplate testRobotScriptTemplate("test_robot.py", R"(import robot
  def test1():
   robot.led1()");

Dans ce script de test, on exécute la fonction led1() qui envoie à l'Arduino une demande d'allumage de la LED déjà présente sur l'Arduino (Pin 13), en utilisant la nouvelle API 'robot'.

On ajoute alors un attribut à la classe ScriptTemplate du fichier epsilon/apps/code/script_template.h :

  static const ScriptTemplate * TestRobot();

Enfin, on rend accessible le script grâce à la fonction addScriptFromTemplate dans le fichier epsilon/apps/code/script_store.cpp

  addScriptFromTemplate(ScriptTemplate::TestRobot());

Le script de test est désormais disponible dans l'application Python de la Numworks sous le nom : test_robot.py. Il n'y a plus qu'a exécuter le script et appeler la fonction définie dans ce dernier.


Après cela, j'ai pu tester la communication entre la Numworks et l'Arduino. Dans un premier temps, l’exécution du script sur la calculatrice provoquait un arrêt de celle-ci qu'elle soit connectée ou non à l'Arduino. La seule solution pour récupérer le contrôle de la calculatrice était alors le bouton reset. J'ai alors pensé à un problème mémoire. Utilisant des mallocs dans l'implémentation de l'API, j'ai vérifié s'il n'y avait pas de fuite mémoire grâce à Valgrind. Ce n'était pas le cas, l'utilitaire n'indiquait aucun problème. Finalement, le problème venait d'une mauvaise utilisation des types MicroPython lors de l'écriture sur le port série grâce à la fonction WriteLine(mp_objet_t objet). Après avoir corrigé ce problème, j'ai pu vérifier le bon fonctionnement de la communication. Lors de l'exécution du script, la LED de l'Arduino s'allume bien.

Semaine 11

Semaine 12

Semaine 13

Documents Rendus