Aller au contenu


Harry

Inscrit(e) (le) 16 févr. 2008
Déconnecté Dernière activité déc. 10 2013 02:52
-----

Réseaux de neurones & algorithme génétique d'apprentissage

Publié Harry le 24 mai 2011 - 08:26 in Intelligence Artificielle

Après de longs mois d'inactivité, je suis de retour avec ce petit tutoriel qui vous donnera - je l'espère - le goût du développement d'intelligence(s) artificielle(s). L'objectif de ce tutoriel est de vous expliquer le fonctionnement d'un "cerveau" pour que vous puissiez en développer un pour votre robot, de telle sorte qu'il apprenne de lui-même à éviter les obstacles... Vous avez bien lu : le robot que je vous propose d'étudier pourra éviter les obstacles après avoir appris à le faire, tout seul !

Bref, passons directement dans le vif du sujet !

Sommaire :

1. Constitution du robot
2. Établissement manuel du réseau de neurone
3. Algorithme génétique d'apprentissage

Constitution du robot

Partie "hardware"

Voici les principaux composants de notre robot
 
 
  •  
  • en vert : 2 moteurs à courant continu
  • en jaune, orange et rouge : 6 capteurs de distance
  • non représenté : un micro-rupteur (= interrupteur) qui se déclenche si le bord du robot touche un obstacle.

Image IPB

Je ne détaille pas l'alimentation, l'électronique employée, etc... l'objectif de ce tutoriel étant de comprendre le fonctionnement des réseaux de neurones : ce tutoriel sera donc applicable à n'importe quel robot ayant cette configuration et pouvant être programmé à vos souhaits.

Partie "software" : le réseau de neurone proprement dit

Un réseau de neurone relie les entrées d'un système à ses sorties.

Image IPBLes entrées et les sorties sont appelés des nœuds. Le "lien" qui relie deux nœuds est appelé un axiome. Ceci n'est donné qu'à titre informatif, il n'est pas indispensable de retenir ce vocabulaire pour comprendre ce tutoriel.

Voici un schéma détaillant la notation que l'on va utiliser pour repérer les capteurs et les moteurs :

Image IPB

J'en ai profité pour dessiner notre premier réseau de neurone. Pour simplifier la compréhension, on ne va s'intéresser qu'au moteur gauche de notre robot.

On distingue clairement sur ce schéma - grâce aux traits en gras - que toutes les entrées (les Ci) sont reliées au moteur (M1). Sur chaque lien, on peut voir d'autres notations : P11, P21, P31, P41, P51 et P61 (ou encore les Pik - P indice i indice k - avec i allant de 1 à 6, et k valant 1 puisqu'on n'étudie que le moteur gauche) C'est ce qu'on appelle les poids. Grâce à ces notations, voici l'équation du réseau de neurone que l'on vient de créer - pour le moteur gauche uniquement :

Image IPB

Les valeurs des capteurs sont données par les capteurs eux-même, en fonction de la distance qui les séparent d'un obstacle extérieur. Les valeurs des moteurs sont contrôlées par le logiciel qui est sur votre robot... ici, il sera contrôlé grâce au réseau de neurone, dont on a écrit l'équation juste au-dessus. Il nous manque donc plus qu'à connaître les valeurs des poids (Pik)... c'est justement ces poids que le robot doit apprendre à déterminer, de lui-même ! Pour comprendre comment lui faire faire cela, nous allons d'abord essayer de voir quelles valeurs pourraient fonctionner pour nos Pik, pour que notre robot puisse éviter les obstacles...

Établissement manuel du réseau de neurone

Comportement désiré

Pour que tout le monde suive, on va mettre à plat le comportement du robot. Le robot doit toujours avancer, sauf dans les cas suivant :

  •  
  • si un obstacle se présente sur la gauche, le robot doit tourner à droite (c'est à dire que le moteur gauche doit "avancer" - tandis que le moteur droit doit "reculer")
  • si un obstacle se présente sur la droite, le robot doit tourner à gauche (c'est à dire que le moteur gauche doit "reculer" - tandis que le moteur droit doit "avancer")
Et si un obstacle se présente au centre ? Ceci est un faux problème car les capteurs ne donneront jamais constamment la même valeur à droite et à gauche : on rentrera donc systématiquement dans un des deux cas précédents. Si vraiment cela devait arriver, je suis sûr que vous saurez palier à ce problème une fois que vous aurez compris comment faire un réseau de neurone. Mais pour l'instant, on va simplifier les choses : aucun obstacle ne se présentera pic poil au milieu, et le robot ne PEUT PAS se planter (admettez-le, ou remettez ce problème à plus tard... ne restez pas bloqué à cause de ce cas).

Nous allons donc traiter ces deux cas, l'un après l'autre, ce qui nous permettra de donner des valeurs fonctionnelles pour P11, P21 et P31 d'une part, puis P41, P51 et P61 d'autre part.

Si un obstacle se présente sur la gauche...

... dans ce cas, les capteurs de gauche, C1, C2 et C3 auront une valeur importante (les capteurs de distance renvoie une valeur d'autant plus importante que l'obstacle est proche). Or, on voulait que si un obstacle se présente à gauche, le moteur gauche devait avancer... pour cela, on doit lui envoyer une valeur positive. Je rappelle notre équation du RdN (réseau de neurone) :

Image IPB

Vu ce que l'on cherche à faire, le plus simple pour répondre à cette demande serait - en premier lieu - d'imposer P11 = 1, P21 = 1 et P31 = 1 (ce sont des valeurs possibles). Notre équation devient donc :

Image IPB

Image IPBOui, mais attends, tu en fais quoi des valeurs des 3 autres poids ?

Je ne les ai pas oublié, soyez patients !

Si un obstacle se présente sur la droite...

... alors là, à l'inverse, ce sont les capteurs C4, C5 et C6 qui auront une valeur importante. Or, on voulait que si un obstacle se présente à droite, notre robot doit tourner à gauche, et pour cela, le moteur gauche doit "reculer"... pour cela, il faut lui envoyer une valeur négative. Vous l'aurez compris, on va imposer P41 = -1, P51 = -1 et P61 = -1. Ainsi :

Image IPB

Pour ceux qui auraient des doutes...

Image IPBAttends c'est n'importe quoi ce que tu fais là ! Tu détermines les valeurs de la moitié des poids pour un cas, et l'autre moitié avec un autre cas ! Ca ne pourra jamais marcher... !!!

Je conviens que la méthode employée n'est pas très catholique : cependant, cela fonctionne ! Vérifiez par vous-même... si un obstacle arrive à gauche, C1, C2 et C3 auront une valeur importante, et C4, C5 et C6 auront une valeur faible... d'autant plus faible que l'objet est loin, voire nulle si l'objet est à l'infini... on peut donc considérer que dans le cas où un obstacle arrive sur la gauche, seules les valeurs de C1, C2 et C3 sont pris en compte... et regardez l'équation dans ce cas là :

Image IPB !!! notre moteur gauche va donc "avancer" !

Et ça marche aussi si un obstacle arrive à droite : C1, C2 et C3 sont faibles cette fois-ci, voire négligeable... alors que C4, C5 et C6 auront une valeur importante, et dans ce cas, l'équation devient :

Image IPB !!! notre moteur gauche va donc "reculer" !

Et le moteur droit alors ?

Cela pourrait faire un très bon exercice... je vous laisse chercher (il suffit de suivre exactement le même schéma que précédemment), mais voici la réponse :

Image IPB

Algorithme génétique d'apprentissage

Introduction

Nous avons créé un réseau de neurone, et nous avons trouvé des valeurs possibles pour les poids de ce réseau. Je vous avais pourtant dit au début que votre robot allait apprendre à le faire de lui-même ! Vous aurais-je menti ? Non, mais je voulais que vous ayez bien compris la constitution d'un réseau de neurone et son fonctionnement. Maintenant que cela est fait, nous allons imaginer que nous ne connaissons pas les valeurs de ces poids, et nous allons faire faire ce dur labeur à notre robot.

Image IPBAttends, c'est complètement idiot de faire ça ! On connaît des valeurs qui marchent, pourquoi ne pas lui donner directement les valeurs et tester ?

Vous avez raison : vous pouvez tester ces valeurs sur votre propre robot. Et ça marchera. Très bien même. Et puis vous aurez deux possibilités :
 
  •  
  • vous êtes content de ce que vous avez appris et cela vous suffit : dans ce cas, merci d'avoir lu ce tutoriel et au revoir ! Bonne journée !
  • vous avez envie d'en apprendre plus, en particulier sur l'algorithme génétique d'apprentissage, et vous avez même envie que l'apprentissage se fasse le plus simplement possible : dans ce cas, je vous invite à lire la suite
Et pour ceux qui trouvent que c'est dommage de rester sur le même exemple - déjà résolu qui plus est - pour apprendre le fonctionnement de l'algorithme génétique d'apprentissage, je répondrai ceci : c'est un choix que j'ai fait en toute connaissance de cause. En effet, si on reste sur un exemple déjà bien connu, les termes que je vais employer auront un aspect "concret", tandis que si je pars sur un autre exemple, ce sera un peu plus subjectifs, et certains risquent de ne pas suivre.

Je vous invite donc à rentrer dans le vif du sujet de cette grosse bête qui paraît si méchante... et qui ne l'est pas du tout en fait o_O

Algorithme d'apprentissage simple

Le principe de l'apprentissage simple est le suivant : on choisit des valeurs aléatoires pour les poids, puis le robot le teste de lui-même. Pour qu'il puisse les tester, on va lui dire d'utiliser un capteur qu'on n'a pas encore utilisé, mais que j'ai indiqué au tout départ, souvenez vous :
Citation de : Harry
Citation

Voici les principaux composants de notre robot
  •  
  • en vert : 2 moteurs à courant continu
  • en jaune, orange et rouge : 6 capteurs de distance
  • non représenté : un micro-rupteur (= interrupteur) qui se déclenche si le bord du robot touche un obstacle.
Nous allons donc demander à notre robot d'utiliser ce micro-rupteur : si celui-ci se déclenche, cela voulait donc dire que les valeurs de nos poids (choisis aléatoirement) étaient "mauvais". Il faut donc faire un autre "jet" (c'est à dire qu'on va choisir d'autres valeurs aléatoires pour les poids). Et ainsi de suite... jusqu'à ce que le robot évite tous les obstacles !

Image IPBAttends... mais ça peut prendre un temps fou ça !

Oui, mais observez un bébé qui apprend à attraper un verre : un coup, sa main passe au dessus, le coup d'après sa main se cogne contre la table ("ouille" :mort: "ouinnnn" Image IPB ), le coup d'après il envoie le verre sur la lune (là, c'est le papa qui n'est pas content :colere: -> fessée)... puis après en avoir cassé plusieurs, il arrive enfin à l'attraper ! Tout cela prend du temps ! Si vous voulez, vous pouvez créer un "simulateur de robot" qui pourra vous permettre de tester votre algorithme pour obtenir la valeur des poids plus rapidement ^^

Algorithme génétique d'apprentissage

On a failli oublié la partie "génétique" dans tout ça dites donc... meuh non, ne vous inquiétez pas ! Image IPB

Cependant, la partie "génétique" est tellement simple que j'ai honte de la détailler... faisons le tout de même. Dans la partie précédente, on a effectué une série de jets sur les poids.

Ce que l'on va faire maintenant, c'est d'enregistrer une dizaine de jet, et, pour chaque jet, lancer notre robot dans une arène pendant disons une dizaine de minute. Puis on regarde les résultats obtenus pour le nombre de fois où le robot s'est cogné pour chaque jet. La "sélection génétique" de cette "génération" va consister à sélectionner le jet qui aura obtenu le meilleur score, c'est à dire le jet pour lequel le robot s'est cogné le moins souvent.

Puis on passe à la génération suivante : cette génération est issu du jet "sélectionné". Je veux dire par là qu'au lieu de faire des choix aléatoires pour tous les poids à chaque nouveau jet, on va en conserver 1 (d'une manière aléatoire, mais on en conserve au moins 1 du jet "parent" pour chaque nouveau jet). Puis on re-fait nos tests, et on refait une sélection du meilleur.

