Difference between revisions of "Projets:Perso:2013:Module de localisation"

From Electrolab
Jump to: navigation, search
(Les signaux analogiques)
(Localisation par balisage infrarouge)
 
(48 intermediate revisions by one user not shown)
Line 1: Line 1:
== Système de balisage infrarouge ==
+
= Localisation par balisage infrarouge =
 
+
Retour à [[User:Mael]]<br />
 
Cette page présente mes travaux actuels sur la fabrication d'un système de balisage infrarouge pour mobile.<br />
 
Cette page présente mes travaux actuels sur la fabrication d'un système de balisage infrarouge pour mobile.<br />
 
Le système contient les fonctions suivantes :<br />
 
Le système contient les fonctions suivantes :<br />
Line 7: Line 7:
 
* Contrôle d'un moteur pas à pas d'entrainement de la tourelle.<br />
 
* Contrôle d'un moteur pas à pas d'entrainement de la tourelle.<br />
  
En plus de la fonction de triangulation associée au balisage, le système intègre en permanence deux entrées codeuses, assurant ainsi l'odométrie du mobile. Les données d'odométrie sont ensuite intégrées aux données de balisage en permanence par l'intermédiaire d'un filtre de Kalman.
+
En plus de la fonction de triangulation associée au balisage, le système intègre en permanence deux entrées codeuses, assurant ainsi l'odométrie du mobile.<br /> Les données d'odométrie sont ensuite intégrées aux données de balisage en permanence par l'intermédiaire d'un filtre de Kalman.<br />
  
Un troisième volet de ce projet est le calcul en permanence d'une "void area", basée sur un réseau de capteurs ultrason déployés sur le mobile.
+
Un troisième volet de ce projet est le calcul en permanence d'une "void area", basée sur un réseau de capteurs ultrason déployés sur le mobile.<br />
  
Dans un second temps, un autre projet viendra compléter celui-ci avec la réalisation d'un système de contrôle commande des moteurs (en DC, stepper DC puis en BLDC) avec un calcul de trajectoire basé sur les splines de Catmull-Rom.  
+
Dans un second temps, un autre projet viendra compléter celui-ci avec la réalisation d'un système de contrôle commande des moteurs (en DC, stepper DC puis en BLDC) avec un calcul de trajectoire basé sur les splines de Catmull-Rom. <br />
 
----
 
----
  
=== Localisation par balisage infrarouge ===
+
== Système de balisage infrarouge ==
 
+
Le principe de balisage infrarouge consiste à utiliser des balises émettant un signal infrarouge que va détecter un plot central, déployé sur une structure mobile roulante par exemple.
+
Le concept est développé dans le cadre de la coupe de France de robotique, et permet de disposer facilement d’une information de localisation sur la table de 3x2m.
+
>>Table + balisage<<
+
 
+
Le but est d’obtenir la meilleur estimation de x et y afin d’avoir la position du robot sur la table, ainsi que son orientation.
+
Le principe appliqué est celui de la triangulation, basée sur une mesure d’angle (il existe le même principe avec les distances, tout n’étant au final qu’une application des équations de trigonométrie).
+
Le choix du balisage infrarouge est lié au faible coût de la mise en œuvre (une centaine d’euros tout compris) et à la relativement grande précision du système.
+
Il sera couplé, dans la suite de ce document, à un dispositif d’odométrie et un dispositif de sonars permettant d’affiner la position courante du robot et de créer une « void area » autour du robot indiquant les zones accessibles ou non au robot.
+
>>Table + odométrie<<
+
  
 +
Le principe de balisage infrarouge consiste à utiliser des balises émettant un signal infrarouge que va détecter un plot central, déployé sur une structure mobile roulante par exemple.<br />
 +
Le concept est développé dans le cadre de la coupe de France de robotique, et permet de disposer facilement d’une information de localisation sur la table de 3x2m.<br />
 +
[[File:Table_de_jeu_-_Localisation_IR.jpg]]
 +
<br />
 +
Le but est d’obtenir la meilleur estimation de x et y afin d’avoir la position du robot sur la table, ainsi que son orientation.<br />
 +
Le principe appliqué est celui de la triangulation, basée sur une mesure d’angle (il existe le même principe avec les distances, tout n’étant au final qu’une application des équations de trigonométrie).<br />
 +
Le choix du balisage infrarouge est lié au faible coût de la mise en œuvre (une centaine d’euros tout compris) et à la relativement grande précision du système.<br />
 +
Il sera couplé, dans la suite de ce document, à un dispositif d’odométrie et un dispositif de sonars permettant d’affiner la position courante du robot et de créer une « void area » autour du robot indiquant les zones accessibles ou non au robot.<br />
 +
[[File:Table_de_jeu_-_Void_area_et_conduite.jpg]]
 +
<br />
 
----
 
----
 +
Les concepts définis par la suite s'appuient principalement sur deux éléments :
 +
* [[Microchip DsPic (33FJ128MC802)]]
 +
* [[SSV DNP5370 (Blackfin)]]
 +
Le détail de l'utilisation de ces composants est donné dans les pages associées.<br />
  
== Microcontrôleur DsPIC ==
+
== Triangulation ==
 +
Le principe de la triangulation est de mesurer trois angles entre des points fixes et un point mobile, et de retrouver, connaissant la position des points fixes, la position du point mobile. La manœuvre est limité à la détection d’objets à l’intérieur de la zone formée par les points fixes (c’est peut-être possible en dehors, mais je n’ai pas fait l’analyse, pas besoin).<br />
 +
Le nombre limite de points fixes est au minimum de 3, dans le cadre de l’étude 4 seront utilisés (par extension de la version à 3 points). Plus il y a de points fixes, meilleure est la précision, mais plus long seront les traitements.<br />
 +
Tout d’abord, définissons quelques variables. Le schéma ci-dessous représente la table sur laquelle est porté un repère orthonormé qui va servir de base aux calculs. Deux angles sont définis :
 +
* θAB,
 +
* θAC.
 +
[[File:Triangulation_-_principe.jpg]]<br />
 +
Le calcul de ces deux angles sera précisé plus bas. Pour l’instant, partons du principe qu’ils sont connus. Par triangulation, nous allons calculer xM et yM en calculant un certain nombre de variables de cette figure qui nous donnerons, in fine, les coordonnées du point M.<br />
 +
Par analyse du schéma, on pose :
 +
:: xM = AM x Cos[a]
 +
:: yM = AM x Sin[a]
 +
'''Nota''' : pour ceux qui préfèrent travailler en polaire, il est possible d’utiliser AM (ie r) et a (ie θ) directement, par exemple pour trianguler la position d’un outil sur un bras SCARA.<br />
 +
Le calcul de la variable AM se base sur les trois formules suivantes :
 +
* Loi des sinus : MB /Sin[a] = MA/Sin[Pi - θAB - a] = AB/Sin[θAB]
 +
* Sin[Pi - x] = Sin[x]
 +
* Sin[a+b] = Sin[a] x Cos[b] + Sin[b] x Cos[a]
 +
On définit ainsi :
 +
:: MB = (AB x Sin[a]) / Sin[θAB] = (AM x Sin[a]) / Sin[Pi - θAB - a]
 +
: Avec : Sin[Pi - θAB - a] = Sin[θAB] x Cos[a] + Sin[a] x Cos[θAB]
 +
D’où, tous calculs fait :
 +
{| class="wikitable centre" width="30%"
 +
|-
 +
! scope=col | AM = (AB x Sin[a]) / Tan[θAB] + AB x Cos[a]
 +
|-
 +
|}
 +
Le calcul de la variable [a] se base sur les formules suivantes :
 +
* Loi des sinus : MB /Sin[a] = MA/Sin[Pi - θAB - a] = AB/Sin[θAB]
 +
* Sin[Pi - x] = Sin[x]
 +
* Sin[a+b] = Sin[a] x Cos[b] + Sin[b] x Cos[a]
 +
* Sin[PI/2 + x] = Cos[x]
 +
* Cos[a-b] = Cos[a] x Cos[b] + Sin[a] x Sin[b]
 +
Dans le schéma ci-dessus, on applique la formule des sinus :
 +
:: AC / Sin[θAC] = AM / Sin[{ACM}]
 +
:: AB / Sin[θAB] = AM / Sin[{MBA}]
 +
Sachant que les angles :
 +
:: {ACM} = PI/2 – a - θAC
 +
:: {MBA} = PI – a - θAB
 +
:: AC / Sin[θAC] = AM / Cos[a - θAC]
 +
:: AB / Sin[θAB] = AM / Sin[a + θAB]
 +
Il ne reste plus qu’à identifier [a] en joignant les équations en AM.<br /> 
 +
Tous calculs faits :
 +
:: (AC x Tan[θAC]) x Cos[a] + AC x Sin[a] = (AB x Tan[θAB]) x Sin[a] + AB x Cos[a]
 +
{| class="wikitable centre" width="30%"
 +
|-
 +
! scope=col | a = Atan[((AC / Tan[θAC]) - AB) / ((AB / Tan[θAB]) - AC)]
 +
|-
 +
|}
 +
Le calcul de [xM, yM] est réalisé en permanence. La précision de la position dépendra en grande partie de la précision de la mesure des angles, qui est liée à la technologie de mesure.<br />
 +
Sur la base de ce qui précède, et sans tenir compte pour l’instant des erreurs de mesure, la triangulation est possible en mesurant au moins deux des quatre angles formées par les 4 balises aux 4 coins de la table et la tourelle de réception.
  
Cette section est dédiée à l'utilisation des microcontrôleurs DsPic de chez Microchip.
+
== Détail des applications ==
Ces chips sont majoritairement utilisés dans le système présenté ici. Il est donc nécessaire de poser un certain nombre de bases sur leur utilisation, en particulier leur mise en oeuvre et les drivers disponibles.
+
Le chapitre suivant va décrire plusieurs applications complètes et unitaires à base de microcontrôleur DsPIC33F, qui reprennent toutes les fonctions vues ci-dessus. Chacune de ces applications peut être reprise dans un projet quelconque sans adaptation particulière, en tenant compte toutefois de l’ICD associée à chaque carte.<br />
 +
Les applications suivantes sont présentées :
 +
* Emetteur infrarouge,
 +
* Récepteur infrarouge,
 +
* Lecture sonars,
 +
* Driver moteur pas à pas,
 +
* Driver servomoteur.
 +
Chaque application va utiliser le même type de µC, des DsPic33FJ64MC802, et la même communication en ECAN pour transmettre ou recevoir des données du Blackfin ou de tout autre composant disposant de module CAN.<br />
 +
Les dimensions proposées sont également standardisées, pour permettre l’intégration dans un fond de panier spécifique.
 +
=== Principe de transmission infrarouge ===
 +
Le principal problème lors d’une transmission infrarouge est lié aux perturbations de la lumière environnante. En effet, une émission directe sans codage sera extrêmement perturbé, probablement inutilisable. <br />
 +
Pour éviter ce problème, le signal émis en infrarouge est encodé, selon le principe suivant : <br />
 +
[[File:Code_winchester.jpg]]<br />
 +
Le signal émis transmet une information codé selon le principe du code Winchester, qui transmet les informations avec une donnée de type front (montant / descendant) au lieu de logique (haut / bas).<br />
 +
L’amplitude du signal est très limitée : entre 889µs et 1778µs, au-delà le récepteur est saturé. Les états hauts sont également codés par une porteuse de rapport cyclique ¼, d’amplitude 7µs haut / 22µs bas. <br />
 +
Il est nécessaire de respecter ces contraintes pour pouvoir utiliser un récepteur intégré, qui détecte automatiquement ce type de codage et le transforme en signal logique (0/1).<br />
 +
L’utilisation d’un code winchester présente plusieurs avantages, mais aussi l’inconvénient d’être assez difficile à décoder. Pour simplifier, nous utiliserons (dans un premier temps) un autre moyen de distinguer un signal d’un autre : le rapport cyclique.<br />
 +
Entre 900 µs et 1800 µs, les amplitudes seront retransmises en l’état par le module de réception IR. Dans cette fenêtre, un rapport cyclique de 900 / 1200 pourra être distingué d’un rapport cyclique 900 / 1500 ou 1500 / 1500 etc.<br />
 +
[[File:Rapport_cyclique.jpg]]<br />
 +
Ainsi, il est possible d’émettre avec plusieurs balises d’émission selon les rapports cyclique différents,  qu’un récepteur pourra identifier pour différencier les balises.
  
=== Configuration générale  ===
+
=== Émetteur Infrarouge ===
Les DsPic sont de petits microcontrôleurs 16 bits très puissants que l'on peut utiliser pour tout ou presque :
+
Les cartes d’émission infrarouge sont basées sur le principe ci-dessus. La version présentée ici est déployée de manière autonome dans une balise qui possède sa propre alimentation, ainsi qu’un Dip switch pour la sélection du rapport cyclique.
* Contrôle de moteur par PWM,  
+
==== Étage électrique ====
* Conversion analogique numérique 16 bits (ADC),  
+
La commande de la LED ne peut pas se faire directement par le microcontrôleur. En effet, la puissance nécessaire à l’émission de la LED est trop importante pour le microcontrôleur. Il est donc nécessaire d’ajouter un étage de puissance par l’intermédiaire d’un transistor [type BC547]. <br />
* Acquisition de signaux numériques (Input Capture)
+
[[File:Alimentation_LED_IR.jpg]]<br />
* Communication CAN (natif), i2c (natif), UART (natif), SPI (natif), etc.
+
Il faut rajouter un montage pour deux LED, mais il est possible de tous les commander avec la même sortie du microcontrôleur. Les LED utilisées sont des TSAL 6200 Vishay.<br />
* Une foultitude de timers,
+
Le signal Sig est associé à la variable _RB6 (en sortie), et le Dip switch est connecté aux ports _RB8 à _RB15 (en entrée).<br />
* etc.
+
[[File:DIL_Switch.jpg]]<br />
 +
==== Algorithmique ====
 +
Pour l’émetteur infrarouge, les fonctions suivantes sont utilisées :
 +
* Configuration de l’horloge : Fcy = 40 MHz, configuration 2 (cf tableau xx),
 +
* Initialisation des timers :
 +
** Timer 1 : 20 ms, ainsi qu’un compteur interne de 25 pour générer un LED clignotant à 1 Hz,
 +
** Timer 2 : 7µs
 +
** Timer 3 : 9µs, ainsi qu’un compteur interne de 100 permettant une interruption à 900 µs.
 +
===== Timer 1 interrupt =====
 +
Le timer 4 génère un clignotement de LED à 2 Hz, utilisé comme bit de vie.
 +
: TMR1=0;
 +
: if(count>=25)
 +
: {
 +
:: _RB7 = !_RB7;
 +
:: count = 0;
 +
: }
 +
: else
 +
: {
 +
:: count = count + 1;
 +
: }
 +
: IFS0bits.T1IF=0;
 +
La variable count est globale, utilisée seulement ici.<br />
 +
Pour rappel, l’opérateur « ! » indique l’inverse d’une variable logique (ie son complément à 1). Ainsi, la fonction _RB7 = !_RB7 signifie que la variable _RB7 (port _RB7 utilisé comme E/S directe, sans passer par le registre PORTB) est mise à son complémentaire à 1 (0 si elle valait 1, 1 sinon).
  
Il existe plusieurs familles de DsPIC : 30F, 32, 33F, 33E. Seuls les 33F sont détaillés ici.<br />
+
===== Timer 2 interrupt =====
Dans une même famille, tous les microcontrôleurs utilisent les drivers de la même façon, avec le même code, sous réserve qu'ils disposent du driver. En effet, dans la famille des 33F il va exister pléthore de chips différents, la différence se situant au niveau du nombre de drivers, d'E/S, de timers, de place mémoire, etc.<br />
+
Le timer 2 génère la porteuse du signal infrarouge. Il déclenche une interruption toutes les 7 µs, qui est ensuite associée à un compteur à 4 pas. Une fois sur quatre, l’interruption positionne le bit_6 (commande des LED) à « x » (1 ou 0 selon l’état haut ou bas du code winchester), et trois fois sur quatre le bit_6 reste à 0. La valeur de x est générée par le timer 3.
Dans la suite, les explications seront apportées sur un DsPIC33FJ128MC802 ou un DsPIC33FJ64GP802, dont la principale différence est un PWM pour le premier.
+
: TMR2=0;
La programmation peut se faire en C ou en assembleur. Les exemples proposés sont en C, je reviendrais par la suite sur la manière de les programmer.<br />
+
: if(count_porteuse == 1)
 +
: {
 +
:: _RB6 = data_out;
 +
:: count_porteuse = count_porteuse + 1;
 +
: }
 +
: else
 +
: {
 +
:: _RB6 = 0;
 +
:: if(count_porteuse >= 4)
 +
:: {
 +
::: count_porteuse = 1;
 +
:: }
 +
:: else
 +
:: {
 +
::: count_porteuse = count_porteuse + 1;
 +
:: }
 +
: }
 +
: IFS0bits.T2IF=0;
 +
La variable count_porteuse est globale, utilisée uniquement ici.<br />
 +
La variable data_out est globale, utilisée ici en lecture et dans timer 3 en écriture.
 +
===== Timer 3 interrupt =====
 +
Le timer 3 va générer l’impulsion entre 900 et 1800 µs. Pour cela, une interruption toutes les 9µs ainsi qu’un compteur >ADU<
 +
: TMR3=0;
 +
: if(elapse >= pulse)
 +
: {
 +
:: data_out = !data_out;
 +
:: elapse = 0;
 +
:: data_in = (_RB4*2) + _RA4;
 +
:: switch (data_in)
 +
:: {
 +
::: case 0 :
 +
:::: long_bit = 100;
 +
:::: short_bit = 200;
 +
::: break;
 +
::: case 1 :
 +
:::: long_bit = 100;
 +
:::: short_bit = 100;
 +
::: break;
 +
::: case 2 :
 +
:::: long_bit = 200;
 +
:::: short_bit = 100;
 +
::: break;
 +
::: case 3 :
 +
:::: long_bit = 150;
 +
:::: short_bit = 150;
 +
::: break;
 +
:: }
 +
:: if(data_out == 1)
 +
:: {
 +
::: pulse = long_bit;
 +
:: }
 +
:: else
 +
:: {
 +
::: pulse = short_bit;
 +
:: }
 +
: }
 +
: else
 +
: {
 +
:: elapse = elapse + 1;
 +
: }
 +
: IFS0bits.T3IF=0;
 +
===== Initialisation du signal =====
 +
ADU
 +
=== Récepteur infrarouge ===
 +
Le récepteur infrarouge est calibré pour lire les signaux générés conformément à la figure [Principe de transmission infrarouge]. Il utilise principalement les détecteurs TSOP48 de Vishay (mais il en existe une foultitude) qui réalisent directement l’intégration de la porteuse pour filtrer les perturbations lumineuses. La longueur du signal détecté n’est pas exactement celle qui est émise. En effet, la commutation des fronts est réalisée lorsque plusieurs occurrences de porteuse sont détectées (ou qu’une absence de codeuse est détectée).
 +
==== Étage électrique ====
 +
La sortie « signal » du récepteur infrarouge est directement lisible par le µC, sans étage d’adaptation. Les 4 signaux sont câblés sur 4 ports du µC réglés en entrées (ie _RBx = 1). Le récepteur infrarouge utilise en plus l’interruption INT1 comme entrée de codeuse, sur laquelle un signal carré indiquera à chaque front montant l’avancement d’un pas du moteur pas à pas (ou d’une codeuse quelconque). L’entrée est réalisée sur le port _RB8. Une seule entrée codeuse est nécessaire ici, pour cette application les 4 récepteurs sont associés à la même motorisation.
 +
==== Algorithmique ====
 +
Le principe de détection est décrit dans le schéma ci-dessous :<br />
 +
[[File:Detection_rapport_cyclique.jpg]]<br />
 +
<span style="color: red;">'''Nota : Attention : si le ratio détecté est 4/3, c’est que la balise émet en ¾ (car inversion du signal par le récepteur).'''</span><br />
 +
Deux fonctions sont nécessaires :
 +
* Détecter que l’on reçoit un message d’une balise,
 +
* Identifier la balise.
 +
Pour la première fonction, il est possible d’utiliser la limite de saturation du récepteur comme indication. En effet, puisqu’aucun signal ne peut transmettre des états logiques hauts supérieurs à 2 ms (en tenant compte d’une marge d’erreur, au lieu de 1778µs), si aucun changement d’état à l’entrée du détecteur n’est détecté pendant un temps supérieur à 2 ms (mettons 3 ms pour avoir une bonne marge), c’est qu’aucune balise n’est en vue. Dès lors, la limite de rollover du timer est fixée à 3 ms :
 +
: TMRiCON = 0x0010 ;
 +
: PMRi = 15000 ;
 +
Lors d’un rollover du timer, une interruption sera générée qui permettra un traitement particulier.<br />
 +
L’input capture (IC) est paramétré pour détecter chaque front, montant ou descendant. En effet, il est nécessaire de mesurer la durée des fronts haut et bas pour en déduire le rapport cyclique.<br />
 +
A la détection d’un front montant ou descendant, une interruption IC est déclenchée et le contenu du timer (associé à l’input capture, le même que celui paramétré précédemment) est transmis au module IC. Si ce temps est le temps depuis la dernière remise à 0 du timer, réalisée au dernier front détecté, la valeur du timer est la durée du front.<br />
 +
Si le front est bas à l’interruption, alors la valeur du timer est la durée du front haut, sinon celle du front bas.<br />
 +
Une limitation de la fonction IC n’autorise l’utilisation que des timers 2 ou 3 pour la recopie de la valeur (cf Interruption Input Capture]). Pour les IC 7 et 8 (le chip dispose de 4 IC : 1, 2, 7 et 8), on utilisera les timers 4 et 5, recopiés manuellement dans la fonction d’interruption puis remis à 0.
 +
: if(_RB10 == 0)
 +
: {
 +
:: count_IR[2][0] = TMR5; // Step 1
 +
:: TMR5 = 0;
 +
: }
 +
: else
 +
: {
 +
:: count_IR[2][1] = TMR5; // Step 0
 +
:: TMR5 = 0;
 +
:: […]
 +
: }
 +
Dès lors qu’une interruption IC est activée, le récepteur détecte une balise. La valeur de rotation de la tourelle est relevée à la première interruption IC après une interruption du timer associé (cf schéma ci-dessus, recopie de l’entrée codeuse) et le timer remis à 0 dans tous les cas.<br />  
 +
Lorsque une interruption Timer est levée à son tour, c’est qu’aucune balise n’est en vue (pas de signal cohérent détecté). La valeur de la tourelle est relevée (entrée codeuse) à la première interruption timer après une interruption IC associée, indiquant la sortie de la fenêtre de détection. Cette fenêtre sera explicitée plus bas.
 +
===== Chaîne de détection =====
 +
Quatre chaines de détections sont disponibles dans l’application traitée ici. Chaque chaine dispose de fonctions spécifiques et de variables spécifiques associées à sa détection. De manière générale, les variables sont des tableaux de variables dont chaque variable est associée à une chaine.
 +
Une chaine de détection est composée :
 +
* Du timer supervisant le rollover et cadencé pour la mesure du rapport cyclique,
 +
* D’un Input Capture associé mesurant le rapport cyclique,
 +
* D’une variable int detection_en_cours[4] qui indique, pour chaque chaine, si une balise est en vue ou non,
 +
* D’une variable unsigned int cds_detect[4] qui est la valeur de la codeuse associée à la chaine,
 +
* D’une variable int  count_IR[4][2], qui est la variable contenant la longueur des fronts hauts et bas mesurés ([2] => [0] pour le front  bas, [1] pour le front haut).
 +
En plus de ces fonctions et variables, quelques fonctions génériques et variables globales sont définies :
 +
* int calculate_angle(int count_a, int count_b), fonction de d’identification d’une balise en fonction de la longueur des fronts hauts et bas,
 +
* traite_IR : si cette variable est à 1, cela signifie qu’une balise a été vue par l’une des chaines et qu’elle doit être identifiée,
 +
* current_IR : indique quelle est la chaine qui a détecté une balise, pour son identification,
 +
Enfin, diverses variables seront utilisées localement pour la réalisation de fonctions particulières (notamment mémorisation de variables), elles seront explicitées au cas par cas.
 +
===== Timer 1 interrupt =====
 +
La fonction est identique à celle de l’émetteur infrarouge.
 +
===== Configuration d'une chaîne de détection =====
 +
Les quatre chaines de détection sont identiques, avec la répartition des fonctions suivantes :
 +
: Chaine 1 => Timer 2 => IC1
 +
: Chaine 2 => Timer 3 => IC2
 +
: Chaine 3 => Timer 4 => IC7
 +
: Chaine 4 => Timer 5 => IC8
 +
Dans la suite de ce chapitre, les références i (n° de chaine), j (numéro de timer) et k (numéro d’input capture) seront utilisées pour présenter la configuration des 4 chaines en même temps.
  
=== Schéma de câblage ===
+
<u>Initialisation du timer j :</u>
Tous les microcontrôleurs de la famille des DsPIC33FJ sont câblés pareils :
+
>> Schéma de câblage chip <<
+
  
Le connecteur J1 est utilisé pour la programmation du DsPic, en utilisant un ICD2 ou un Pickit 3.<br />
+
Le timer j est initialisé pour générer une interruption toutes les 3 ms, indiquant un rollover (cf plus haut), et s’incrémente toutes les 0,2 µs.
# !MCLR
+
: TiCON = 0x0010 ;
# +Vcc
+
: PMRi = 15000 ;
# GND
+
<u>Initialisation de l’input capture k :</u>
# PGEC1
+
# PGED1
+
  