A la génération suivante, on va conserver cette fois-ci 2 valeurs de poids...

A la génération suivante, on va en conserver 3...

et ainsi de suite...

Petit à petit, vous verrez que les enfants issus des "meilleures" sélections auront les mêmes comportements que ceux de leurs parents, mais améliorés (les parents ont transmis leur "savoir" à leurs enfants qui à leur tours font leur vie et enseignent ce qu'ils ont appris à leurs enfants).

Je trouve ce concept passionnant et pourtant d'une facilité déconcertante une fois qu'on a compris de quoi il s'agissait, n'est-ce pas ?

N'hésitez pas à poser vos questions sur le forum !


Programmation d'un pic sous MicroC

Publié Harry le 13 octobre 2009 - 07:17 in Programmation

Introduction


Bonjour,

Ce cours a pour but de vous apprendre à programmer ce qu'on appelle un microcontrolleur :heu: ... Non ! Ne partez pas ! Je vous assure que c'est utile et accessible à tous ! Mais ce n'est pas simple, je le conçois ... Je vais faire en sorte que ça passe en douceur.

Image IPBCe cours nécessite de savoir programmer en C...

 

Une présentation des pics et de l'environnement de développement


Je vais commencer par décrire ce qu'est un pic : c'est un micro contrôleur qui intègre un processeur. Voilà, c'est fait et personne n'a rien compris. Donc on va décrire plus simplement... en disant à quoi ça ressemble et à quoi ça peut servir.

A quoi ça ressemble un pic ?

Un pic est un parallélépipède noir avec des "pattes" métalliques :

Image IPB

Mais, à quoi ça sert ce truc ?!?

Ce type de pic sert à gérer à peu près tout et n'importe quoi en robotique, grâce au processeur qui est intégré dedans : votre PC est rempli de ces processeurs et les pics les plus polyvalents actuellement (Février 2008) sont les pics dont la référence commence par 18F*****. En gros, grâce à ça, vous allez pouvoir gérer les contacteurs, les capteurs de distances, les moteurs, vos lumières... bref tout ce qui fait joli dans un robot Image IPB

Où puis-je m'en procurer ?


Vous pouvez vous en procurer à deux endroits :
- Tout magasin d'électronique qui se respecte devrait pouvoir vous en vendre certaines catégories pour environ une quinzaine d'euros chaque...
- Sinon, si vous avez un mail "étudiant" (j'entends par là, autre que Yahoo et Hotmail...), vous pouvez recevoir 2 échantillons gratuit par mois sur le site de microchip.


Image IPBBon, maintenant que j'ai mon pic, ... je fais comment pour le programmer ?

Image IPBCette question a une réponse simple : il vous faut un programmateur de pic qui permet de relier le pic au PC, et c'est ce qui est expliqué juste après.

La carte de développement

Le programmateur de pic - ou carte de dév (développement) - est un élément essentiel pour "FLASHER" votre pic : en gros cela consiste à charger votre programme (compilé grâce à un logiciel dont vous aurez tous les secrets plus tards Image IPB ) sur votre pic, et ce, grâce à un câble qui relie le PC au pic.

Différents programmateur

Il existe différentes carte de dév... certaines que vous pouvez faire vous-même, d'autres que vous pouvez acheter à divers endroits (en particulier sur le net ?

Où puis-je me procurer un schéma si je veux le faire moi-même ?

Voici un site qui donne un schéma d'un programmateur, mais je ne sais pas du tout ce qu'il vaut...

Où puis-je en acheter un tout prêt plutôt que de m'embêter à le faire moi-même ?

Pour ça, je vous conseille de préparer votre porte-feuille... certains ne coûtent effectivement pas cher, mais ils n'ont pas les del rouges, très utiles, surtout au début, pour voir en "live" si votre programme fonctionne, avec des boutons poussoirs qui permettent de tester la réactivité de vos programmes...

Voici le mien :

Image IPB

J'utilise personnellement l'EasyPic5 de MikroElectronika (hé oui, c'est en anglais... mais ce dont vous avez besoin se trouve dans la section "Developpement Tool" en haut) et après, il faut que votre programmateur de pic puisse utiliser les pics que vous aurez commandés chez microchip... l'EasyPic5 a l'avantage de justement pouvoir programmer tous les PIN-40 (c'est-à-dire tous les pic ayant 40 pattes : les 16F, et les 18F entre autres), ainsi que tous les autres pics de forme rectangulaire.

Sinon vous pourrez aussi en trouver sur eBay mais bon, là aussi, il faut faire attention à ce que vous faites...

A vous de choisir la solution qui vous convient. Sachez que les pics de forme rectangulaire sont plus faciles à manipuler que ceux à forme carré (avis personnel).

Euh, ça coûte trop cher je trouve... je peux essayer de juste programmer, sans le tester sur mes pics ?

Oui, bien sûr ! Un simulateur est intégré dans le logiciel que je vais détailler... mais il reste très rudimentaire ! Mais au moins, vous pourrez déjà savoir si la programmation d'un pic peut vous plaire ou non...

Lisez la notice d'utilisation de votre programmateur !

Une fois que vous aurez votre programmateur en main, n'hésitez pas à lire sa documentation, surtout pour le branchement et la mise en marche !

On a le pic, on a (peut-être...) la carte de développement, il nous manque une dernière chose : le logiciel qui va nous permettre de programmer notre pic !

Le logiciel de développement

Alors là, vous avez encore plusieurs possibilités qui s'offrent à vous... Mais bon, je vous conseille d'utiliser le même logiciel que moi si vous voulez suivre ce cours, c'est-à-dire la première des solutions que je vous propose.

La solution MicroC + PicFlash

Si vous avez commandé chez MikroElectronika, normalement un CD vous est fourni. Alors installez les logiciel PicFlash (qui vous permettra de flasher votre pic à partir du PC) et MicroC (qui vous permettra de programmer C... vous avez aussi MicroBASIC, etc...). Ces deux logiciels se trouvent normalement sous le dossier "zip" de la racine du CD. Installez-les.

D'autres logiciels de programmation

Le compilateur SDCC

Vous pourrez le trouver ici

Le programmateur MpLab de microchip

Vous pourrez le trouver ici

D'autres logiciels pour flasher votre pic

Logiciel de programmation des composants IC-Prog

Vous pourrez le trouver ici

J'utilise personnellement le logiciel MicroC avec PICFlash... Toute la suite est basée sur ce logiciel, mais si vous n'avez pas ce logiciel, vous pouvez toujours lire la suite pour apprendre à lire la datasheet, qui sera votre outil indispensable dans le futur pour programmer n'importe quel pic...

 

Première étape : préparer son espace de travail


L'espace de travail

Maintenant que vous avez tous les outils, il faut les préparer. Ceci implique donc 3 étapes.

Branchez votre pic sur la carte de développement

Normalement, la notice d'utilisation de votre carte de développement devrait vous indiquer comment faire. Voici à quoi ça ressemble chez moi :

Image IPB

Branchez votre carte de développement au pc

Là, de la même manière, la notice d'utilisation vous indique comment faire. Chez moi, il suffit de brancher la carte au pc via un câble USB qui permet également d'alimenter la carte en électricité.

Installez vos logiciels de programmation et de compilation

Là aussi, référez-vous à la documentation des logiciels que vous avez choisi (et pour MicroC et PICFlash, je l'ai déjà rapidement décrit).

Une fois que votre espace de travail "physique" est prêt, il faut préparer l'espace "virtuel"...

Documentation du pic

Toute programmation de pic nécessite absolument d'avoir la documentation du pic que l'on est en train de coder. Certes, c'est en anglais, donc rébarbatif, mais ne vous inquiétez pas, si vous suivez mes conseils, cela passera en "douceur".

Repérer la référence du pic que vous avez

Prenons ce pic :

Image IPB

La référence est écrite dessus (je sais, ce n'est pas un 18F... à vous de vous adapter ^^). Dans notre exemple, c'est un 16F877A

Trouver la datasheet

Une datasheet (littéralement « feuille de données ») est un fichier donnant toutes les caractéristiques d'un élément électronique. Il faut aller la chercher sur le site du constructeur.

Allez là et remplissez le champ en haut qui correspond à votre pic (pour moi, c'est donc le 16F877A) :

Image IPB

Image IPB

Cliquez sur la ligne correspondant à la "Sillicon Product", vous devriez obtenir :

Image IPB

Enfin cliquez sur l'icône "pdf" pour télécharger la Datasheet. Voici celle de mon pic, à la page la plus intéressante :

Image IPB

Vous remarquerez que j'ai regroupé les "RAi", "RBi", "RCi", "RDi" et "REi" sous des couleurs différentes. En réalité A, B, C, D et E sont les ports programmables. Les "i" varient (en gros) de 0 à 7 : vous aurez reconnu là la formation d'un octet: chaque RAi est un bit (un chiffre binaire, c'est à dire un 0 ou un 1) de l'octet correspondant au port A.

On est fin prêt pour créer notre premier projet !

Créer un projet

Je vais décrire comment créer un projet proprement sous MicroC... Et désolé pour ceux qui n'utilise pas ce logiciel :|

Fermez les projets qui seraient déjà ouverts

Avant tout, fermez tous les projets en cours (Project --> Close Project) :

Image IPB

Créez un projet sous MicroC

Puis créez votre premier projet (Project --> New Project) :

Image IPB

Ensuite, mettez le nom de votre programme, choisissez l'endroit où vous voudrez l'enregistrer (cliquez sur "choose"), sélectionnez votre PIC dans la liste, mettez le "clock" à 010.000000, et cliquez sur "Default" puis sur OK :

Image IPB

Oh le joli écran noir ! :tare: Non, sérieux, ne vous inquiétez pas, c'est normal ^^

Et voilà ! On peut ENFIN commencer à coder en tant que tel Image IPB




Deuxième étape : votre premier code


Les règles de base

Pour vous aider à faire votre premier code, je vais faire comme suit : d'abord je vais vous donner les règles à respecter, ensuite je vous laisserai vous exercer.

Les 5 commandements tu respecteras

Image IPBA partir de maintenant, voici les règles à respecter quoi qu'il en soit :
  • Les instructions en minuscules sont propres au C
  • Les instructions en MAJUSCULES sont propres aux micro contrôleurs
  • "0b" (zero Image IPB indique que la suite est écrite en binaire (0 ou 1)
  • un nombre écrit tel quel indique qu'il est écrit en décimal (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
  • "0x" (zero x) indique que la suite est écrite en héxadécimal (0, 1, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E et F... pas très utile)
Quelques instructions

A partir de là, j'ai décidé de travailler sur le port B. Voici les instructions de base à connaître et à utiliser à bon escient (le code suivant ne sert qu'à vous donner des définitions):

TRISB

L'attribution de TRISB permet de configurer le port B en entrée ou en sortie (ou à moitié en entrée et à moitié en sortie...)

TRISB = 0; //Le port B est configuré en mode sortie (le pic envoie des ordres à un système extérieur via ce port, par exemple à un moteur)

TRISB = 1; //Le port B est configuré en mode entrée (le pic reçoit des informations d'un système extérieur via ce port : capteur, autre pic, etc...)

Imaginons qu'on ait besoin que de 4 bits sur les 8 disponibles pour recevoir une information, et qu'on ait besoin que de 4 bits pour envoyer des données ; au lieu de «gâcher» 2 ports (1 en entrée, 1 en sortie), on peut utiliser la moitié du port B en entrée et l'autre moitié en sortie : il est alors plus lisible d'écrire la valeur en binaire :
TRISB = 0b11110000; //Les pattes 7, 6, 5 et 4 du port B sont configurées en mode entrée (0b11110000) et les pattes 3, 2, 1 et 0 sont configurées en mode sortie (0b11110000)

Rem : Au dessus, au lieu d'écrire TRISB = 0 on pouvait donc écrire TRISB = 0b00000000.

PORTB

Image IPBImaginons qu'on ait configuré le port B en sortie, comment affecter une valeur à ce port ? (valeur qui sera interprété par exemple par le moteur d'une roue : avancer, reculer, vitesse…)

Il faut attribuer une valeur à PORTB:
PORTB = 0b01111111; //La patte 7 du port B prend la valeur 0 (une DEL qui s'éteint...) alors que les pattes 6, 5, 4, 3, 2, 1 et 0 prennent la valeur 1 (des DEL qui s'allument...)

Bien sûr, si on écrit PORTB = 1; tous les bits du port B prennent la valeur 1; et si on écrit PORTB = 0; tous les bits du port B prennent la valeur 0.

Image IPBEt physiquement, qu'est-ce qui se passe ?

Physiquement, si un bit d'un port est sur 1, il envoie une tension seuil 5 Volts à la patte correspondante (voir alors la Datasheet) et s'il est sur 0, il envoie du 0 Volt ; logiquement vous avez branché un fil, imaginons que ce fil soit relié à un moteur, ce moteur modifiera son comportement selon qu'il reçoit du 5 Volts ou du 0 Volt.

Image IPBOui, d'accord, mais moi, j'ai configuré la moitié de mon port en entrée et l'autre moitié en sortie, et bien sûr il ne faut pas modifier les valeurs d'entrées, comment envoyer quelque chose en sortie proprement ?

Il faut alors configurer chaque bit de sortie un par un. Pour accéder à un bit précisément, il faut utiliser : PORTB.Fnumbit
Exemple :
PORTB.F0 = 0; //Attribue 0 au bit 0 du port B

Image IPBEt si mes ports sont en entrée ? Comment je fais pour savoir ce que j'ai reçu ?

Bien évidemment, si le port B est en entrée, on ne va pas attribuer des valeurs aux bits de ce port (risque de conflits), mais lire les valeurs qu'il a pris. On utilise alors des tests logiques.
Ex :
Code : C

 
if (PORTB.F0==1) //si je lis 1 sur le bit 0 du port B
{
PORTC.F0=0; // alors, j'envoie 0 sur le bit 1 du port C (par ex, je demande au moteur de freiner (très vulgarisé))
}
else //sinon, si je lis 0 sur le bit 0 du port B
{
PORTC.F0=1; // alors, j'envoie 0 sur le bit 1 du port C (par ex, je demande au moteur d'accélérer (très vulgarisé))
}
DELAY_MS

DELAY_MS(1000); //Fait une pause de 1000ms = 1s - ATTENTION A BIEN CRÉER LE PROJET COMME J'AI DIT (en particulier pour la valeur du timer à 100)

L'opérateur ~

PORTB = ~PORTB; //Inverse la valeur de chaque bit de PORTB (0 --> 1 et 1 --> 0) exemple : 01100011 --> 10011100 (le "~" se fait en faisant "Alt Gr + 2" puis "espace" sous azerty)

Ces codes peuvent être utilisés n'importe où dans le programme.

Certains conseils tu suivras

Maintenant, pour alléger votre code, vous pouvez utiliser des define, comme en C, à mettre avant le main :

#define sortie PORTB //A mettre avant le main... Il faudra mettre "sortie = 0;" dans le main pour avoir effectivement PORTB en sortie

Il y a quelque chose de remarquable avec MicroC... Vous pouvez non seulement créer des variables dans le main... mais aussi en dehors de n'importa laquelle de vos accolades !!! C'est pourquoi je vous conseille fortement de déclarer toutes vos variables en "globales", au début de vos programmes de pic.

Des exercices tu feras

Bon, c'est pas tout ça, mais vous ne bossez pas beaucoup... Allez, à vous de coder ! Un sujet ? Ok, pas de problème, voici une question à laquelle vous allez répondre :

Image IPBComment faire clignoter toutes les DEL du plateau alors ?

Image IPBRéponse possible :

Code : C
 
#define sortie TRISB
void main (void)// N'OUBLIEZ PAS LE SECOND "VOID" !!!
{
sortie = 0;
PORTB = 0;
while(1)
{
delay_ms(1000);
PORTB = ~PORTB;
}
}
Image IPBMais on fait comment pour que les dels clignotent ? Parce que le programme il est joli, mais je veux que les leds clignotent, moi !!

Image IPBAlors, déjà, il faut que vous ayez connecté votre pic à votre pc, qu'il soit allumé (moi j'ai un bouton on/off) et que votre pc le reconnaisse. Ensuite, une fois votre programme écrit, faites "Ctrl + F11" pour compiler ET flasher le pic. Si vous ne voulez que le compiler, faites "Ctrl + F5" et juste pour le flasher, faites "F11".

Sinon, vous pouvez toujours simuler le projet, et c'est le thème de la partie suivante.

Le simulateur

Avant de tester directement votre programme sur le pic, vous pouvez aussi le simuler sur votre PC... et c'est d'ailleurs la seule méthode qu'ont ceux qui n'ont pas de kit de développement pour vérifier si leur programme pourrait marcher...

Le simulateur de MicroC

Pour utiliser ce simulateur, sélectionnez d'abord votre simulateur. Ici allez dans "Debugger --> Select Debugger --> Software Pic Simulator" :

Image IPB

Puis cliquez sur "Run --> Start Debugger". Vous arrivez sur cette page :

Image IPB

Vous avez plusieurs boutons. Dans l'ordre :
Image IPBStart Debugger (F9) : permet de placer le curseur au début de votre programme
Image IPB Run/Pause Debugger (F6) : permet de remettre en marche ou de mettre en pause votre programme
Image IPB Stop Debugger (Ctrl + F2) : permet d'arrêter votre programme
Image IPBStep Over (F8) : permet de passer à l'étape suivante de votre programme. Ce bouton est essentiel pour voir évoluer votre programme (mais d'abord, il faut rajouter vos variables, ce que nous allons faire avec un autre bouton plus loin)
Image IPB Step Out (Ctrl + F8) : permet d'exécuter votre programme en entier
Image IPB Run To Cursor (F4) : permet d'exécuter votre programme jusqu'à l'endroit où vous aviez placé le curseur
Image IPB Step Into (F7) : euh... à tester, je ne sais pas
Image IPB Jump To High Priority Interrupt (F2) : permet d'aller à votre interruption principale (j'expliquerai plus tard ce que sont les interruptions)

Puis, les boutons qui vous permettent de placer et d'éditer vos variables dans la fenêtre d'état du dessous :
Image IPB Permet d'ajouter une variable particulière dans la liste de variables à "simuler", après l'avoir sélectionnée dans le menu déroulant juste en dessous
Image IPB Permet de supprimer une variable de simulation particulière que vous avez sélectionné dans la liste
Image IPB Permet de rentrer dans les propriétés d'une variable particulière que vous avez sélectionné dans la liste pour en changer sa valeur en temps réel par exemple (vous pouvez aussi double-cliquer sur la variable pour avoir ces propriétés aussi)
Image IPBPermet d'ajouter toutes les variables dans la liste de simulation
Image IPB Permet de supprimer toutes les variables de la liste de simulation

Pour votre première simulation (l'exemple écrit dans le premier paragraphe), je vous conseille pour ne pas vous embêter de cliquer sur Add All, puis d'appuyer une seule fois sur "F9" (ou cliquez sur Start Debugger). Puis pour voir votre programme avancer, appuyer sur "F8" (ou cliquez sur "Step Over") à plusieurs reprises. Vous allez ainsi pouvoir voir votre curseur se déplacer dans votre fenêtre de code, et voir l'état des variables changer.

<1> Au départ (F9), votre curseur se place sur "sortie = 0;". (je vous rappelle que sortie est une variable se référant au TRISB et que la valeur qui lui est affectée - 0 - permet de configurer tout le port B en sortie).

Image IPB


Pour pouvoir exécuter la ligne sélectionnée, appuyez une fois sur (F8). La valeur n'est donc pas modifié puisque de base les pics sont configurés en sortie donc la ligne correspondant à TRISB reste à 0 (dans la colonne "Value").


<2> Ensuite, votre curseur est placé sur "PORTB = 0;".

Image IPB

De même, appuyez sur (F8) pour exécuter cette ligne et la valeur du port B ne change pas puisque de base il vaut 0.

<3> Ensuite, votre curseur est placé sur "delay_ms(1000);". Remarquez en bas au milieu de la fenêtre que la valeur du temps qui s'est écoulé est : "Time = 2.00 Us" (deux micro-secondes).

Image IPB

Ainsi, en appuyant sur (F8), vous exécutez cette ligne et du coup on obtient : "Timer = 1003.39 Us"... Mais d'où sort ce 1.39 Us de trop ?!? Rhhaa là-là, sachez que le pic prend du temps pour traiter les ordres que vous lui donnez, et ça, microC les simule aussi ^^

<4>Donc votre curseur est maintenant sur la ligne : "PORTB = ~PORTB;" :

Image IPB

Appuyez maintenant sur (F8) : la valeur de PORTB passe à 255 d'un seul coup (!!!) :

Image IPB

Image IPBMais pourquoi ça fait ça ?

Image IPBJe vous rappelle que le tild "~" permet de changer la valeur de TOUTES les pattes du pic. Donc tous les 0 passent à 1 et réciproquement. Ici PORTB=0 au départ. Donc on a bien zéro. Mais les valeurs affichées sont en décimal, et non en binaire ! Donc ça n'affiche pas 11111111, mais sa traduction en décimal, c'est à dire... 255 ! Mais si vous voulez avoir sa valeur en binaire, double cliquez sur la variable et sélectionnez "décimal" ^^

Voilà, maintenant, vous avez compris la simulation d'un projet ! Vous n'avez plus qu'à appliquer cette méthode quand vous voulez !

C'est intéressant tout ça, mais... je ne suis pas sûr que vous compreniez ce que vous faites... d'ailleurs si vous n'avez pas utilisé exactement les mêmes données que moi, ça NE MARCHE PAS, et j'ai envie de dire que... c'est normal. Mais ne vous inquiétez pas ! Il suffit juste d'adapter votre code à VOTRE pic. Mais pour cela, il faut que vous arrivez jusqu'à la partie "La structure d'une datasheet" en me faisant confiance jusque là... Pour l'instant, je vais juste vous prouver que tous les codes que vous avez utilisé se trouvent dans la datasheet, et c'est l'objet de la prochaine partie.

Explications

Voici quelques explications intéressantes qui vont vous permettre de faire une petite pause...

Un peu de vocabulaire sur ce qu'on vient de faire

On a utilisé plein de codes propres au pic. Ces codes se trouvent dans la datasheet (je rappelle qu'une datasheet est un fichier qui décrit votre composant, et que vous avez téléchargé...).

Ces codes sont reconnus sous la dénomination de "registre" ("register" en anglais). Ils sont reconnus par le pic, et donc par tous les logiciels de programmation de pic (normalement...). Voici à quoi ça ressemble chez moi :

Image IPB

Pour trouver cette page chez vous, faites une recherche de mot ("Ctrl + f"), et tapez "register file map". Appuyez sur entrée et vérifiez bien que vous êtes sur la page correspondant à votre pic. Pour moi, il y a marqué, au centre en haut : PIC16F876A/877A (seul ce qu'il y a marqué en gras m'intéresse). Il y a marqué aussi, en haut à droite : PIC16F87XA. Le "X" signifie que n'importe quel nombre peut-être mis à cette place.

En cherchant bien dans ce tableau, vous remarquerez que vous connaissez déjà PORTA, PORTB, PORTC, PORTD, PORTE (au début de la première colonne) et TRISA, TRISB, TRISC, TRISD, TRISE (deuxième colonne). Et pour l'instant, c'est tout !

Mais, je ne vois pas delay_ms !

L'autre code que je vous ai dit n'est pas propre au pic, (delay_ms) mais au logiciel microC.

Pour le retrouver, revenez sur microC et allez dans l'onglet Qhelp à gauche de la fenêtre (si vous n'avez pas ces onglets, allez dans "View --> Code explorer") :

Image IPB

Si vous êtes arrivé jusqu'ici en me faisant confiance, bravo ! Si vous êtes arrivé jusqu'ici en vous entraînant ne serait-ce qu'avec le pic que j'utilise, et le simulateur, c'est mieux ! L'idéal étant bien évidemment ceux qui ont un kit de développement qui marche avec le pic que j'utilise pour mes explications... Maintenant, faites une pause, parce que ce qui suit risque d'en décourager plus d'un... seulement c'est la partie suivante qui vous rendra définitivement indépendant dans ce monde impitoyable des pic




Troisième étape : les interruptions et... la datasheet


Les interruptions

Alors nous allons maintenant nous attaquer à un gros morceau de l'utilisation du pic : les interruptions !

Sous ce nom pas si barbare que ça se cache une notion SIMPLE.

En fait, votre robot va évoluer en fonction de ce qu'il se passe autour de lui. L'exemple le plus simple est si jamais il rencontre un obstacle : pour détecter un contact avec l'extérieur, vous pouvez placer des contacteurs, ou encore appelés "Microrupteurs". En réalité, c'est juste une lame qui lorsque on appuie dessus fait passer le courant par une broche, sinon il passe par une autre.

Dès que le courant passera par l'une de ces broches, le pic va l'interpréter comme un "1", et le programme s'arrêtera : d'où le nom d'interruption. En réalité, pendant cette interruption, il effectuera certaines tâches qui sont prévues pour cette interruption (par exemple un robot adverse qui vient nous pousser Grrrr....)

Suivez bien ce qui va suivre : ça va vous permettre de comprendre entièrement la structure d'une datasheet ! Et si vous comprenez entièrement une datasheet, vous n'avez plus besoin de ce tutoriel Image IPB.

Image IPBLe travail qui suit, est vraiment difficile... Faites le à tête reposée, en étant motivé pour faire cette partie et la suivante d'un trait, c'est le meilleur pour votre compréhension finale.

Nous allons commencer par repérer la page qui va nous intéresser dans la datasheet pour gérer les interruptions dans le PORTB :

Image IPB

Pour trouver cette page, j'ai lancé une recherche (Ctrl + f) et j'ai lancé la recherche pour "interrupt" jusqu'à arriver à une page ayant la structure précédente : les deux encadrés rouges permettent de repérer la structure. D'abord le nom du registre décrit sur cette page, puis - sachant qu'un registre est formé de huit bit - la description des huit bit (7 à 0) dans l'ordre. Je ne suis pas arrivé sur la page ci-dessus de suite, mais sur une autre qui ne m'intéressait pas. Pour trouver celle du dessus, j'ai regardé dans le texte de description si la page parle des interruptions dans un PORT. Le second que j'ai trouvé concernait le PORT B.

Sur cette page, INTCON est le nom du registre. Ce registre sert (comme c'est marqué en anglais en haut de la page...) à modifier la valeur des bits de type "ENABLE" et "FLAGS" des dépassements du timer0 (dépassements : en gros la valeur du timer0 a dépassé une valeur limite définie par défaut, et c'est ce qui est considérée comme interruptions pour le timer0... bon de toutes façons, on reverra la gestion du temps plus tard) ; ce registre sert aussi à modifier les valeurs des bits de type "ENABLE" et "FLAGS" des interruptions (provenant de l'extérieur) au niveau du PORTB du pic(et ça, ça nous intéresse au plus haut point ^^)

=> Les bits de type "ENABLE" permet d'AUTORISER la réalisation de certaines actions. Ici on cherche à autoriser les interruptions dans le PORTB. Cherchez à quel bit il correspond et notez-le (notez les tous s'il y en a plusieurs : réponse plus loin).

=> Les bits de type "FLAG" qui permettent de SAVOIR S'IL Y A EU UN CHANGEMENT QUELCONQUE DANS TOUT LE PORT. Ici, on va vouloir savoir si le PORTB a changé d'état ou non. Cherchez le ou les bits qui permettent de savoir si le PORTB a changé d'état ou non.

Solution dans le chapitre suivant...

La structure d'une datasheet

Voici la réponse aux recherches effectuées dans le précédent chapitre...
La page sur laquelle vous êtes arrivés est en fait accessible via le plan de la datasheet qui est affichée à gauche (ici, on regarde les interruptions : donc cherchez un chapitre qui parle d'interruption et cliquez dessus pour revenir sur une page comme celle ci-dessus. Si vous ne tombez pas sur la même page, cherchez dans les pages suivantes)

Une fois que vous avez vu que c'est accessible via le menu de gauche, vous remarquez les différents autres chapitres et sous chapitres qui existent. Essayez de comprendre un peu leur structure, et lisez rapidement leurs textes descriptifs pour savoir de quoi ils parlent. Vous aurez ainsi un aperçu de ce que vous pouvez faire avec votre pic.

J'ai parlé de bits de type "ENABLE" et "FLAGS". Sachez que les bits ENABLE doivent être mis à 1 pour pouvoir utiliser les fonctions qui leurs correspondent (et qui sont décris dans le texte descriptif en anglais, ainsi que sur le schéma de votre pic - qui se trouve au tout début, si vous vous rappelez bien). Les bits "FLAGS" permettent de connaître l'état de certaines pin ou bit d'un ou de plusieurs registres de votre pic. En gros, cela permet de savoir qu'est-ce qui a changé.

Il faut donc que vous réinitialisiez vos bits "ENABLE" et "FLAGS" après chaque changement !

Maintenant, vous pouvez travailler un peu plus tout seul !




D'autres outils du pic


Les timers

Je manque de temps pour terminer ce tutoriel. Je vais donner des axes d'études pour que vous puissiez continuer un peu seul. Voici la première aide pour utiliser les timers...
Enfin, pour travailler avec les interruptions, il faut que vous utilisiez la structure suivante, en dehors du main :

Code : C

void interrupt(void)
{
// votre code qui s'effectuera lors d'une interruption
}


Mettez le moins de choses possible dans la structure "interrupt". Elle ne doit vous permettre que :
  • de savoir si quelque chose a changé au niveau des entrées
  • d'enregistrer ces changements dans une variable globale (qui est donc définie au tout début du programme, et en dehors du main, via la commande :
Code : C

double ma_variable ma_valeur;


* et de réinitialiser vos registres "ENABLE" (que vous utilisez) à 1 et "FLAGS" à 0

Il ne faut surtout pas faire de calcul ou de traitement complexe (if for etc...) dans une interruption, sinon ça va "ramer" et ça risque surtout de planter.

Dans "Interrupt", il ne faut pas non plus utiliser de fonctions que vous utilisez aussi dans le main ! Cela est perçu comme une "redondance". En effet, imaginez que vous essayez d'utiliser la fonction cos dans le main, et que pendant que le pic fait le calcul, il y a une interruption... et que pendant cette interruption, vous essayez aussi de faire la fonction cos... eh bien, sachant que la fonction est programmée à "un seul endroit", vos variables utilisées dans l"interrupt" vont ecraser celles du main... du coup, plantage.

Pensez à aller voir la datasheet et google pour compléter vos connaissances sur le timer !

Se répérer dans un plan

Pour vous repérer dans un plan, vous devrez mettre des capteurs au niveau de vos roues, et ces capteurs peuvent être des encodeurs optiques ou "roues codeuses"...

Si vous avez compris tout ce qui précède, déjà, vous pouvez vous débrouiller - avec un peu de logique et de recherche - pour utiliser
* des contacteurs (des interrupteurs)
* des roues codeuses... en fait, le signal d'UNE SEULE roue codeuse est composée de deux bits en sortie (des interrupteurs qui sont activés puis désactivés) qui peuvent chacun avoir deux valeurs. Seulement, leur séquence est bien définie :

110011001100 : --__--__--__

011001100110 : _--__--__--_

A gauche, c'est la description de leurs états en séquence de 1 et de 0, et à droite, un "semblant de chronographe" qui leur correspond. Vous pouvez remarquer que leurs signaux sont identiques, à ceci près qu'ils sont décalés d'1/4 de période. Cela permet de savoir dans quel SENS la roue tourne ! en effet, si vous "lisez" la séquence dans le sens inverse, le signal n'est pas le même ! Il faut pouvoir interpréter cela sur un pic, grâce aux interruptions qui se déclenchent sur les front montants de l'un des bits seulement. Pour plus de détails sur les roues codeuses, voici la définition que j'ai rédigée sur le dicobot concernant les roues codeuses :

Ce composant est un capteur qui envoie des impulsions au fur et à mesure que son axe tourne. En clair, il peut-être utilisé comme capteur d'angle, indispensable si on veut savoir précisément où son robot se trouve.

Le signal envoyé peut avoir un codage dit "GRAY" ou alors en "QUADRATURE DE PHASE".
Ces roues codeuses possèdent deux fils d'alimentation (5V et la masse en général), plus d'autres fils qui dépendent du type de codage... Dans chaque cas, le codage représente le signal envoyé au fur et à mesure que l'axe tourne (chaque ligne correspond à un angle différent sur la roue codeuse), sur chaque fil (chaque colonne correspond à un fil différent sur la roue codeuse) : le 1 correspond au 5V utilisé par un pic par exemple, ou le 24V utilisé pour certaines certaines cartes d'acquisitions, et le 0 représente le 0V.

Roue codeuse à codage GRAY :

Ces roues codeuses possèdent plusieurs autres fils (au moins 3 de plus). Je vais détailler le signal envoyé par des roues codeuses 4 bits (donc avec 4 fils de plus, soit 6 fils au total) sur deux colonnes (lire chaque ligne de la colonne de gauche de haut en bas, puis celle de droite) :
0000|----|1100
0001|----|1101
0011|----|1111
0010|----|1110
0110|----|1010
0111|----|1011
0101|----|1001
0100|----|1000

Le signal ainsi envoyé se répète en recommençant à la première ligne une fois que la roue a fait un tour complet. On peut donc connaître facilement à tout instant l'angle actuel de la roue.

Avantage : angle facile à obtenir
Inconvénient : il y a beaucoup de fil, ce qui limite l'utilisation du pic, et ce genre de codeur coûte plus cher que celles à codage en quadrature de phase

Roue codeuse à codage en QUADRATURE DE PHASE

Ces roues codeuses possèdent deux autres fils. Voici un schéma du code reçu (signal en quadrature de phase) au cours du temps :

Image IPB


On remarque que le signal est très simple, et qu'il se répète indéfiniment. Mais attention cependant, une période de ce signal ne correspond pas à un tour complet, mais à seulement à 1/n ème de tour de la roue ! Plus "n" est grand, plus la roue codeuse est précise !

Vous pouvez remarquer que les deux signaux sont identiques, à ceci près qu'ils sont décalés d'1/4 de période. Cela permet de savoir dans quel SENS la roue tourne ! En effet, si vous "lisez" la séquence dans le sens inverse, le signal n'est pas le même !

Avantage : seuls deux fils sort de cette roue qui coûte moins cher qu'une roue à codage gray

Inconvénient : si vous prenez une roue trop précise, et que votre axe tourne trop vite... la fréquence d'envoi d'une période risque d'être trop importante pour un pic... Mais en gros, une précision de n = 50 pas par tour est facilement utilisable. De plus, ce n'est pas un codage absolu en angle ! En effet, le code se répète tous les 1/n tours... en fait, vous allez pouvoir connaître de combien a tourné votre axe par rapport à sa position antérieure.

Image IPBComment le codage est réalisé dans ces roues ?

C'est tout simple : un disque avec des trous, puis un laser et un capteur de lumière de l'autre côté du disque et le tour est joué : c'est ce capteur de lumière qui envoie "1" lorsque la lumière du laser traverse le trou et "0" lorsque la lumière est arrêté par le disque.

ATTENTION : la description que j'ai donnée pour les roues codeuses correspond à une description de roues codeuses avec deux signaux en codage binaire en "quadrature de phase" - comme on dit dans le jargon. Il existe d'autres types de roues codeuses, avec plus de bits, et dont le codage est dit "GRAY". Il en existe d'autres...

ATTENTION 2 : le signal des roues codeuses mécaniques est particulièrement merdique... Il faudra donc penser à utiliser des condensateurs pour affiner la courbe que vous pourrez obtenir avec ces roues lorsqu'elles tourneront, voire même utiliser un montage d'hystérésis pour ne prendre que les fronts montants et descendants aux valeurs que vous définirez pour obtenir des signaux carrés vraiment "jolis" et utilisables par un pic

* des moteurs à courant continu

* des moteurs pas à pas

Je donne quelques explications sur l'utilisation de ces deux moteurs dans les deux derniers chapitres.
Vous pourrez trouver plein d'autres capteurs que les roues codeuses pour vous repérer... Des pistes seront données en conclusion finale.

PWM : contrôle moteurs

PWM = Pulse Width Modulation ou Signal Modulé en Fréquence... C'est grâce à cette fonction du PIC que vous pourrez contrôler la vitesse d'un moteur à courant continu par exemple !
PWM = Pulse Width Modulation...

Bon en gros c'est la manière de fournir le courant continu au moteur à courant continu... En réalité, si on envoie une tension et un courant plus ou moins puissant dans le moteur, le moteur va logiquement plus ou moins vite. Du coup, comme c'est difficile d'envoyer de tels signaux, on utilise des composants qui font ça à notre place, et ces composants nécessitent un signal dit "carré"...

Signal carré

Ce signal possède deux état : un dit "HAUT" et l'autre dit "BAS". On dit aussi état 1 ou 0 (1 pour la tension maximale et 0 pour 0V). Ce que fait le composant dont je vous ai parlé, c'est de transformer ce signal en un signal "moyen". Si la moitié du temps il a du 5V et l'autre moitié il a du 0V, il enverra alors une tension moyenne de 2,5V... Si par contre 80% du temps il a 5V et les 20% restants il a 0, il sortira du 4V moyen... Vous vous apperevrez alors que votre moteur ira plus vite !

Le pourcentage représentant la fréquence où la tension est à l'état haut est appelé "Rapport cyclique" et comporte généralement la lettre "alpha".

Vous pouvez essayer d'utiliser des moteurs à courant continu (en utilisant un signal PWM) en passant par un circuit intermédiaire qui va transformer le rapport cyclique de votre PWM en tension continue à puissance nominale (je vous conseille les LM621).

Voici un code que j'ai écris et qui fonctionne (à condition de faire le montage qui correspond...) :

Un robot avec deux roues motrices à courant continu, contrôlés à vitesse maximale ou nulle ou négative via des interrupteurs à 3 positions (la mise en marche d'une courroie et d'une porte à l'arrière du robot étaient aussi commandés via deux autres interrupteurs)

Code : C
 
// --------------------------------------------
// Liste des ports utilisés pour gérer le robot
// --------------------------------------------

// Vitesse du moteur de la roue Gauche (VG) et Vitesse du moteur de la roue Droite (VD) [VITESSE ==> FREQUENCE] <=> à gérer avec le PWM et la fréquence d'envoi des données
#define VG CCPR1L // 1 bit
#define VD CCPR2L // 1 bit
#define SENSG1 PORTC.F3 // 1 bit : 1 si vers l'avant 0 vers l'arrière sauf si SENSG1 = SENSG2 ==> STOP
#define SENSG2 PORTC.F0 // 1 bit : 1 si vers l'arrière 0 vers l'avant sauf si SENSG1 = SENSG2 ==> STOP
#define SENSD1 PORTD.F0 // 1 bit : 1 si vers l'avant 0 vers l'arrière sauf si SENSG1 = SENSG2 ==> STOP
#define SENSD2 PORTD.F1 // 1 bit : 1 si vers l'arrière 0 vers l'avant sauf si SENSG1 = SENSG2 ==> STOP
// Télécommande : ordonne l'avancée et le recul des roues gauche et droite
#define ARG PORTD.F6
#define RRG PORTD.F7
#define ARD PORTD.F4
#define RRD PORTD.F5

// -------------------------
// Définition des constantes
// -------------------------
double PI = 3.14159265;
double VGMAX = 30;
double VDMAX = 30;
// double K = 0.001396; // = 2 * PI * 80mm / ( 15 * 24 ) CONSTANTE de réduction roue codeuse

// --------------------------
// Définition des variables :
// --------------------------
double t;

//==============================================================================
//==============================================================================
//==============================================================================

// Fonction qui permet de gérer le temps
// Nécessite :
void Temp(void)
{
if (INTCON.F2) // Gestion du temps : [0 ==> 100000] temppetit ==> x100000 ==> [0 ==> 100000] tempmoyen ==> x100000 ==> [0 ==> taille(double)] tempgros
{
t = t + 1;
TMR0L = 0;
}
}

//==============================================================================

// Fonction qui permet de gérer les interruptions
// Nécessite :
void interrupt(void)
{
Temp();
INTCON = 0b10100000;
}

//==============================================================================
//==============================================================================
//==============================================================================

// ===================
// Programme principal
// ===================

void main(void)
{
// -----------------------------
// Liste des variables utilisées
// -----------------------------

// ---------------------------------------------------------------------
// Définition des ports pour les interruptions sur le PORTB et le timer0
// ---------------------------------------------------------------------
T0CON = 0b10000100; // Utiliser le TMR0 en 16 bits et prescaler de TMR0 initialisé à 1/16
TMR0L = 0;
//TMR0L = 0b00011110; // Les interruptions s'effectueront toutes les : 1 s (si on est sur 16 bits)
//TMR0H = 0b00000000; // Uniquement sur 16 bits
INTCON = 0b10100000; // Autoriser les interruptions, celles de TMRO
ADCON1 = 0b00001111; // Utiliser les 4 premiers bits du port B en mode analogic [= analogique en anglais] uniquement (et non pas en digital = numérique en anglais)

// ---------------------------------------------------------------------
// Configuration du PWM à 4 kHz (hmmm plus en fait) Vitesse maximale 125
// ---------------------------------------------------------------------
PR2 = 0b00111111 ;
T2CON = 0b00000101 ;
CCP1CON = 0b00001100 ;
CCP2CON = 0b00111100 ;

// ---------------------
// Initialisation du pic
// ---------------------
TRISC = 0; // sortie
TRISD = 0b11110000; // 4 en sortie et 4 en entrée
PORTC = 0;
PORTD = 0;
VG = 0;
VD = 0;

// -----------------
// Boucle principale
// -----------------

while(1)
{
if (ARG)
{
VG = VGMAX; SENSG1 = 1; SENSG2 = 0;
if (ARD)
{
VD = VDMAX; SENSD1 = 1; SENSD2 = 0;
}
else if (RRD)
{
VD = VDMAX/2; SENSD1 = 0; SENSD2 = 1;
}
else
{
VD = 0; SENSD1 = 0; SENSD2 = 0;
}
}
else if (RRG)
{
VG = VGMAX/2; SENSG1 = 0; SENSG2 = 1;
if (ARD)
{
VD = VDMAX; SENSD1 = 1; SENSD2 = 0;
}
else if (RRD)
{
VD = VDMAX/2; SENSD1 = 0; SENSD2 = 1;
}
else
{
VD = 0; SENSD1 = 0; SENSD2 = 0;
}
}
else
{
VG = 0; SENSG1 = 0; SENSG2 = 0;
if (ARD)
{
VD = VDMAX; SENSD1 = 1; SENSD2 = 0;
}
else if (RRD)
{
VD = VDMAX/2; SENSD1 = 0; SENSD2 = 1;
}
else
{
VD = 0; SENSD1 = 0; SENSD2 = 0;
}
}
}
}

Pour ce dernier robot, j'ai fait aussi le schéma électrique

Avec votre pic, vous devriez pouvoir créer un ou plusieurs signaux PWM... renseignez vous avec la datasheet !

Moteurs pas à pas

Voici un exemple de code de contrôle d'un moteur pas à pas. Vous trouverez une explication sur certains moteurs ici.
Moteur pas à pas qui tourne en même temps que tourne une roue codeuse :

Code : C
 
// --------------------------------------------
// Liste des ports utilisés pour gérer le robot
// --------------------------------------------
// CONTacteurs
#define CONT PORTB.F2
// Roues codeuses gauche et droite
#define RCG1 PORTB.F0
#define RCG2 PORTB.F3
#define RCD1 PORTB.F1
#define RCD2 PORTB.F4

// Vitesse du moteur de la roue Gauche (VG) et Vitesse du moteur de la roue Droite (VD) [VITESSE ==> FREQUENCE] <=> à gérer avec le PWM et la fréquence d'envoi des données
#define VG CCPR1L // 1 bit
#define VD CCPR2L // 1 bit
#define SENSG1 PORTD.F0 // 1 bit : 1 si vers l'avant 0 vers l'arrière sauf si SENSG1 = SENSG2 ==> STOP
#define SENSG2 PORTD.F1 // 1 bit : 1 si vers l'arrière 0 vers l'avant sauf si SENSG1 = SENSG2 ==> STOP
#define SENSD1 PORTD.F2 // 1 bit : 1 si vers l'avant 0 vers l'arrière sauf si SENSG1 = SENSG2 ==> STOP
#define SENSD2 PORTD.F3 // 1 bit : 1 si vers l'arrière 0 vers l'avant sauf si SENSG1 = SENSG2 ==> STOP

// Vitesse de la Courroie (VC) avant : [VITESSE ==> FREQUENCE] <=> à gérer avec la fréquence d'envoi des données
#define VC PORTC.F4 // 1 bit
#define SENSC1 PORTD.F4 // 1 bit
#define SENSC2 PORTD.F5 // 1 bit
// Vitesse de la Porte (VP) arrière : [VITESSE ==> FREQUENCE] <=> à gérer avec la fréquence d'envoi des données
#define VP PORTC.F6 // 1 bit
#define SENSP1 PORTD.F6 // 1 bit
#define SENSP2 PORTD.F7 // 1 bit



// --------------------------
// Définition des objectifs :
// --------------------------

// -------------------------
// Définition des constantes
// -------------------------


typedef enum Sens {AVANT,ARRIERE} Sens;

// --------------------------
// Définition des variables :
// --------------------------
char t;
Sens SRCG; // Sens Roue Codeuse Gauche
char NbPasCG; // Nombre de pas de roue codeuse gauche



//==============================================================================
//==============================================================================
//==============================================================================



// *****************************
// Liste des fonctions utilisées
// *****************************


//==============================================================================



//==============================================================================
//==============================================================================
//==============================================================================


// Fonction qui permet de gérer le temps
// Nécessite :
void Temp(void)
{
//if (INTCON.F2) // Gestion du temps : [0 ==> 100000] temppetit ==> x100000 ==> [0 ==> 100000] tempmoyen ==> x100000 ==> [0 ==> taille(double)] tempgros
{
t = t + 1;
//TMR0L = 0;
}
}



//==============================================================================


// Fonction qui permet de changer l'état du PORTD à chaque fois qu'il y a une interruption
// Nécessite :
void CodeurG(void)
{
if (RCG2 == 1)
{
SRCG = AVANT;
NbPasCG++;
}
else
{
SRCG = ARRIERE;
NbPasCG--;
}
}



//==============================================================================


// Fonction qui permet de gérer les interruptions
// Nécessite : Temp()
void interrupt(void)
{
if (INTCON.F2 == 1)
{
Temp();
INTCON.F2 = 0;
}
if (INTCON.F1 == 1)
{
CodeurG();
INTCON.F0 = 0;
}

INTCON = 0b10110000; // Réinitialise les options d'interruptions
}


//==============================================================================
//==============================================================================
//==============================================================================


// ===================
// Programme principal
// ===================

void main(void)
{
// -----------------------------
// Liste des variables utilisées
// -----------------------------


// ---------------------------------------------------------------------
// Définition des ports pour les interruptions sur le PORTB et le timer0
// ---------------------------------------------------------------------
T0CON = 0b10000100; // Utiliser le TMR0 en 16 bits et prescaler de TMR0 initialisé à 1/16
//TMR0L = 0b00011110; // Les interruptions s'effectueront toutes les : 1 s (si on est sur 16 bits)
//TMR0H = 0b00000000; // Uniquement sur 16 bits
INTCON = 0b10110000; // Autoriser les interruptions, celles de TMRO et INT0 (B.0)
ADCON1 = 0b00001111; // Utiliser les 4 premiers bits du port B en mode analogic [= analogique en anglais] uniquement (et non pas en digital = numérique en anglais)

/*
// ---------------------------------------------------------------------
// Configuration du PWM à 4 kHz (hmmm plus en fait) Vitesse maximale 125
// ---------------------------------------------------------------------
PR2 = 0b00111111 ;
T2CON = 0b00000101 ;
CCP1CON = 0b00001100 ;
CCP2CON = 0b00111100 ;
*/

// ---------------------
// Initialisation du pic
// ---------------------
TRISA = 0;
TRISB = 0b11111111;
TRISC = 0;
TRISD = 0;
TRISE = 0;
PORTA = 0;
PORTB = 0;
PORTC = 0;
PORTD = 0;
PORTE = 0;
t = 0;

// ************************
// DEFINITION DES OBJECTIFS
// ************************

while(1)
{
PORTD = t;
PORTC = NbPasCG;
if (SRCG == AVANT)
{
PORTA.F4 = 0;
}
else
{
PORTA.F4 = 1;
}
}
}



Vitesse du Moteur pas à pas contrôlé par la roue codeuse

Code : C

// Roues codeuses gauche et droite
#define RCG1 PORTB.F0
#define RCG2 PORTB.F3
#define RCD1 PORTB.F1
#define RCD2 PORTB.F4

typedef enum Sens {AVANT,ARRIERE} Sens;

char t;
Sens SRCG; // Sens Roue Codeuse Gauche
char NbPasCG; // Nombre de pas de roue codeuse gauche

int pas;


// Fonction qui permet de gérer le temps
// Nécessite :
void Temp(void)
{
//if (INTCON.F2) // Gestion du temps : [0 ==> 100000] temppetit ==> x100000 ==> [0 ==> 100000] tempmoyen ==> x100000 ==> [0 ==> taille(double)] tempgros
{
t = t + 1;
//TMR0L = 0;
}
}



//==============================================================================


// Fonction qui permet de changer l'état du PORTD à chaque fois qu'il y a une interruption
// Nécessite :
void CodeurG(void)
{
if (RCG2 == 1)
{
SRCG = AVANT;
NbPasCG++;
}
else
{
SRCG = ARRIERE;
NbPasCG--;
}
}



//==============================================================================


// Fonction qui permet de gérer les interruptions
// Nécessite : Temp()
void interrupt(void)
{
if (INTCON.F2 == 1)
{
Temp();
INTCON.F2 = 0;
}
if (INTCON.F1 == 1)
{
CodeurG();
INTCON.F0 = 0;
}

INTCON = 0b10110000; // Réinitialise les options d'interruptions
}


void wait_ms(int x)
{
int i;
for(i=0;i<=x;i++)
{
delay_ms(1);
}
}


void majEtat(void)
{
int etat = pas%4;
if (etat<0) etat += 4;
if ( etat == 0 )
PORTD = 0b00101000;
else if ( etat == 1 )
PORTD = 0b00000101;
else if ( etat == 2 )
PORTD = 0b00000011;
else // etat = 4
PORTD = 0b00011000;
}


void main (void)
{
int x, Vmax, Vmin, Da, Dc, Df, SAVE, V, S; // Distance de : Acceleration, constante, freinage

// ---------------------------------------------------------------------
// Définition des ports pour les interruptions sur le PORTB et le timer0
// ---------------------------------------------------------------------
T0CON = 0b10000100; // Utiliser le TMR0 en 16 bits et prescaler de TMR0 initialisé à 1/16
//TMR0L = 0b00011110; // Les interruptions s'effectueront toutes les : 1 s (si on est sur 16 bits)
//TMR0H = 0b00000000; // Uniquement sur 16 bits
INTCON = 0b10110000; // Autoriser les interruptions, celles de TMRO et INT0 (B.0)
ADCON1 = 0b00001111; // Utiliser les 4 premiers bits du port B en mode analogic [= analogique en anglais] uniquement (et non pas en digital = numérique en anglais)

/*
// ---------------------------------------------------------------------
// Configuration du PWM à 4 kHz (hmmm plus en fait) Vitesse maximale 125
// ---------------------------------------------------------------------
PR2 = 0b00111111 ;
T2CON = 0b00000101 ;
CCP1CON = 0b00001100 ;
CCP2CON = 0b00111100 ;
*/

// ---------------------
// Initialisation du pic
// ---------------------
TRISA = 0;
TRISB = 0b11111111;
TRISC = 0;
TRISD = 0;
TRISE = 0;
PORTA = 0;
PORTB = 0;
PORTC = 0;
PORTD = 0;
PORTE = 0;
t = 0;
pas = 0;
Vmax = 5;
V = 30;
Vmin = 80;
Da = 100;
Df = 100;
Dc = 200;
SAVE = NbPasCG;
S = 1;

while(1)
{
if (S == 1)
{
if(NbPasCG > SAVE)
{
if(V>Vmax)
{
V--;
}
}
else if (NbPasCG < SAVE)
{
if(V<Vmin)
{
V++;
}
else
{
S = -1;
V = Vmin-1;
}
}
pas++;
}
else
{
if(NbPasCG < SAVE)
{
if(V>Vmax)
{
V--;
}
}
else if (NbPasCG > SAVE)
{
if(V<Vmin)
{
V++;
}
else
{
S = 1;
V = Vmin-1;
}
}
pas--;
}
SAVE = NbPasCG;
wait_ms(V);
majEtat();
}
}
Avec un peu de recherche sur la datasheet et en pensant au composant LM621, vous devriez arriver à vos fins avec un peu de recherche sur le net (je manque vraiment de temps pour terminer convenablement le tutoriel).

Par manque cruel de temps, ce tutoriel a été littéralement "bâclé" sur la fin. Vous avez néanmoins suffisamment de bagage pour pouvoir faire vos recherches un peu seul...

Vous pouvez par la suite essayer de vous intéresser aux réseaux I2C qui permettent à plusieurs pic de communiquer entre eux de manière assez "facile" (vous n'avez pas à programmer vous-même le protocole de communication) ou encore les Conversion Analogique Numérique et inversement (CNA/CAN). Vous pouvez également essayer d'utiliser les entrées/sorties analogique (au lieu du tout ou rien) qui permettront de recevoir un signal en provenance d'un capteur de distance par exemple... Ce sont autant de pistes pour vous aider à continuer à vous améliorer.

Sur ce, j'espère que ce tutoriel vous aura quand même beaucoup aidé, et si certains sont motivés pour re-rédiger ce tutoriel en tout ou partie, il est totalement libre, je demande juste à ce qu'il le reste.


Trigonométrie

Publié Harry le 14 avril 2016 - 05:53 in Concepts

Bonjour, ce tutoriel est plus une liste de formules très utiles en robotique qu'un vrai tutoriel, mais il peut être très efficace de l'avoir à portée de main...

Voici le cercle trigonométrique, son rayon étant pris égal à 1 :

Image IPB


Image IPB Sur le cercle est positionné l'angle que fait le vecteur OA avec l'horizontale : on l'appelle alpha.

Vous remarquerez que l'angle entre OB et AB est 90° : OAB est donc rectangle en B. Prenons ce triangle rectangle.

On pose trois fonctions indispensables en mathématiques que sont cosinus, sinus et tangente, qui sont écrits, si alpha est un angle : cos(alpha), sin(alpha) et tan(alpha)

Ils sont définis par (à connaître par coeur avec le schéma qui va avec) :
cos(alpha) = côté adjacent / hypoténuse
sin(alpha) = côté opposé / hypoténuse
tan(alpha) = côté opposé / côté adjacent


sachant que dans le triangle rectangle OAB :
côté adjacent = OB
côté opposé = BA
hypoténuse = OA

alors on obtient (à connaître par coeur avec le schéma qui va avec) :
cos(alpha) = OB/OA
sin(alpha) = BA/OA
tan(alpha) = BA/OB


Image IPB

Image IPB Un symbole bizarre (en jaune) est introduit sur le schéma : il s'appelle PI et vaut environ 3,1415... (le nombre de chiffre après la virgule est infini).
L'unité utilisé en trigonométrie pour les angles n'est pas le degré (noté °), mais le radian (noté rad)
Ainsi, PI rad = 180°


Image IPB Le cosinus de l'angle fait entre OA et l'horizontale se trouve en projetant A sur l'horizontale (soit B ce point). Son sinus est trouvé en projetant A sur la verticale (soit C ce point). Certaines valeurs de cosinus et de sinus sont à connaître par cœur :


Image IPBcos (0) = sin(PI/2) = 1
cos(PI/6) = sin(PI/3) = racine(3)/2
cos(PI/4) = sin(PI/4) = racine(2)/2
cos(PI/3) = sin(PI/6) = 1/2
cos(PI/2) = sin ( 0 ) = 0


Image IPBExercice : trouver la valeur des tangentes de ces nombres

Image IPBOn remarque que sin(alpha)/cos(alpha) = tan(alpha) (il suffit d'écrire la relation avec les longueurs pour s'en convaincre). Ainsi, connaissant les valeurs de sin(alpha) et cos(alpha) on retrouve les valeurs facilement.

tan(0) = 0
tan(PI/6) = 1/racine(3)
tan(PI/4) = 1
tan(PI/3) = racine(3)
tan(PI/2) = l'infini



Vous devez retrouver l'équivalent des angles mis sur le schéma suivant, et les connaître par cœur, et savoir les positionner sans problème sur le cercle trigonométrique, ainsi que leurs cosinus, sinus et tangente :

Image IPB

Un sens positif et un sens négatif sont indiqués : en réalité l'angle en radian n'est pas un angle simple. Il est dit "algébrique" car il peut être négatif comme positif. Cela a donc une conséquence sur les valeurs des sinus et des cosinus. En clair, il faut savoir refaire le schéma précédent par cœur.


Formules trigonométriques à savoir - si on prend "a" et "b" deux angles algébriques :

sin(-a) = -sin(a)
cos(-a) = cos(a)
cos (a + B ) = cos(a).cos( B ) - sin(a).sin( B )
sin (a + B ) = sin(a).cos( B ) + cos(a).sin( B )
cos²(a) + sin²(a) = 1
cos(2a) = cos²(a) - sin²(a) = 2.cos²(a) - 1 = 1 - 2.sin²(a) (à savoir retrouver)
sin(2a) = 2.sin(a).cos(a) (à savoir retrouver)


Formules à savoir retrouver (ça peut faire d'excellents exercices) :
cos( PI/2 - a ) = sin(a)
sin( PI/2 - a ) = cos(a)
cos( PI/2 + a ) = sin(a)
sin( PI/2 + a ) = -cos(a)
cos( PI - a ) = -cos(a)
sin( PI - a ) = sin(a)
cos( PI + a ) = -cos(a)
sin( PI + a ) = -sin(a)
cos( a - B ) = cos(a).cos( B ) + sin(a).sin( B )
sin( a - B ) = sin(a).cos( B ) - sin( B ).cos(a)


Très utiles à savoir retrouver :

Image IPB

Formule à utiliser quand vous en aurez besoin :

Image IPB

Loi des sinus : formule valable pour un triangle quelconque ABC(en prenant A, B et C les angles et a, b, c leurs côtés opposés respectifs)

a/sin(A) = b/sin( B ) = c/sin( C ) = 2R


Enfin, loi des cosinus (ou loi d'Al-kashi) : des formules qui donne des relations entre le cosinus d'un angle et les longueurs d'un triangle quelconque ! Extrêmement utile en robotique pour effectuer des calculs sans utiliser des librairies lourdes pour un pic pour calculer des relations particulières :

a² = b² + c² - 2b.c.cos A ==> On en déduit la valeur de cos A
b² = c² + a² - 2c.a.cos B ==> On en déduit la valeur de cos B
c² = a² + b² - 2a.b.cos C ==> On en déduit la valeur de cos C


Voilà, j'espère que cette liste de formules vous sera utile.