Les ports suivants sont nécessairement câblés pour pouvoir utiliser le chip, conformément à la figure précédente :<br />
+
Les quatre IC s’initialisent dans la même fonction :
* '''!MCLR''' : La patte sert à la fois à la programmation et à la mise en marche du Chip : !MCLR = 0/NC - Chip inactif, !MCLR = VDD : Chip actif.<br />
+
: ICkCON = 0x0081 ; => pour k = 1, on recopie timer 2
* '''PGEDi/PGECi''' : broches de programmation, elles peuvent aussi avoir d'autres application lors de l'usage du chip. Il y a plusieurs mappages des PGEC/D possibles, j'utilise ici le premier.<br />
+
: ICkCON = 0x0001 ; => pour k = 2,3 et 4, on recopie timer 3.
* '''VSS/AVSS''' : ground, 0 logique. La broche "A-" est normalement la branche utilisée pour faire passer de la puissance dans le chip, mais en règle général il vaut mieux tout avoir sur la même source et utiliser ensuite des composant fait pour pour les aspects puissance.<br />
+
La configuration des ports se fait par les deux registres RPINR7 et RPINR10. Chacun de ces mots contrôle un port par registre (MSB / LSB) :
* '''OSC1 / OSC2''' : entrées du quartz. Voir le chapitre horloge pour le détail d'utilisation.<br />
+
: RPINR7 = 0x0B0A ; => L’IC1 est associée à la broche RP10 (10 = 0x0A), l’IC2 à la broche RP11 (0x0B)
* '''VDD/AVDD''' : 3,3V (+/- 10%)<br />
+
: RPINR10 = 0x0D0C ; => L’IC7 est associée à la broche RP12 (0x0C), l’IC8 à la broche RP13 (0x0D)  
* '''VCAP''' : Cette entrée doit être reliée à la masse par un condo de 10µF, l'idéal étant un tantale polarisé (CMS ou non d'ailleurs). Sans ce condo, le chip ne marchera pas et ne pourra même pas être programmé.<br />
+
<u>Initialisation de l’interruption externe :</u>
  
=== Horloge ===
+
L’interruption externe réalise le comptage pour les chips :
Les DsPIC peuvent être cadencés assez haut, mais ils utilisent  pour cela une PLL interne (Phase Lock Loop).
+
: RPINR0 = 0x0900; => La broche d’entrée pour les interruptions externes est la broche RP9
Le principe de la PLL est détaillé sur internet, nous ne verrons ici que l'application.
+
: _INT1EP = 0; => Les interruptions sont sur front montant.
Trois types d'horloges sont utilisées :  
+
<u>Initialisation des ports :</u>
* Quartz,
+
* Oscillateur,
+
* Horloge interne.
+
De manière générale, il est déconseillé d'utiliser des horloges trop rapides. En effet, les signaux "carrés" sont plus mauvais à ces fréquences. Une bonne pratique est de dédier un quartz de fréquence raisonnable (8 ou 10 MHz) à chaque chip, et d'en augmenter la fréquence par une PLL.
+
  
==== Utilisation d'un quartz ====
+
Les broches associées à l’Input Capture et à l’interruption externe sont contrôlées directement par les drivers. Ils ne sont donc pas concernés par la direction des ports.
Les quartz sont des cristaux passifs que le microcontrôleur utilise pour générer une horloge. Le quartz est relié aux broches OSC1 et OSC2 du microcontrôleur. Les quartz utilisables sont de deux types, en fonction de leur vitesse d'horloge :<br />
+
===== Les interruptions =====
* XT : quartz jusqu'à 10 MHz,
+
Comme la plupart des applications, le récepteur IR fonctionne principalement avec les interruptions. L’algorithme de détection, pour chaque chaine, est le suivant :
* HS : quartz de 10 MHz à 40 MHz.
+
# Détection d’un changement d’état sur l’input capture alors qu’aucune détection n’est en cours (detection_en_cours[i] == 0),
 +
# Relevé de la position de la tourelle (ie valeur de la codeuse tourelle),
 +
# Identification du rapport cyclique (ie de la balise émettrice),
 +
# Détection d’une fin de détection (ie rollover du timer associé à la chaine) si une détection est en cours (ie detection_en_cours[i] == 1),
 +
# Relevé de la position de la tourelle (ie valeur de la codeuse tourelle),
 +
# Calcul de la position milieu de la tourelle dans sa fenêtre de détection,  
 +
# Transmission de cette position ainsi que du numéro de balise au BF via le réseau CAN.
 +
Chacune de ces étapes sera détaillée ci-dessous au travers de trois fonctions, associées aux chaines vues précédemment. Chaque chaine aura un timer et un input capture dédié, la fonction d’identification du rapport cyclique étant générique et appelée automatiquement dès lors qu’un récepteur est en cours de détection.
 +
 +
<u>Interruption Input capture</u>
  
Chaque broche OSC1 et OSC2 est également relié à la masse (GND), par l'intermédiaire d'un condo de 15 µF. A noter que ça marche aussi sans le condo, mais ils sont recommandés dans la doc du chip.<br />
+
L’interruption input capture va réaliser la détection d’un changement d’état (fonction nominale de l’input capture) et recopier la valeur courante de la codeuse si il s’agit du premier changement d’état depuis le rollover.
Dans : '''MPLAB''' : Configure\configuration bits...<br />
+
: void __attribute__((interrupt, no_auto_psv)) _ICkInterrupt(void)
* Oscillator mode : Primary oscillator (XT, HS, EC) w/ PLL
+
: {
* Internal External switch over mode : Start up device with FRC, then automatically switch to bla bla bla...
+
:: if(_RBn == 0)
* Primary oscillator source : HS Oscillator mode (pour cause de quartz utilisé à 10 MHz)
+
::: // Si le récepteur est à l’état bas
''Nota'' : les quartz étant passifs, ils sont dédiés à un seul chip et unique chip, on ne peut pas utiliser un quartz pour plusieurs chips.
+
:: {
 +
::: count_IR[i][0] = IC1BUF;
 +
:::: // Durée du pulse haut
 +
::: TMRj = 0;
 +
:::: // Réinit du timer pour une nouvelle mesure
 +
:: }
 +
:: Else
 +
::: // Si le récepteur est à l’état haut
 +
:: {
 +
::: count_IR[i][1] = IC1BUF;
 +
:::: // Durée du pulse bas
 +
::: TMRj = 0;
 +
:::: // Réinit du timer pour une nouvelle mesure
 +
::: traite_IR = 1;
 +
:::: // Déclenche la fonction d’identification
 +
::: current_IR = i;
 +
:::: // Chaine à identifier
 +
:: }
  
==== Utilisation d'un oscillateur ====
+
A  noter, pour l’input capture 1 on peut utiliser l’IC1BUF, qui est rempli automatiquement avec la valeur du timer 2 lors de la détection. Pour les autres chaines, il faudra recopier la valeur du timer associé : '''count_IR[i][0] = TMRj;'''
ADU
+
==== Utilisation de l'horloge interne ====
+
ADU
+
  
==== Configuration de l'horloge ====
+
Afin de relever le début de la fenêtre de détection, une variable est testée à chaque interruption. Seule une interruption timer pourra remettre l’état à 0 et ainsi déclencher la réaction associée. Une fois une balise en vue, et tant qu’elle le reste (ie les interruptions IC à moins de 3 ms), la valeur de la codeuse n’est plus relevée :
Ci-dessous un bout de code permettant de paramétrer la PLL au démarrage du chip :
+
: if(detection_en_cours[i] == 0)
: void oscConfig(void){
+
: {
:: PLLFBD=30; // M=32
+
:: detection_en_cours[i] = 1;
:: CLKDIVbits.PLLPOST=0;              // N1=2
+
:: cds_detect[i] = codeuse;
:: CLKDIVbits.PLLPRE=0;              // N2=2
+
:: OSCTUN=0;                          // Tune FRC oscillator, if FRC is used
+
:: RCONbits.SWDTEN=0;                // Disable Watch Dog Timer
+
:: __builtin_write_OSCCONH(0x03);    // Initiate Clock Switch to Primary Oscillator with PLL (NOSC=0b011)
+
:: __builtin_write_OSCCONL(0x01);    // Start clock switching
+
:: while (OSCCONbits.COSC != 0b011); // Wait for Clock switch to occur
+
:: while(OSCCONbits.LOCK!=1) {};     // Wait for PLL to lock
+
 
: }
 
: }
 +
: _IC1IF = 0;
 +
}
 +
 +
<u>Interruption timer</u>
  
Ce code est prévu pour un quartz de 10 MHz, permettant de passer la fréquence de cycle du microcontrôleur à 40 MHz (une opération toutes les 1 / [40.E6], soit 25 ns).
+
L’interruption timer intervient lors d’un rollover du compteur timer, soit au bout de 3 ms sans détection. Cela correspond à une zone sans balise en vue.  
  
=== Configuration des ports d'entrées / sorties ===
+
Donc dès lors que cette interruption intervient lorsque la variable detection_en_cours[i] est positionnée à 1, elle indique la sortie de la zone de détection.
La configuration des ports d’E/S est réalisée de base par les trois registres suivants :
+
: void __attribute__((interrupt, no_auto_psv)) _TjInterrupt (void)
* '''AD1PCFGL'''' : Ports analogiques, à positionner à [0xffff] pour les désactiver,
+
: {
* '''_TRISBx / _TRISAx''' : Direction de la broche RBx / RAx :
+
:: TMRj=0;
** 1 : broche en entrée,
+
:: if(detection_en_cours[i] == 1)
** 0 : broche en sortie.
+
:: {
* '''_RBx / _RAx''' : Etat de la broche :
+
Un étage de filtrage des mauvaises détections est ajouté dans la procédure. Cet étage est géré dans la boucle principale, il sera détaillé par la suite. Memory_detecteur[i] n’est supérieur à 0 qu’en cas de détection correcte.
** 1 : broche à VDD (+3,3V),
+
:: if(memory_detecteur[i]>0)
** 0 : broche à VSS (GND).
+
:: {
 
+
Il est nécessaire de tenir compte du sens de la codeuse, pour éviter d’envoyer des angles négatifs au BF :
''Nota'' : Lorsque l’on passe plusieurs broches à l’état logique 1 l’une à la suite de l’autre, notamment avec des très hautes fréquences, il est possible que le condensateur qui sert à modifier l’état de la broche ne puisse pas suivre et alimenter toutes les broches. Dans ce cas, il faut séparer les opérations de changement d’état de plusieurs cycles (au moins deux ou trois) pour permettre au condo de recharger.
+
::: if(cds_detect[i] >= codeuse)
 
+
::: {
En plus des manipulations standards, il existe deux autres opérations associées à l’utilisation des drivers : redirection des ports d’entrée ou de sortie : '''_RPxR''' (output) et '''RPINRx''' (input).
+
:::: cds_detect[i] = cds_detect[i] - (cds_detect[i] - codeuse)/2;
* '''_RPxR''' : les _RPxR sont des registres associés à **ADU**
+
::: }
* '''RPINRx''' : la plupart des drivers ont leurs entrées redirigeables vers l’une ou l’autre des broches RPx du chip. Pour chaque driver, il faut trouver le bon RPINRx et indiquer dans ce registre le numéro de la broche à laquelle associer l’entrée.
+
::: else
 
+
::: {
Pour chaque driver, les redirections de port seront précisées au cas par cas.
+
:::: cds_detect[i] = cds_detect[i] + (codeuse - cds_detect[i])/2;
=== Les timers ===
+
::: }
Les timers sont probablement les fonctions les plus utiles d’un microcontrôleur. Ils permettent de réaliser des opérations avec une fréquence particulière, avec des actions spécifiques à chaque révolution du timer. Le principe est qu’un registre va être incrémenté régulièrement, proportionnellement à la fréquence Fcy, jusqu’à une certaine valeur. Lorsque cette valeur est atteinte, une interruption est déclenchée qui permet de réaliser l’action voulue à la révolution du timer.<br />
+
Dès lors que la fenêtre de détection est identifiée, puis que son centre est calculé, les données sont transmises au BF (confer ICD pour trouver le message associé).
Les DsPIC disposent de plus ou moins de timer selon les séries, celui utilisé ici dispose de 5 timers.<br />
+
::: canTxMessage.buffer=0;
Certains timers sont utilisés par des fonctions (comme les timers 2 et 3 avec l’Input Capture, définit plus loin), ou peuvent être combinés pour former des timers 32 bits (de révolution très largement supérieure). Les applications présentées ici utiliseront plusieurs de ces fonctions, mais sans être exhaustif.
+
::: canTxMessage.id=220;
==== Principe ====
+
::: canTxMessage.data[0] = memory_detecteur[i];
Les timers fonctionnent selon le principe ci-dessous :  
+
::: canTxMessage.data[1] = GET_LOW_BYTE(cds_detect[i]);
<<Schéma Timer à ajouter>>
+
::: canTxMessage.data[2] = GET_HIGH_BYTE(cds_detect[i]);
==== Paramétrage du timer ====
+
::: canTxMessage.data_length=3;
Le code ci-dessous permet de paramétrer un timer pour avoir une interruption cadencée à la fréquence voulue :<br />
+
::: send_can = 1;
: void init_timer1(void)  
+
::: }
 +
:: }
 +
::: // On reset toutes les variables de la chaine
 +
:: memory_detecteur[i] = 0;
 +
:: detecteur[i] = 0;
 +
:: detection_en_cours[i] = 0;
 +
:: IFS0bits.TkIF=0;
 +
: }
 +
Le traitement complet n’est réalisé que lorsqu’une fenêtre de détection est en cours.
 +
<u>Identification d’une balise</u>
 +
L’identification d’une balise est une fonction générique appelée dans la boucle principale. La fonction main réalise les initialisations de fonctions :
 +
: oscConfig();
 +
: init_pic();
 +
:: /****/
 +
: IFS0=0;
 +
: IFS1=0;
 +
: IFS2=0;
 +
: IFS3=0;
 +
: IFS4=0;
 +
:: /****/
 +
: timer1 = 1;
 +
: timer2 = 0;
 +
: timer3 = 1;
 +
: timer4 = 0;
 +
: timer5 = 1;
 +
Ainsi que l’initialisation de la communication CAN :
 +
:: /* Enable ECAN1 Interrupt */
 +
: IEC2bits.C1IE=1;
 +
:: /* enable Transmit interrupt */
 +
: C1INTEbits.TBIE=1;
 +
:: /* Enable Receive interrupt */
 +
: C1INTEbits.RBIE=1;
 +
:: /* Enable Error interrupt */
 +
: C1INTEbits.ERRIE = 1;
 +
: canTxMessage.message_type=CAN_MSG_DATA;
 +
: canTxMessage.frame_type=CAN_FRAME_STD;
 +
Avant de rentrer dans la boucle principale while(1) :  
 +
: if(send_can == 1)
 
: {
 
: {
:: _T1IE=0; // interruption timer
+
:: send_can = 0;
:: _T1IF=0; // flag d’interruption remis à zéro
+
:: sendECAN(&canTxMessage);
:: T1CON=0x0020;  
+
: }
::: /* configuration du registre de controle :
+
Si une demande de communication est réalisée par l’un des chaines, la structure remplie est transmise. Plusieurs demandes concourantes s’écrasent, mais la rapidité du système évite (en règle générale) la perte d’information.
::: Le bit de poids fort <Bit_15> commande le démarrage du timer,
+
: if(traite_IR == 1)
::: Les bits <5-4> permettent de ralentir la vitesse du timer, en divisant la fréquence de mise à jour du timer par une valeur spécifique (ce que l’on appelle un prescaler. Ainsi, au lieu d’être incrémenté à chaque coup d’horloge (Fcy), le compteur du timer est incrémenté tous les 8, 64 ou 256 coups :
+
: {
:::* <5-4> = 0 ; // A chaque coup d’horloge
+
:: traite_IR = 0;
:::* <5-4> = 1 ; // tous les 8 coups
+
:: switch(current_IR)
:::* <5-4> = 2 ; // tous les 64 coups
+
:: {
:::* <5-4> = 3 ; // tous les 256 coups */
+
Un switch est réalisé pour exécuter un code spécifique à chaque chaîne <<ADU : Optimiser avec une variable et un seul traitement>>
:: PR1= <<valeur>>;
+
::: case 1 :
::: // Lorsque le compteur du timer atteint cette valeur, une interruption est générée et le timer remis à 0
+
:::: detecteur[0] = calculate_angle(count_IR[0][0], count_IR[0][1]);
:: T1CONbits.TON=1; // démarrage du timer
+
:::: if (detecteur[0] > 0)
:}
+
:::: {
 
+
::::: memory_detecteur[0] = detecteur[0];
Pour faciliter la manipulation du timer, on définit en général la donnée suivante :  
+
:::: }
: ''' #define timer1 IEC0bits.T1IE'''
+
::: break;
Avec 1 pour le timer 1, 2 pour le timer 2 et ainsi de suite. Cela permet au microcontrôleur de générer les interruptions, ce qu’il ne fait pas sinon.
+
::: case 2 :
 +
:::: detecteur[1] = calculate_angle(count_IR[1][0], count_IR[1][1]);
 +
::: break;
 +
::: case 3 :
 +
:::: detecteur[2] = calculate_angle(count_IR[2][0], count_IR[2][1]);
 +
:::: if (detecteur[2] > 0)
 +
:::: {
 +
::::: memory_detecteur[2] = detecteur[2];
 +
:::: }
 +
::: break;
 +
::: case 4 :
 +
:::: detecteur[3] = calculate_angle(count_IR[3][0], count_IR[3][1]);
 +
::: break;
 +
:: }
 +
: }
 +
La fonction calculate_angle est détaillée ci-dessous, c’est elle qui identifie les balises en fonction des rapports cycliques (count_IR[i][0] pour l’état haut et count_IR[i][1] pour l’état bas). Pour rappel, le sens de détection est l’inverse de celui d’émission (le récepteur IR fonctionne en logique inversée).
 +
 +
: int calculate_angle(int count_a, int count_b)
 +
: {
 +
On gère ici les différents cas possibles en fonction des mesures. La longueur de l’intervalle peut être allongée pour augmenter le nombre de balises détectables, au détriment de la précision (on augmente en effet le risque de déborder d’une durée caractéristique si cette dernière est trop courte).
 +
:: if((count_a < 5000) && (count_a > 4000)) //[800 - 1000] µs
 +
:: {
 +
::: if((count_b < 5000) && (count_b > 4000)) //[800 - 1000] µs
 +
::: {
 +
::::: // Balise B
 +
:::: return 2;
 +
::: }
 +
::: else if((count_b < 9500) && (count_b > 8500)) //[1700 - 1900] µs
 +
::: {
 +
::::: // Balise C
 +
:::: return 3;
 +
::: }
 +
::: else
 +
::: {
 +
::::: // -1 est le code erreur, la balise détectée est inconnue
 +
:::: return -1;
 +
::: }
 +
:: }
 +
:: else if((count_a < 9500) && (count_a > 8500)) //[1700 - 1900] µs
 +
:: {
 +
::: if((count_b < 5000) && (count_b > 4000)) //[800 - 1000] µs
 +
::: {
 +
::::: // Balise A
 +
:::: return 1;
 +
::: }
 +
::: else
 +
::: {
 +
:::: return -1;
 +
::: }
 +
:: }
 +
:: else if((count_a < 7250) && (count_a > 6250)) //[1250 - 1450] µs
 +
:: {
 +
::: if((count_b < 7250) && (count_b > 6250)) //[1250 - 1450] µs
 +
::: {
 +
::::: // Balise D
 +
:::: return 4;
 +
::: }
 +
::: else
 +
::: {
 +
:::: return -1;
 +
::: }
 +
:: }
 +
:: else
 +
:: {
 +
::: return -1;
 +
:: }
 +
: }
  
''Nota 1'' : même si les interruptions sont désactivées, le timer fonctionne si T1CONbits.TON=1.
+
Il est nécessaire de recalculer (finement) toutes les valeurs de test si l’on change les types de balises (ie leur rapport cyclique). Le tableau ci-dessous résume la configuration présente (40 MHz) :
''Nota 2'' : vérifier que le registre IEC0 contient bien le bit du timer associé. En particulier, pour les timer 4 et 5 il faut chercher sur le registre IEC1 :
+
{| class="wikitable centre" width="30%"
: ''' #define timer4 IEC1bits.T4IE'''
+
A titre informatif, le tableau ci-dessous donne quelques réglages de base pour générer des interruptions aux périodes indiquées, sur la base de réglage de la PLL et du quartz utilisé :
+
{| class="wikitable centre" width="80%"
+
|+ Tableau
+
 
|-
 
|-
! scope=col |
+
! scope=col colspan="2" | Fréquence
! scope=col | Config 1
+
! scope=col | 40 MHz
! scope=col | Config 2
+
! scope=col | 5 MHz
! scope=col | Config 3
+
! scope=col | Config 4
+
 
|-
 
|-
| width="5%" |
+
! scope=col colspan="2" | Période
Horloge
+
! scope=col | 25 ns
 +
! scope=col | 200 ns
 +
|-
 +
| align="center" width="20%" colspan="2" |
 +
Période du timer (µs)
 
| align="center" width="10%" |
 
| align="center" width="10%" |
10 MHz
+
0,2
 
| align="center" width="10%" |
 
| align="center" width="10%" |
10 MHz
+
1,6
 +
|-
 +
| align="center" width="10%" rowspan="2" |
 +
900
 
| align="center" width="10%" |
 
| align="center" width="10%" |
10 MHz
+
800
 
| align="center" width="10%" |
 
| align="center" width="10%" |
10 MHz
+
4000
|-
+
| width="5%" |
+
M
+
 
| align="center" width="10%" |
 
| align="center" width="10%" |
38
+
500
 +
|-
 
| align="center" width="10%" |
 
| align="center" width="10%" |
30
+
1000
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
5000
 
| align="center" width="10%" |
 
| align="center" width="10%" |
10
+
625
 
|-
 
|-
| width="5%" |
+
| align="center" width="10%" rowspan="2" |
N1
+
1350
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
1250
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
6250
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
781,25
| align="center" width="10%" |
+
0
+
 
|-
 
|-
| width="5%" |
 
N2
 
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
1450
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
7250
 
| align="center" width="10%" |
 
| align="center" width="10%" |
0
+
906,25
| align="center" width="10%" |
+
0
+
 
|-
 
|-
| width="5%" |
+
| align="center" width="10%" rowspan="2" |
FCAN
+
1800
| align="center" width="10%" |
+
50 MHz
+
 
| align="center" width="10%" |
 
| align="center" width="10%" |
40 MHz
+
1700
 
| align="center" width="10%" |
 
| align="center" width="10%" |
5 MHz
+
8500
 
| align="center" width="10%" |
 
| align="center" width="10%" |
15 MHz
+
1062,5
 
|-
 
|-
| width="5%" |
 
Période
 
 
| align="center" width="10%" |
 
| align="center" width="10%" |
20 ns
+
1900
 
| align="center" width="10%" |
 
| align="center" width="10%" |
25 ns
+
9500
 
| align="center" width="10%" |
 
| align="center" width="10%" |
200 ns
+
1187,5
| align="center" width="10%" |
+
66 ns
+
 
|}
 
|}
==== Interruption timer ====
 
Une interruption timer est définie dans la routine suivante :
 
: void __attribute__((interrupt, no_auto_psv)) _T1Interrupt (void)
 
: {
 
:: TMR1=0; // remise à 0 du compteur
 
:: [[Ajouter le code ici]]
 
:: IFS0bits.T1IF=0; // remise à 0 du drapeau d’interruption du timer
 
: }
 
''Nota 1'' : le code ne doit pas être trop long, sinon son exécution peut masquer la survenue d’autres interruptions (voir priorité des interruptions),
 
''Nota 2'' : TMR4 et TMR5 sont sur IFS1.
 
  
==== Cas d'utilisations des timers ====
+
=== Driver moteur pas à pas ===
Les timers sont utilisés dans les applications qui suivent dans plusieurs cas :
+
* Génération de la commande d’un servomoteur (pulse d’une milliseconde sur une période de 20ms),
+
* Génération d’un signal pour LED infrarouge conforme protocole IRDa,
+
* Commande d’un moteur pas à pas avec une carte de commande,
+
* Etc.
+
Ces différents cas seront détaillés dans la description complète des fonctions, au niveau des applications elles-mêmes.
+
=== Input Capture et interruptions externes ===
+
Il existe deux fonctions assez similaires qui réagissent à un changement d’état d’une entrée du microcontrôleur en générant une interruption. Ces fonctions sont :
+
* Input Capture, permettant via un couplage aux timer de mesurer précisément le temps écoulé entre deux changements d’état,
+
* Interruptions externes : principalement utilisées pour du comptage d’occurrence de changement d’état.
+
==== Paramétrage de l'Input Capture ====
+
La fonction d’Input Capture (IC) est très utile pour la mesure précise d’une période entre deux changements d’états d’une entrée, sur le même front ou sur des fronts différents. Il est également possible d’utiliser, comme pour les timers, un prescaler générant une interruption au bout de la 4eme, 16eme, etc. survenue du changement d’état attendu.
+
: void init_input_capture(void)
+
: {
+
:: IC1CON = 0x0001;
+
:: IC7CON = 0x0001;
+
:: RPINR7 = 0x000B;
+
:: RPINR10 = 0x000A;
+
:: _IC1IF = 0;
+
:: _IC1IE = 1;
+
:: _IC7IF = 0;
+
:: _IC7IE = 1;
+
:}
+
Le registre ICxCON permet le paramétrage du module d’input capture. Dans notre cas, seuls les paramètres suivants sont intéressants :
+
* Bit 7 – ICTMR : lorsque l’interruption survient, le contenu d’un timer est recopié dans un registre associé à l’input capture. Si ICTMR = 1, c’est le contenu du timer 2 (ie TMR2) qui est recopié dans le registre, sinon c’est le contenu du timer 3 (ie TMR3),
+
* Bit <6 – 5> - ICI<1:0> : décale la génération de l’interruption de 1 à 4 survenue de l’évènement scruté. Par exemple, si ce paramètre vaut 3, et que l’IC est programmée pour détecter tous les fronts montants sur une broche, l’interruption sera générée au troisième front montant et non à chaque fois,
+
* Bit <2 - 0> - ICM<2:0> : précise l’évènement scruté. Un paramètre à 0 désactive l’IC. Un paramètre à 1 génèrera une interruption à chaque front montant ou descendant. Dans ce mode le décalage de l’ICI<1:0> ne fonctionne pas. Un paramètre à 2 ou plus entrainera une interruption, après décalage de ICI<1:0>, sur chaque front montant, chaque front descendant, tous les 4, 16 front montants, etc. se reporter au datasheet du composant.<br />
+
Dans le paramétrage de l’exemple, l’IC va générer une interruption à chaque front montant ou descendant.<br />
+
Il faut ensuite associer à chaque IC une broche, et on utilise pour ça le registre RPINR vu précédemment. Dans l’exemple, le RPINRx de l’IC1 est RPINR7 octet de poids faible, auquel on a associé la valeur 11 (0xB). Ainsi, l’entrée de l’IC1 est la broche RP11 (ie RB11).<br />
+
Enfin, de la même manière que pour les timers, il est nécessaire d’activer les interruptions sur l’IC :
+
* _IC1IE = 1;
+
Après s’être assuré au préalable que l’interruption n’est pas en cours, sans quoi le code ne sera jamais appelé :
+
* _IC1IF = 0;
+
==== Interruption Input Capture ====
+
Une interruption d’input capture est définie dans la routine suivante :
+
: void __attribute__((interrupt, no_auto_psv)) _IC1Interrupt(void)
+
: {
+
:: Value = IC1BUF;
+
::: // Ajouter TMR3 = 0 ou TMR2 = 0 si il est nécessaire de recommencer à compter depuis 0 (et ainsi éviter les rollovers)
+
:: _IC1IF = 0;
+
: }
+
La valeur du timer 2 ou 3 (selon réglage) est copiée dans le registre ICxBUF, mais pas remise à 0. Si besoin, il est possible de la remettre à 0 en utilisant directement les registres de comptage TMRx. Noter qu’il est possible, pour by-passer une limitation du chip, d’utiliser le TMR d’un autre timer, sous réserve que celui-ci soit remis à 0 juste après et qu’aucun autre soft ne s’en serve.
+
==== Paramétrage de l'interruption externe ====
+
Les interruptions externes sont en réalité une extension des fonctions d’entrée / sortie, mais avec la génération d’une interruption à chaque front montant. Si la fonction ressemble beaucoup à l’IC, elle est cependant limitée sans possibilité de prescaler. A  noter enfin qu’il ne vaut mieux pas utiliser l’INT0 qui est très haute dans la liste des priorité d’interruption, et qui peut donc rapidement poser problème pour l’exécution du code.
+
: void init_interrupts(void)
+
: {
+
:: RPINR0 = 0x0900; // RPINR0 => octet de poids fort pour le choix de la broche associée à l’interruption 1 (INT1), donc ici sur la broche RP9 (RB9)
+
:: _INT1EP = 0;
+
:: _INT1IF = 0; // vérification que l’interruption n’est pas en cours
+
:: _INT1IE = 1; // Activation de l’interruption
+
: }
+
 
+
Le bit _INT1EP contrôle le sens de génération de l’interruption :
+
* 1 – interruption sur le front descendant,
+
* 0 – interruption sur le front montant.
+
==== Interruption externe ====
+
Une interruption externe est définie dans la routine suivante :
+
: void __attribute__((interrupt,no_auto_psv))_INT1Interrupt(void) 
+
: {
+
:: [[code]]
+
:: _INT1IF = 0;
+
: }
+
=== Les communications ===
+
Les DsPIC33FJ possèdent plusieurs modes de communication gérés automatiquement par le chip, qu’il ne reste qu’à paramétrer :
+
* I2C,
+
* UART,
+
* ECAN.
+
Dans le projet décrit par la suite, seule I2C et ECAN seront utilisés, l’I2C en maitre et l’ECAN en émission / réception.
+
Les drivers de communication sont définis dans des fichiers indépendants :
+
* ecan.c / ecan.h, disponibles sur le site : ****
+
* i2c_Func.c / i2c.h, disponibles sur le site : ****
+
Les fichiers ne sont pas disponibles ici car ils appartiennent à leurs créateurs (qui n’est pas moi, simple utilisateur), et donc restent disponibles sous leur contrôle uniquement.
+
==== Utilisation de l'I2C ====
+
Dans les applications du projet, le chip est utilisé comme maitre I2C pour lire des télémètres à ultrason.<br />
+
Il est possible d’utiliser l’i2C pour la communication globale avec le chip, par exemple pour la transmission d’info avec le blackfin. Cependant, il faut penser que l’I2C n’est pas fait pour la communication à distance (de l’ordre du mètre et plus), en particulier dans un environnement CEM sévère (par exemple avec de puissants moteurs brushless à proximité).<br />
+
C’est pourquoi dans les applications du projet, les communications internes seront réalisées en ECAN (plus robuste), et l’I2C limité à l’interface avec les composants qui ne comprennent que ça (typiquement les sonars à ultrason).
+
===== Câblage de l'I2C =====
+
Le principe de l’i2c est qu’il est possible d’utiliser des composants sur plusieurs niveaux de tension, par exemple entre les DsPIC33F (en 3,3V) et les capteurs à ultrason (en 5V).<br />
+
Pour cela, on utilise sur chaque ligne des résistances de pull-up, portant la tension de la ligne à la tension nécessaire pour que tout le monde comprenne (c’est un bus, donc il peut y avoir plusieurs composants). Tous les '''sda''' (serial data) sont à relier ensemble, tous les '''scl''' (serial clock) aussi.
+
<<Ajouter figure>>
+
===== Paramétrage de l'I2C =====
+
La fonction I2C est initialisée facilement (en mode maitre) à partir de la fonction donnée dans le fichier i2c_Func.c : InitI2C().<br />
+
Cette fonction peut cependant être (légèrement) modifiée :
+
* I2C1BRG : ce registre permet de modifier la fréquence de communication de l’I2C. L’I2C fonctionne principalement en 140 KHz et 400 KHz. A noter, puisque c’est le maître qui donne l’horloge, il est normalement possible de mettre la valeur que l’on souhaite dans la limite des 400 KHz max.
+
Il n’y a pas de redirection (à ma connaissance) sur les broches de l’I2C, il faut nécessairement utiliser les broches RB8 / RB9 ou RB5 / RB6 (I2C complémentaire, utilisable en modifiant les bits de configuration (Configure\ Configuration Bits… \ ALTI2C (Alternate I2C Pins)).
+
===== Interruption sur l'I2C =====
+
En mode maitre, c’est le chip qui décide d’initier la communication au moment opportun. Les esclaves peuvent également initier la communication, mais cette fonction ne sera pas étudiée ici.<br />
+
Il existe deux fonctions pour la communication I2C :
+
* Write : écrit un certain nombre de données dans le chip à l’adresse indiquée. La définition des données écrites pour les deux composants doit faire l’objet d’une ICD .
+
* Read : Un premier cycle d’écriture va positionner l’esclave en lecture (en général, l’envoi de l’adresse du composant esclave « +1 », par exemple 0xE2 : adresse de l’esclave en écriture, 0xE3 : adresse de l’esclave en lecture), puis un redémarrage permet à l’esclave de transmettre ses données.
+
La fonction standard d’écriture du fichier i2c_Func.c est utilisable en l’état (rien trouvé à redire).<br />
+
On l’appelle ainsi :
+
: '''LDByteWriteI2C(0xE2, 0, 0x51);'''
+
Avec :
+
* 0xE2 : adresse de l’esclave,
+
* 0 : adresse de démarrage pour l’écriture des données,
+
* 0x51 : la (les) valeur(s) à écrire.
+
Pour la fonction de lecture, j’ai modifié légèrement la fonction donnée dans le fichier.<br />
+
On l’appelle ainsi :
+
: '''LDByteReadI2C(0xE2, 0x02, i2c_buffer_out_1, 4);'''
+
Avec :
+
* 0xE2 : adresse de l’esclave,
+
* 0 x02: adresse de démarrage de la lecture des données,
+
* I2c_buffer_out_1 : une structure de type [unsigned char i2c_buffer_out_1[20]] permettant de récupérer les données en lecture,
+
* 4 : le nombre de bits à lire.
+
+
La fonction est modifiée comme suit :
+
: unsigned int LDByteReadI2C(unsigned char ControlByte, unsigned char Address, unsigned char *Data, unsigned char Length)
+
: {
+
:: IdleI2C();
+
::: //wait for bus Idle
+
:: StartI2C();
+
::: //Generate Start Condition
+
:: WriteI2C(ControlByte);
+
::: //Write Control Byte
+
:: IdleI2C();
+
::: //wait for bus Idle
+
:: WriteI2C(Address);
+
::: //Write start address
+
:: IdleI2C();
+
::: //wait for bus Idle
+
:: RestartI2C();
+
::: //Generate restart condition
+
:: WriteI2C(ControlByte | 0x01);
+
::: //Write control byte for read
+
:: IdleI2C();
+
::: //wait for bus Idle
+
:: getsI2C(Data, Length);
+
::: //read Length number of bytes
+
:: NotAckI2C();
+
::: //Send Not Ack
+
:: StopI2C();
+
::: //Generate Stop
+
}
+
 
+
De plus, avant de démarrer une communication, il faut forcer les esclaves à revenir en idle mode. Ajouter les deux lignes suivantes dans le Main :
+
* StartI2C();
+
* StopI2C();
+
En règle générale ça marche…
+
 
+
==== Utilisation du CAN ====
+
Le bus CAN est plus robuste à la CEM que l’I2C, il est donc plus judicieux de l’utiliser au lieu de l’i2c pour toutes les communications sur lesquelles on a le choix.<br />
+
Le protocole CAN est standardisé, le plus simple est de choisir des composants qui le possèdent en natif. Dans le projet présenté,  les deux composants sont natifs CAN :
+
* Blackfin : le module noyau can.ko doit être inséré au système d’exploitation, il peut ensuite être utilisé (voir ***)
+
* DsPIC33F : il faut choisir dans la gamme des composants qui ont de l’ECAN en natif. C’est le cas notamment des composants 64MC802.
+
Avant toute chose, il y a deux choses à savoir sur le CAN indépendamment de sa programmation :
+
# Il est impératif d’utiliser un driver matériel indépendant (convertissant le can+/can- en CAN_H / CAN_L), par exemple des MCP2551 de chez microchip,
+
# Il est impératif d’avoir au moins deux composants lorsque l’on utilise un réseau CAN. En effet, un seul composant a besoin qu’on lui réponde pour compléter son cycle de transmission de données.
+
Sans ces deux précautions, il est impossible de faire marcher un module CAN.
+
===== Câblage de l'ECAN =====
+
Comme expliqué précédemment, il n’est pas possible de câbler deux composants par leurs can+ / can- directement. Il faut les câbler par leurs CAN_H / CAN_L après ajout d’un driver matériel et surtout d’un composant en face. Le schéma ci-dessous précise le montage, tous les CAN_H sont à relier ensemble, les CAN_L ensemble également.<br />
+
<<Ajouter l'image>><br />
+
<span style="color: red;">'''Attention, le MCP2551 se plug en 5V uniquement.'''</span>
+
===== Paramétrage du module CAN =====
+
Dans un réseau CAN, les composants échangent des données sous la forme de trames de 8 octets. Le principe est sensiblement différent de l’i2c, dans lequel on adresse un composant puis une adresse de données dans le composant, avant de lire ou d’écrire à cette adresse. Dans le CAN, on adresse des données.<br />
+
Le principe est que chaque « type » d’information a sa propre adresse. En effet, un composant va transmettre une information avec une adresse indiquant à la fois le type et l’origine de l’information.<br />
+
Par exemple, un détecteur va transmettre régulièrement ses données sur le réseau CAN avec une référence (le terme est plus explicite qu’adresse) propre à la fois au composant et au type de données qu’il transmet. Ensuite, tous les composants du réseau qui doivent lire ces données sont paramétrées pour filtrer les données échangées sur le réseau et lire précisément celle-ci. <br />
+
Le réseau va traiter les cas de collision automatiquement (par un jeu magique de comparaison des bits dans les références, on va dire que c’est sans objet dans l’application courante).<br />
+
Comme pour l’i2c, il est possible d’utiliser le code tout fait développé par microchip et contenu dans les fichiers ***. L’initialisation se fait par les fonctions :
+
: initECAN();
+
: initDMAECAN();
+
Il faut nécessairement utiliser la DMA dans l’exemple. On peut faire sans mais je n’ai pas creusé plus loin.<br />
+
Pour l’initialisation de l’ECAN (ECAN pour extended CAN, possibilité d’utiliser des références sur 29 bits au lieu de 11 pour avoir plus de messages) il faut paramétrer les filtres et masques qui permettent de limiter les données lues. Dans l’exemple, je n’utilise que les adresses standards (sur 11 bits).<br />
+
Les lignes à modifier sont :
+
* C1RXM0SID=CAN_FILTERMASK2REG_SID(0x15E) : On applique un masque permettant de lire tous les bits de 350 à 360 (0x15E ***  ADU).
+
* C1RXF0SID=CAN_FILTERMASK2REG_SID(0x0x15E) : adu.
+
De même, il est possible de modifier la vitesse de transmission via la valeur BRP_VAL définit dans le ecan.h :
+
: ''' #define BRP_VAL ((FCAN/(2*NTQ*BITRATE))-1)'''
+
Avec :
+
: ''' #define FCAN      40000000'''
+
:: // Fréquence de cycle, Fcy en réalité.
+
: ''' #define BITRATE 125000''' 
+
:: // vitesse de communication
+
: ''' #define NTQ 20'''
+
:: // 20 Time Quanta in a Bit Time (ne pas changer)
+
 
+
C’est le paramètre BITRATE qui modifie la vitesse de communication sur le réseau CAN. Hélas, la valeur de 125 kHz n’est pas choisie au hasard, elle a été codée en dur dans le module linux can.ko. Donc il faut nécessairement utiliser cette valeur.<br />
+
Les ports de communication utilisés sont les broches RB2 et RB3. Pour l’association de ces broches au driver CAN, on utilise deux registres de redirection :
+
* _TRISB2 = 0;
+
** // Broche RB2 en sortie
+
* _RP2R = 0x10;
+
** // C1TX = RB2, allocation de la broche RB2 à la fonction TxCAN
+
* _TRISB3 = 1;
+
** // Broche RB3 en entrée
+
* RPINR26 = 0x03;
+
** // C1RX = RB3, allocation de la broche RB3 à la fonction RxCAN
+
===== Interruption sur CAN =====
+
La fonction suivante est directement issue de l’application note *** :
+
: void __attribute__((interrupt,no_auto_psv))_C1Interrupt(void) 
+
: {
+
::: /* check to see if the interrupt is caused by receive */   
+
:: if(C1INTFbits.RBIF)
+
:: {
+
:::: /* check to see if buffer 1 is full */
+
::: if(C1RXFUL1bits.RXFUL1)
+
::: {
+
::::: /* set the buffer full flag and the buffer received flag */
+
:::: canRxMessage.buffer_status=CAN_BUF_FULL;
+
:::: canRxMessage.buffer=1;
+
::: }
+
:::: /* check to see if buffer 2 is full */
+
::: else if(C1RXFUL1bits.RXFUL2)
+
::: {
+
::::: /* set the buffer full flag and the buffer received flag */
+
:::: canRxMessage.buffer_status=CAN_BUF_FULL;
+
:::: canRxMessage.buffer=2;
+
::: }
+
:::: /* check to see if buffer 3 is full */
+
::: else if(C1RXFUL1bits.RXFUL3)
+
::: {
+
::::: /* set the buffer full flag and the buffer received flag */
+
:::: canRxMessage.buffer_status=CAN_BUF_FULL;
+
:::: canRxMessage.buffer=3;
+
::: }
+
::: else;
+
:::: /* clear flag */
+
::: C1INTFbits.RBIF = 0;
+
:: }
+
:: else if(C1INTFbits.TBIF)
+
:: {
+
:::: /* clear flag */
+
:::C1INTFbits.TBIF = 0;    
+
:: }
+
:: else if(C1INTFbits.ERRIF)
+
:: {
+
::: C1INTFbits.ERRIF = 0;
+
::: C1TR01CONbits.TXREQ0=0;
+
:: }
+
::: /* clear interrupt flag */
+
:: IFS2bits.C1IF=0;
+
: }
+
 
+
La routine d’interruption est composée de trois parties :
+
* La première [C1INTFbits.RBIF] est liée à une interruption suite à la réception de données. Dans ce cas, l’un des trois buffers de réception est rempli et le flag de réception levé [canRxMessage.buffer_status > 0]. On récupère les données via la fonction rxECAN(&canRxMessage) dans le corps de la fonction main.
+
* La seconde [C1INTFbits.TBIF] est liée à la transmission de données. L’interruption est levée dès lors qu’une transmission c’est bien passé. Ne sert à rien dans l’application, mais y ajouter une LED qui clignote permet de savoir rapidement que le réseau CAN est opérationnel.
+
* La dernière n’est pas active par défaut, elle permet le traitement des erreurs [C1INTFbits.ERRIF].
+
 
+
Pour activer la génération des interruptions, il faut ajouter les lignes suivantes dans le corps de la fonction main :
+
:: /* Enable ECAN1 Interrupt */ 
+
: IEC2bits.C1IE=1;       
+
:: /* enable Transmit interrupt */
+
: C1INTEbits.TBIE=1;
+
:: /* Enable Receive interrupt */
+
: C1INTEbits.RBIE=1;
+
:: /* Activer les interruptions sur erreur */
+
:C1INTEbits.ERRIE = 1;
+
 
+
La récupération des données à traiter se fait par la fonction :
+
: rxECAN(&canRxMessage);
+
+
La structure canRxMessage est également définie dans l’application note ***, reprise ci-dessous :
+
:: /* message structure in RAM */
+
: typedef struct{
+
:: /* keep track of the buffer status */
+
:: unsigned char buffer_status;
+
:: /* RTR message or data message */
+
:: unsigned char message_type;
+
:: /* frame type extended or standard */
+
:: unsigned char frame_type;
+
:: /* buffer being used to send and receive messages */
+
:: unsigned char buffer;
+
:: /* 29 bit id max of 0x1FFF FFFF
+
:: *  11 bit id max of 0x7FF */
+
:: unsigned long id;
+
:: unsigned char data[8];
+
:: unsigned char data_length;
+
: }mID;
+
La création de la structure est réalisée comme suit :
+
* mID canTxMessage;
+
* mID canRxMessage;
+
Avec une structure pour la transmission de message et l’autre pour la réception.<br />
+
Pour la transmission, il faut utiliser la fonction suivante :
+
* sendECAN(&canTxMessage);
+
La structure canTxMessage est instanciée directement dans le code.<br />
+
Enfin, les deux paramètres message_type et frame_type sont initialisés avec les constantes suivantes dans l’exemple :
+
: canTxMessage.message_type=CAN_MSG_DATA;
+
:: // Le message est un message de données simple
+
: canTxMessage.frame_type=CAN_FRAME_STD;
+
:: // L’ID du message est standard, sur 11 bits.
+
=== Les signaux analogiques ===
+
Les microcontrôleurs sont des composants logiques, c’est-à-dire que toutes les données qu’ils traitent sont des données logiques (« 0 » / « 1 ») dont le niveau électrique dépendant de la technologie (0/5V ou 0/3.3V). Il n’est donc, à priori, pas possible de commander un équipement avec un signal analogique variant entre 0 et 5V par exemple, ou de disposer d’une entrée codant sont information sur son niveau électrique.<br />
+
Pour palier à cela, il existe deux fonctions complémentaires : <br />
+
* ADC : Analogic to Digital Converter, ou convertisseur analogique / numérique. Le fonctionnement détaillé d’un ADC ne sera pas abordé ici, seule sa mise en œuvre sera traitée.
+
* PWM : Pulse Width Modulation, ou modulation par amplitude d’un signal carré. De même, seule la mise en œuvre avec un Chip sera abordée ici.
+
==== Convertisseurs Analogiques / Numériques ====
+
Le module ADC va convertir un signal analogique, de niveau 0 à 3.3V ou 5V, en une donnée numérique de type flottant ou entier, signé ou non ('''AD1CON1\FORM<1:0>'''). Le principe est de réaliser un échantillonnage du signal à une fréquence plus ou moins haute, puis de convertir ce signal de manière régulière ou évènementielle ('''AD1CON1\SSRC<2:0>''').<br />
+
Ce module utilisera obligatoirement les PIN du chip ayant une capacité de conversion analogique en numérique, à savoir sur le GP/MC802 les ports A0-1, B0-3 et B12-15. Il est en outre possible de choisir les références, positives et négatives, utilisées pour l’échantillonnage du signal ('''AD1CON2\VCFG<2:0>''').<br />
+
:: // Format des données converties : 0 = Entier non signé
+
: AD1CON1bits.FORM  = 0;
+
:: // Source de l’horloge de conversion. J’utilise ici GP Timer 3 : à chaque échéance du timer, une conversion intervient
+
: AD1CON1bits.SSRC  = 2;
+
:: // Contrôle de l’échantillonnage : 1 => un nouvel échantillonnage démarre automatiquement après la dernière conversion
+
: AD1CON1bits.ASAM  = 1;
+
:: // Opération en mode 10 Bits. La donnée est convertie sur 10 Bits. Il est possible d’opérer sur 12 également
+
: AD1CON1bits.AD12B  = 0;
+
:: // Scan des entrées sélectionnées pour CH0+ lors de l’échantillonnage A
+
: AD1CON2bits.CSCNA = 1;
+
:: // Conversion du canal CH0 uniquement
+
: AD1CON2bits.CHPS  = 0;
+
:: // Horloge de conversion dérivée de l’horloge système (réglable)
+
: AD1CON3bits.ADRC = 0;
+
:: // Durée de conversion : (n + 1) fois le temps d’opération du chip Tcy, ici 63 => Tad = 0,025ns * (63+1) = 1,6µs
+
: AD1CON3bits.ADCS = 63;
+
:: // Nombre de canaux utilisés pour le scan : ici NUM_CHS2SCAN = 1
+
: AD1CON2bits.SMPI = (NUM_CHS2SCAN-1);
+
:: //AD1CSSH/AD1CSSL: A/D Input Scan Selection Register
+
:: //AD1CSSH = 0x0000;
+
: AD1CSSLbits.CSS0=1;
+
:: // Enable AN4 for channel scan
+
:: //AD1PCFGH/AD1PCFGL: Port Configuration Register
+
: AD1PCFGL=0xFFFF;
+
:: // AD1PCFGH=0xFFFF;
+
: AD1PCFGLbits.PCFG0 = 0;
+
:: // AN4 as Analog Input
+
: IFS0bits.AD1IF = 0;
+
:: // Clear the A/D interrupt flag bit
+
: IEC0bits.AD1IE = 1;
+
:: // Enable A/D interrupt
+
: AD1CON1bits.ADON = 0;
+
:: // Turn on the A/D converter
+

Latest revision as of 10:33, 9 April 2014

Localisation par balisage infrarouge

Retour à User:Mael
Cette page présente mes travaux actuels sur la fabrication d'un système de balisage infrarouge pour mobile.
Le système contient les fonctions suivantes :

  • Emission infrarouge de 4 balises autonomes,
  • Réception infrarouge sur une tourelle,
  • Contrôle d'un moteur pas à pas d'entrainement de la tourelle.

En plus de la fonction de triangulation associée au balisage, le système intègre en permanence deux entrées codeuses, assurant ainsi l'odométrie du mobile.
Les données d'odométrie sont ensuite intégrées aux données de balisage en permanence par l'intermédiaire d'un filtre de Kalman.

Un troisième volet de ce projet est le calcul en permanence d'une "void area", basée sur un réseau de capteurs ultrason déployés sur le mobile.

Dans un second temps, un autre projet viendra compléter celui-ci avec la réalisation d'un système de contrôle commande des moteurs (en DC, stepper DC puis en BLDC) avec un calcul de trajectoire basé sur les splines de Catmull-Rom.


Système de balisage infrarouge

Le principe de balisage infrarouge consiste à utiliser des balises émettant un signal infrarouge que va détecter un plot central, déployé sur une structure mobile roulante par exemple.
Le concept est développé dans le cadre de la coupe de France de robotique, et permet de disposer facilement d’une information de localisation sur la table de 3x2m.
Table de jeu - Localisation IR.jpg
Le but est d’obtenir la meilleur estimation de x et y afin d’avoir la position du robot sur la table, ainsi que son orientation.
Le principe appliqué est celui de la triangulation, basée sur une mesure d’angle (il existe le même principe avec les distances, tout n’étant au final qu’une application des équations de trigonométrie).
Le choix du balisage infrarouge est lié au faible coût de la mise en œuvre (une centaine d’euros tout compris) et à la relativement grande précision du système.
Il sera couplé, dans la suite de ce document, à un dispositif d’odométrie et un dispositif de sonars permettant d’affiner la position courante du robot et de créer une « void area » autour du robot indiquant les zones accessibles ou non au robot.
Table de jeu - Void area et conduite.jpg


Les concepts définis par la suite s'appuient principalement sur deux éléments :

Le détail de l'utilisation de ces composants est donné dans les pages associées.

Triangulation

Le principe de la triangulation est de mesurer trois angles entre des points fixes et un point mobile, et de retrouver, connaissant la position des points fixes, la position du point mobile. La manœuvre est limité à la détection d’objets à l’intérieur de la zone formée par les points fixes (c’est peut-être possible en dehors, mais je n’ai pas fait l’analyse, pas besoin).
Le nombre limite de points fixes est au minimum de 3, dans le cadre de l’étude 4 seront utilisés (par extension de la version à 3 points). Plus il y a de points fixes, meilleure est la précision, mais plus long seront les traitements.
Tout d’abord, définissons quelques variables. Le schéma ci-dessous représente la table sur laquelle est porté un repère orthonormé qui va servir de base aux calculs. Deux angles sont définis :

  • θAB,
  • θAC.

Triangulation - principe.jpg
Le calcul de ces deux angles sera précisé plus bas. Pour l’instant, partons du principe qu’ils sont connus. Par triangulation, nous allons calculer xM et yM en calculant un certain nombre de variables de cette figure qui nous donnerons, in fine, les coordonnées du point M.
Par analyse du schéma, on pose :

xM = AM x Cos[a]
yM = AM x Sin[a]

Nota : pour ceux qui préfèrent travailler en polaire, il est possible d’utiliser AM (ie r) et a (ie θ) directement, par exemple pour trianguler la position d’un outil sur un bras SCARA.
Le calcul de la variable AM se base sur les trois formules suivantes :

  • Loi des sinus : MB /Sin[a] = MA/Sin[Pi - θAB - a] = AB/Sin[θAB]
  • Sin[Pi - x] = Sin[x]
  • Sin[a+b] = Sin[a] x Cos[b] + Sin[b] x Cos[a]

On définit ainsi :

MB = (AB x Sin[a]) / Sin[θAB] = (AM x Sin[a]) / Sin[Pi - θAB - a]
Avec : Sin[Pi - θAB - a] = Sin[θAB] x Cos[a] + Sin[a] x Cos[θAB]

D’où, tous calculs fait :

AM = (AB x Sin[a]) / Tan[θAB] + AB x Cos[a]

Le calcul de la variable [a] se base sur les formules suivantes :

  • Loi des sinus : MB /Sin[a] = MA/Sin[Pi - θAB - a] = AB/Sin[θAB]
  • Sin[Pi - x] = Sin[x]
  • Sin[a+b] = Sin[a] x Cos[b] + Sin[b] x Cos[a]
  • Sin[PI/2 + x] = Cos[x]
  • Cos[a-b] = Cos[a] x Cos[b] + Sin[a] x Sin[b]

Dans le schéma ci-dessus, on applique la formule des sinus :

AC / Sin[θAC] = AM / Sin[{ACM}]
AB / Sin[θAB] = AM / Sin[{MBA}]

Sachant que les angles :

{ACM} = PI/2 – a - θAC
{MBA} = PI – a - θAB
AC / Sin[θAC] = AM / Cos[a - θAC]
AB / Sin[θAB] = AM / Sin[a + θAB]

Il ne reste plus qu’à identifier [a] en joignant les équations en AM.
  Tous calculs faits :

(AC x Tan[θAC]) x Cos[a] + AC x Sin[a] = (AB x Tan[θAB]) x Sin[a] + AB x Cos[a]
a = Atan[((AC / Tan[θAC]) - AB) / ((AB / Tan[θAB]) - AC)]

Le calcul de [xM, yM] est réalisé en permanence. La précision de la position dépendra en grande partie de la précision de la mesure des angles, qui est liée à la technologie de mesure.
Sur la base de ce qui précède, et sans tenir compte pour l’instant des erreurs de mesure, la triangulation est possible en mesurant au moins deux des quatre angles formées par les 4 balises aux 4 coins de la table et la tourelle de réception.

Détail des applications

Le chapitre suivant va décrire plusieurs applications complètes et unitaires à base de microcontrôleur DsPIC33F, qui reprennent toutes les fonctions vues ci-dessus. Chacune de ces applications peut être reprise dans un projet quelconque sans adaptation particulière, en tenant compte toutefois de l’ICD associée à chaque carte.
Les applications suivantes sont présentées :

  • Emetteur infrarouge,
  • Récepteur infrarouge,
  • Lecture sonars,
  • Driver moteur pas à pas,
  • Driver servomoteur.

Chaque application va utiliser le même type de µC, des DsPic33FJ64MC802, et la même communication en ECAN pour transmettre ou recevoir des données du Blackfin ou de tout autre composant disposant de module CAN.
Les dimensions proposées sont également standardisées, pour permettre l’intégration dans un fond de panier spécifique.

Principe de transmission infrarouge

Le principal problème lors d’une transmission infrarouge est lié aux perturbations de la lumière environnante. En effet, une émission directe sans codage sera extrêmement perturbé, probablement inutilisable.
Pour éviter ce problème, le signal émis en infrarouge est encodé, selon le principe suivant :
Code winchester.jpg
Le signal émis transmet une information codé selon le principe du code Winchester, qui transmet les informations avec une donnée de type front (montant / descendant) au lieu de logique (haut / bas).
L’amplitude du signal est très limitée : entre 889µs et 1778µs, au-delà le récepteur est saturé. Les états hauts sont également codés par une porteuse de rapport cyclique ¼, d’amplitude 7µs haut / 22µs bas.
Il est nécessaire de respecter ces contraintes pour pouvoir utiliser un récepteur intégré, qui détecte automatiquement ce type de codage et le transforme en signal logique (0/1).
L’utilisation d’un code winchester présente plusieurs avantages, mais aussi l’inconvénient d’être assez difficile à décoder. Pour simplifier, nous utiliserons (dans un premier temps) un autre moyen de distinguer un signal d’un autre : le rapport cyclique.
Entre 900 µs et 1800 µs, les amplitudes seront retransmises en l’état par le module de réception IR. Dans cette fenêtre, un rapport cyclique de 900 / 1200 pourra être distingué d’un rapport cyclique 900 / 1500 ou 1500 / 1500 etc.
Rapport cyclique.jpg
Ainsi, il est possible d’émettre avec plusieurs balises d’émission selon les rapports cyclique différents, qu’un récepteur pourra identifier pour différencier les balises.

Émetteur Infrarouge

Les cartes d’émission infrarouge sont basées sur le principe ci-dessus. La version présentée ici est déployée de manière autonome dans une balise qui possède sa propre alimentation, ainsi qu’un Dip switch pour la sélection du rapport cyclique.

Étage électrique

La commande de la LED ne peut pas se faire directement par le microcontrôleur. En effet, la puissance nécessaire à l’émission de la LED est trop importante pour le microcontrôleur. Il est donc nécessaire d’ajouter un étage de puissance par l’intermédiaire d’un transistor [type BC547].
Alimentation LED IR.jpg
Il faut rajouter un montage pour deux LED, mais il est possible de tous les commander avec la même sortie du microcontrôleur. Les LED utilisées sont des TSAL 6200 Vishay.
Le signal Sig est associé à la variable _RB6 (en sortie), et le Dip switch est connecté aux ports _RB8 à _RB15 (en entrée).
DIL Switch.jpg

Algorithmique

Pour l’émetteur infrarouge, les fonctions suivantes sont utilisées :

  • Configuration de l’horloge : Fcy = 40 MHz, configuration 2 (cf tableau xx),
  • Initialisation des timers :
    • Timer 1 : 20 ms, ainsi qu’un compteur interne de 25 pour générer un LED clignotant à 1 Hz,
    • Timer 2 : 7µs
    • Timer 3 : 9µs, ainsi qu’un compteur interne de 100 permettant une interruption à 900 µs.
Timer 1 interrupt

Le timer 4 génère un clignotement de LED à 2 Hz, utilisé comme bit de vie.

TMR1=0;
if(count>=25)
{
_RB7 = !_RB7;
count = 0;
}
else
{
count = count + 1;
}
IFS0bits.T1IF=0;

La variable count est globale, utilisée seulement ici.
Pour rappel, l’opérateur « ! » indique l’inverse d’une variable logique (ie son complément à 1). Ainsi, la fonction _RB7 = !_RB7 signifie que la variable _RB7 (port _RB7 utilisé comme E/S directe, sans passer par le registre PORTB) est mise à son complémentaire à 1 (0 si elle valait 1, 1 sinon).

Timer 2 interrupt

Le timer 2 génère la porteuse du signal infrarouge. Il déclenche une interruption toutes les 7 µs, qui est ensuite associée à un compteur à 4 pas. Une fois sur quatre, l’interruption positionne le bit_6 (commande des LED) à « x » (1 ou 0 selon l’état haut ou bas du code winchester), et trois fois sur quatre le bit_6 reste à 0. La valeur de x est générée par le timer 3.

TMR2=0;
if(count_porteuse == 1)
{
_RB6 = data_out;
count_porteuse = count_porteuse + 1;
}
else
{
_RB6 = 0;
if(count_porteuse >= 4)
{
count_porteuse = 1;
}
else
{
count_porteuse = count_porteuse + 1;
}
}
IFS0bits.T2IF=0;

La variable count_porteuse est globale, utilisée uniquement ici.
La variable data_out est globale, utilisée ici en lecture et dans timer 3 en écriture.

Timer 3 interrupt

Le timer 3 va générer l’impulsion entre 900 et 1800 µs. Pour cela, une interruption toutes les 9µs ainsi qu’un compteur >ADU<

TMR3=0;
if(elapse >= pulse)
{
data_out = !data_out;
elapse = 0;
data_in = (_RB4*2) + _RA4;
switch (data_in)
{
case 0 :
long_bit = 100;
short_bit = 200;
break;
case 1 :
long_bit = 100;
short_bit = 100;
break;
case 2 :
long_bit = 200;
short_bit = 100;
break;
case 3 :
long_bit = 150;
short_bit = 150;
break;
}
if(data_out == 1)
{
pulse = long_bit;
}
else
{
pulse = short_bit;
}
}
else
{
elapse = elapse + 1;
}
IFS0bits.T3IF=0;
Initialisation du signal

ADU

Récepteur infrarouge

Le récepteur infrarouge est calibré pour lire les signaux générés conformément à la figure [Principe de transmission infrarouge]. Il utilise principalement les détecteurs TSOP48 de Vishay (mais il en existe une foultitude) qui réalisent directement l’intégration de la porteuse pour filtrer les perturbations lumineuses. La longueur du signal détecté n’est pas exactement celle qui est émise. En effet, la commutation des fronts est réalisée lorsque plusieurs occurrences de porteuse sont détectées (ou qu’une absence de codeuse est détectée).

Étage électrique

La sortie « signal » du récepteur infrarouge est directement lisible par le µC, sans étage d’adaptation. Les 4 signaux sont câblés sur 4 ports du µC réglés en entrées (ie _RBx = 1). Le récepteur infrarouge utilise en plus l’interruption INT1 comme entrée de codeuse, sur laquelle un signal carré indiquera à chaque front montant l’avancement d’un pas du moteur pas à pas (ou d’une codeuse quelconque). L’entrée est réalisée sur le port _RB8. Une seule entrée codeuse est nécessaire ici, pour cette application les 4 récepteurs sont associés à la même motorisation.

Algorithmique

Le principe de détection est décrit dans le schéma ci-dessous :
Detection rapport cyclique.jpg
Nota : Attention : si le ratio détecté est 4/3, c’est que la balise émet en ¾ (car inversion du signal par le récepteur).
Deux fonctions sont nécessaires :

  • Détecter que l’on reçoit un message d’une balise,
  • Identifier la balise.

Pour la première fonction, il est possible d’utiliser la limite de saturation du récepteur comme indication. En effet, puisqu’aucun signal ne peut transmettre des états logiques hauts supérieurs à 2 ms (en tenant compte d’une marge d’erreur, au lieu de 1778µs), si aucun changement d’état à l’entrée du détecteur n’est détecté pendant un temps supérieur à 2 ms (mettons 3 ms pour avoir une bonne marge), c’est qu’aucune balise n’est en vue. Dès lors, la limite de rollover du timer est fixée à 3 ms :

TMRiCON = 0x0010 ;
PMRi = 15000 ;

Lors d’un rollover du timer, une interruption sera générée qui permettra un traitement particulier.
L’input capture (IC) est paramétré pour détecter chaque front, montant ou descendant. En effet, il est nécessaire de mesurer la durée des fronts haut et bas pour en déduire le rapport cyclique.
A la détection d’un front montant ou descendant, une interruption IC est déclenchée et le contenu du timer (associé à l’input capture, le même que celui paramétré précédemment) est transmis au module IC. Si ce temps est le temps depuis la dernière remise à 0 du timer, réalisée au dernier front détecté, la valeur du timer est la durée du front.
Si le front est bas à l’interruption, alors la valeur du timer est la durée du front haut, sinon celle du front bas.
Une limitation de la fonction IC n’autorise l’utilisation que des timers 2 ou 3 pour la recopie de la valeur (cf Interruption Input Capture]). Pour les IC 7 et 8 (le chip dispose de 4 IC : 1, 2, 7 et 8), on utilisera les timers 4 et 5, recopiés manuellement dans la fonction d’interruption puis remis à 0.

if(_RB10 == 0)
{
count_IR[2][0] = TMR5; // Step 1
TMR5 = 0;
}
else
{
count_IR[2][1] = TMR5; // Step 0
TMR5 = 0;
[…]
}

Dès lors qu’une interruption IC est activée, le récepteur détecte une balise. La valeur de rotation de la tourelle est relevée à la première interruption IC après une interruption du timer associé (cf schéma ci-dessus, recopie de l’entrée codeuse) et le timer remis à 0 dans tous les cas.
Lorsque une interruption Timer est levée à son tour, c’est qu’aucune balise n’est en vue (pas de signal cohérent détecté). La valeur de la tourelle est relevée (entrée codeuse) à la première interruption timer après une interruption IC associée, indiquant la sortie de la fenêtre de détection. Cette fenêtre sera explicitée plus bas.

Chaîne de détection

Quatre chaines de détections sont disponibles dans l’application traitée ici. Chaque chaine dispose de fonctions spécifiques et de variables spécifiques associées à sa détection. De manière générale, les variables sont des tableaux de variables dont chaque variable est associée à une chaine. Une chaine de détection est composée :

  • Du timer supervisant le rollover et cadencé pour la mesure du rapport cyclique,
  • D’un Input Capture associé mesurant le rapport cyclique,
  • D’une variable int detection_en_cours[4] qui indique, pour chaque chaine, si une balise est en vue ou non,
  • D’une variable unsigned int cds_detect[4] qui est la valeur de la codeuse associée à la chaine,
  • D’une variable int count_IR[4][2], qui est la variable contenant la longueur des fronts hauts et bas mesurés ([2] => [0] pour le front bas, [1] pour le front haut).

En plus de ces fonctions et variables, quelques fonctions génériques et variables globales sont définies :

  • int calculate_angle(int count_a, int count_b), fonction de d’identification d’une balise en fonction de la longueur des fronts hauts et bas,
  • traite_IR : si cette variable est à 1, cela signifie qu’une balise a été vue par l’une des chaines et qu’elle doit être identifiée,
  • current_IR : indique quelle est la chaine qui a détecté une balise, pour son identification,

Enfin, diverses variables seront utilisées localement pour la réalisation de fonctions particulières (notamment mémorisation de variables), elles seront explicitées au cas par cas.

Timer 1 interrupt

La fonction est identique à celle de l’émetteur infrarouge.

Configuration d'une chaîne de détection

Les quatre chaines de détection sont identiques, avec la répartition des fonctions suivantes :

Chaine 1 => Timer 2 => IC1
Chaine 2 => Timer 3 => IC2
Chaine 3 => Timer 4 => IC7
Chaine 4 => Timer 5 => IC8

Dans la suite de ce chapitre, les références i (n° de chaine), j (numéro de timer) et k (numéro d’input capture) seront utilisées pour présenter la configuration des 4 chaines en même temps.

Initialisation du timer j :

Le timer j est initialisé pour générer une interruption toutes les 3 ms, indiquant un rollover (cf plus haut), et s’incrémente toutes les 0,2 µs.

TiCON = 0x0010 ;
PMRi = 15000 ;

Initialisation de l’input capture k :

Les quatre IC s’initialisent dans la même fonction :

ICkCON = 0x0081 ; => pour k = 1, on recopie timer 2
ICkCON = 0x0001 ; => pour k = 2,3 et 4, on recopie timer 3.

La configuration des ports se fait par les deux registres RPINR7 et RPINR10. Chacun de ces mots contrôle un port par registre (MSB / LSB) :

RPINR7 = 0x0B0A ; => L’IC1 est associée à la broche RP10 (10 = 0x0A), l’IC2 à la broche RP11 (0x0B)
RPINR10 = 0x0D0C ; => L’IC7 est associée à la broche RP12 (0x0C), l’IC8 à la broche RP13 (0x0D)

Initialisation de l’interruption externe :

L’interruption externe réalise le comptage pour les chips :

RPINR0 = 0x0900; => La broche d’entrée pour les interruptions externes est la broche RP9
_INT1EP = 0; => Les interruptions sont sur front montant.

Initialisation des ports :

Les broches associées à l’Input Capture et à l’interruption externe sont contrôlées directement par les drivers. Ils ne sont donc pas concernés par la direction des ports.

Les interruptions

Comme la plupart des applications, le récepteur IR fonctionne principalement avec les interruptions. L’algorithme de détection, pour chaque chaine, est le suivant :

  1. Détection d’un changement d’état sur l’input capture alors qu’aucune détection n’est en cours (detection_en_cours[i] == 0),
  2. Relevé de la position de la tourelle (ie valeur de la codeuse tourelle),
  3. Identification du rapport cyclique (ie de la balise émettrice),
  4. Détection d’une fin de détection (ie rollover du timer associé à la chaine) si une détection est en cours (ie detection_en_cours[i] == 1),
  5. Relevé de la position de la tourelle (ie valeur de la codeuse tourelle),
  6. Calcul de la position milieu de la tourelle dans sa fenêtre de détection,
  7. Transmission de cette position ainsi que du numéro de balise au BF via le réseau CAN.

Chacune de ces étapes sera détaillée ci-dessous au travers de trois fonctions, associées aux chaines vues précédemment. Chaque chaine aura un timer et un input capture dédié, la fonction d’identification du rapport cyclique étant générique et appelée automatiquement dès lors qu’un récepteur est en cours de détection.   Interruption Input capture

L’interruption input capture va réaliser la détection d’un changement d’état (fonction nominale de l’input capture) et recopier la valeur courante de la codeuse si il s’agit du premier changement d’état depuis le rollover.

void __attribute__((interrupt, no_auto_psv)) _ICkInterrupt(void)
{
if(_RBn == 0)
// Si le récepteur est à l’état bas
{
count_IR[i][0] = IC1BUF;
// Durée du pulse haut
TMRj = 0;
// Réinit du timer pour une nouvelle mesure
}
Else
// Si le récepteur est à l’état haut
{
count_IR[i][1] = IC1BUF;
// Durée du pulse bas
TMRj = 0;
// Réinit du timer pour une nouvelle mesure
traite_IR = 1;
// Déclenche la fonction d’identification
current_IR = i;
// Chaine à identifier
}

A noter, pour l’input capture 1 on peut utiliser l’IC1BUF, qui est rempli automatiquement avec la valeur du timer 2 lors de la détection. Pour les autres chaines, il faudra recopier la valeur du timer associé : count_IR[i][0] = TMRj;

Afin de relever le début de la fenêtre de détection, une variable est testée à chaque interruption. Seule une interruption timer pourra remettre l’état à 0 et ainsi déclencher la réaction associée. Une fois une balise en vue, et tant qu’elle le reste (ie les interruptions IC à moins de 3 ms), la valeur de la codeuse n’est plus relevée :

if(detection_en_cours[i] == 0)
{
detection_en_cours[i] = 1;
cds_detect[i] = codeuse;
}
_IC1IF = 0;

}   Interruption timer

L’interruption timer intervient lors d’un rollover du compteur timer, soit au bout de 3 ms sans détection. Cela correspond à une zone sans balise en vue.

Donc dès lors que cette interruption intervient lorsque la variable detection_en_cours[i] est positionnée à 1, elle indique la sortie de la zone de détection.

void __attribute__((interrupt, no_auto_psv)) _TjInterrupt (void)
{
TMRj=0;
if(detection_en_cours[i] == 1)
{

Un étage de filtrage des mauvaises détections est ajouté dans la procédure. Cet étage est géré dans la boucle principale, il sera détaillé par la suite. Memory_detecteur[i] n’est supérieur à 0 qu’en cas de détection correcte.

if(memory_detecteur[i]>0)
{

Il est nécessaire de tenir compte du sens de la codeuse, pour éviter d’envoyer des angles négatifs au BF :

if(cds_detect[i] >= codeuse)
{
cds_detect[i] = cds_detect[i] - (cds_detect[i] - codeuse)/2;
}
else
{
cds_detect[i] = cds_detect[i] + (codeuse - cds_detect[i])/2;
}

Dès lors que la fenêtre de détection est identifiée, puis que son centre est calculé, les données sont transmises au BF (confer ICD pour trouver le message associé).

canTxMessage.buffer=0;
canTxMessage.id=220;
canTxMessage.data[0] = memory_detecteur[i];
canTxMessage.data[1] = GET_LOW_BYTE(cds_detect[i]);
canTxMessage.data[2] = GET_HIGH_BYTE(cds_detect[i]);
canTxMessage.data_length=3;
send_can = 1;
}
}
// On reset toutes les variables de la chaine
memory_detecteur[i] = 0;
detecteur[i] = 0;
detection_en_cours[i] = 0;
IFS0bits.TkIF=0;
}

Le traitement complet n’est réalisé que lorsqu’une fenêtre de détection est en cours. Identification d’une balise L’identification d’une balise est une fonction générique appelée dans la boucle principale. La fonction main réalise les initialisations de fonctions :

oscConfig();
init_pic();
/****/
IFS0=0;
IFS1=0;
IFS2=0;
IFS3=0;
IFS4=0;
/****/
timer1 = 1;
timer2 = 0;
timer3 = 1;
timer4 = 0;
timer5 = 1;

Ainsi que l’initialisation de la communication CAN :

/* Enable ECAN1 Interrupt */
IEC2bits.C1IE=1;
/* enable Transmit interrupt */
C1INTEbits.TBIE=1;
/* Enable Receive interrupt */
C1INTEbits.RBIE=1;
/* Enable Error interrupt */
C1INTEbits.ERRIE = 1;
canTxMessage.message_type=CAN_MSG_DATA;
canTxMessage.frame_type=CAN_FRAME_STD;

Avant de rentrer dans la boucle principale while(1) :

if(send_can == 1)
{
send_can = 0;
sendECAN(&canTxMessage);
}

Si une demande de communication est réalisée par l’un des chaines, la structure remplie est transmise. Plusieurs demandes concourantes s’écrasent, mais la rapidité du système évite (en règle générale) la perte d’information.

if(traite_IR == 1)
{
traite_IR = 0;
switch(current_IR)
{

Un switch est réalisé pour exécuter un code spécifique à chaque chaîne <<ADU : Optimiser avec une variable et un seul traitement>>

case 1 :
detecteur[0] = calculate_angle(count_IR[0][0], count_IR[0][1]);
if (detecteur[0] > 0)
{
memory_detecteur[0] = detecteur[0];
}
break;
case 2 :
detecteur[1] = calculate_angle(count_IR[1][0], count_IR[1][1]);
break;
case 3 :
detecteur[2] = calculate_angle(count_IR[2][0], count_IR[2][1]);
if (detecteur[2] > 0)
{
memory_detecteur[2] = detecteur[2];
}
break;
case 4 :
detecteur[3] = calculate_angle(count_IR[3][0], count_IR[3][1]);
break;
}
}

La fonction calculate_angle est détaillée ci-dessous, c’est elle qui identifie les balises en fonction des rapports cycliques (count_IR[i][0] pour l’état haut et count_IR[i][1] pour l’état bas). Pour rappel, le sens de détection est l’inverse de celui d’émission (le récepteur IR fonctionne en logique inversée).  

int calculate_angle(int count_a, int count_b)
{

On gère ici les différents cas possibles en fonction des mesures. La longueur de l’intervalle peut être allongée pour augmenter le nombre de balises détectables, au détriment de la précision (on augmente en effet le risque de déborder d’une durée caractéristique si cette dernière est trop courte).

if((count_a < 5000) && (count_a > 4000)) //[800 - 1000] µs
{
if((count_b < 5000) && (count_b > 4000)) //[800 - 1000] µs
{
// Balise B
return 2;
}
else if((count_b < 9500) && (count_b > 8500)) //[1700 - 1900] µs
{
// Balise C
return 3;
}
else
{
// -1 est le code erreur, la balise détectée est inconnue
return -1;
}
}
else if((count_a < 9500) && (count_a > 8500)) //[1700 - 1900] µs
{
if((count_b < 5000) && (count_b > 4000)) //[800 - 1000] µs
{
// Balise A
return 1;
}
else
{
return -1;
}
}
else if((count_a < 7250) && (count_a > 6250)) //[1250 - 1450] µs
{
if((count_b < 7250) && (count_b > 6250)) //[1250 - 1450] µs
{
// Balise D
return 4;
}
else
{
return -1;
}
}
else
{
return -1;
}
}

Il est nécessaire de recalculer (finement) toutes les valeurs de test si l’on change les types de balises (ie leur rapport cyclique). Le tableau ci-dessous résume la configuration présente (40 MHz) :

Fréquence 40 MHz 5 MHz
Période 25 ns 200 ns

Période du timer (µs)

0,2

1,6

900

800

4000

500

1000

5000

625

1350

1250

6250

781,25

1450

7250

906,25

1800

1700

8500

1062,5

1900

9500

1187,5

Driver moteur pas à pas