«

»

Jan 05

Ada & timer

Hola amigos,

Aujourd’hui au programme, une introduction aux timers ! Un timer est un periphérique qui va créer des unités de temps, c’est à dire va émettre des impulsions à intervalles réguliers. A quoi cela va-t-il servir ? Nous allons pouvoir générer des valeurs de tensions différentes de GND et VCC grâce aux PWM (voir plus loin), ce qui va permettre par exemple de contrôler la vitesse d’un moteur ou encore l’intensité d’une led.

Comme tous les périphériques que nous avons vu jusqu’ici, il faut l’initialiser. Nous avons en tout 14 timers sur notre carte, chacun composé de 4 channels. Pour générer les timers, nous disposons du package Stm32.Timer. Commençons donc par définir les paramètres d’un timer. Attention, certaines caractéristiques ne sont disponibles que sur certains timers, il faut donc lire la datasheet pour être sur de faire quelque chose de légitime. Je vous laisse vous référer à ce manuel. Nous allons survoler ces paramètres.

Ces derniers sont condensés dans le type Timer_Params. C’est un record où il nous faut définir les champs suivants :

  • Prescaler : C’est la division de l’horloge d’entrée (qui dépend du champ Clock_Division) qui permet de réduire la fréquence reçue d’un entier sur 16bits + 1.
  • Period : C’est la période sur laquelle le timer va compter. Chaque timer possède un compteur qui permet de se repérer dans le temps. Ce compteur possède une valeur limite. Une fois cette valeur atteinte, on a la possibilité de déclencher une interruption.
  • Counter_Mode : C’est la manière dont le timer va compter. Il peut compter de façon croissante (Up), décroissante (Down) ou croissante puis décroissante en déclenchant un interruption quand on diminue (CenterAligned1), augmente (CenterAligned2) ou les deux (CenterAligned3).
  • Clock_Division : Définit de combien l’horloge est divisée avant de rentrer dans le timer. Attention, toutes les divisions ne sont pas disponibles sur chaque timer. On peut choisir de laisser l’horloge telle quelle (Div_1), de la diviser en 2 (Div_2) ou en 4 (Div_4).
  • Repetition_Counter : c’est le nombre de fois que l’on va compter jusqu’au seuil. Il s’agit d’un entier non signé sur 8 bits (Unsigned_8). Cette option n’est pas disponible sur tous les timers (que sur TIM1 pour la stm32f4-discovery).

Maintenant que nous savons quels sont les paramètres d’un timer, nous allons les appliquer. Pour cela, nous disposons de la fonction Init_Timer. Elle prend quatre paramètres..
Tout d’abord, il faut lui donner le numéro du timer à initialiser. Il s’agit d’un Timer_Number, c’est à dire un entier entre 1 et 14 inclus. Ensuite il faut lui donner les caractéristiques du timer comme nous les avons défini juste au dessus.Puis il faut lui indiquer la source de l’interruption de va déclencher le périphérique, c’est à dire sur quel évènement se fier pour signaler au programme qu’il est l’heure de se réveiller. On a de nombreux choix. Pour commencer, on peut tout simplement ne pas utiliser
d’interruption. Dans ce cas, on choisit Disable. Ensuite on peut en déclencher une quand le compteur atteint sa valeur limite et se recharge (Update). On peut aussi interrompre le programme quand on atteint une certaine valeur définie dans
les channels (voir plus bas). Dans ce cas on choisit entre CC1, CC2, CC3 et CC4 selon le channel. On a ensuite divers autres événements que ne sont pas forcément définis sur tous les timers et toutes les cartes : COM (communication), Trigger et Break.
Enfin, le dernier paramètre de la fonction est la priorité de l’interruption. Elle est de type IRQ_Priority, c’est à dire un entier entre 0 et 15 inclus. C’est ce nombre qui va définir quelles sont les interruptions à exécuter en premier.

Maintenant que nous savons utiliser un timer, mettons nos connaissances en application. Nous allons faire un programme qui initialise un timer et va lire les valeurs du compteur. Quand on atteint une certaine valeur, on change l’éclairage d’une led. Pour aller lire la valeur du compteur, on peut utiliser la fonction Get_Counter qui prend en argument le numéro du timer à scruter.

 

 

 

Que remarque-t-on ? La led ne clignote pas de façon régulière ! En effet, il se peut que nous rations la valeur du compteur recherchée. De plus, à cette vitesse, le clignotement doit être visible à l’oeil nu, je vous laisse imaginer ce qui ce produirai si nous accélérions. La solution : les interruptions ! Quand nous définissons le handler, il ne faut pas oublier de d’effacer l’interruption en sortant de la fonction. Pour cela, nous disposons de la fonction Clear_Interrupt qui prend deux arguments : le numéro du timer et la source de l’interruption (définie plus haut). Au passage, il existe un fonction pour réinitialiser un timer avec ces paramètres par défault : Reset_Timer. Elle prend en argument le numéro du timer.  

 

 

Faisons quelques tests : changez la valeur du prescaler pour ne plus pouvoir distinguer le clignotement. Que constatez vous par rapport à un allumage classique de led ? La luminosité est moindre. Deux fois moins pour être exact. Que se passe-t-il ? L’oeil est un filtre passe bas : il est insensible aux hautes fréquences, aux changements très rapides. C’est ce principe qui est utilisé au cinéma avec les célèbres 24 images par secondes : on a l’illusion d’un mouvement continue. Nous utilisons le même phénomène avec notre led. Elle est allumée la moitié du temps mais nous faisons une moyenne : nous la voyons deux fois moins éclairée. Ce principe fonctionne aussi avec les moteurs : l’inertie fait que ce dernier n’est pas directement sensible au variations trop rapides du courant. Par contre, il fait des moyennes.

Diviser pour 2 la tension c’est bien beau, mais nous voulons aussi pouvoir la diviser par 4,6, 15, 33, 42,… Bref (presque) toutes les valeurs possibles. Nous pourrions alors contrôler avec précision l’intensité de notre led, ou la vitesse de notre moteur. Comment faire ? Certains s’écrirons : ajoutons un compteur au programme et comptons ! Cela peut marcher dans certains cas, mais souvent, le même problème que plus haut se présente : nous pouvons rater des mises à jour et nous perdons en précision. Heuresement, les choses sont bien faites : nous avons un élément au niveau matériel qui va compter pour nous : nous allons utiliser des PWMs.

LES PWMs

Qu’est ce qu’un PWM ? Pour en connaitre plus les détails, je vous laisse consulter le cours de Félix sur le sujet. En bref, un PWM va nous permettre de passer de données discrètes (des 0 et des 1) dans notre mémoire à des données semblant continues (les valeurs entre 0 et 1). Je dis semblant car tous va dépendre de la sensibilité du matériel qui reçoit cette valeur : un oeil, un moteur, une photodiode,…

 

Nous allons ici introduire la notion de channels, déjà mentionnée plus haut. Chaque timer en a quatre. Ils vont nous ajouter pleins de fonctionnalité gérée au niveau matériel et donc qui ne ralentissent pas l’exécution de notre programme. C’est grâce à eux que nous allons créer des PWMs. Qui plus est, ils sont directement reliés à nos pins : plus besoin de décrire les tensions au niveau de programme.
Un channel est branché à des pins qui lui sont propres : on ne peut pas utiliser un channel n’importe où. La documentation nous indique quel channel va où. De plus, un channel dispose de deux sorties dites complémentaires : quand l’une est
à l’état haut, l’autre est à l’état bas et vice versa. Voyons maintenant quels sont les paramètres d’un channel. Ils sont regroupés dans le type Output_Channel_Params. Il faut définir les champs de ce record, qui sont :

  • Pulse : C’est la longueur de l’impulsion que va générer le channel. On l’appelle aussi la valeur de comparaison. C’est un entier non signé sur 32 bits (Unsigned_32). C’est grâce à cette valeur que l’on va pouvoir définir le duty cycle de notre sortie (DC = valeur_comparaison/Period_timer).
  • Mode : il s’agit du mode dans lequel on va utiliser le channel. Nous avons plusieurs choix. Nous pouvons choisir simplement de compter : c’est le mode Timing. Il sert par example quand on veut utiliser des interruptions nous le faisions pour les timers. Nous avons ensuite les modes Active et Inactive : le premier met la pin à l’état haut quand la valeur de comparaison est atteint alors que le deuxième la met à l’état bas. Toggle change la inverse la valeur de la pin quand la valeur de comparaison est atteinte. Enfin nous avons les modes PWM1 et PWM2. Ils diffèrent sur l’emplacement des parties à l’état haut. En mode PWM1, la pin est à l’état haut tant qu’on est en dessous ou égal à la valeur de comparaison si l’on compte de manière croissante (elle est à l’état bas sinon). Si l’on compte de manière décroissante, nous avons un état inactif tant qu’on est supérieur ou égal à la valeur de comparaison (actif sinon). En PWM2, c’est l’inverse (changer haut/bas, actif/inactif).
  • Output_State : Activation de la sortie du channel, de type OC_State_Type. On a le choix entre Disable et Enable.
  • Output_N_State : idem pour la sortie complémentaire mais de type OC_N_State_Type.
  • Polarity : C’est la polarité de la sortie, de type OC_Polarity_Type. On peut choisir entre High (haute) et Low (basse).
  • N_Polarity : idem mais de type OC_N_Polarity_Type.
  • Idle_State : Etat de la sortie au repos, de type OC_Idle_State. On peut choisir entre Reset pour désactiver la sortie et Set pour la mettre à la valeur de la polarité.
  • N_Idle_State : Idem pour la sortie complémentaire, mais de type OC_N_Idle_State.

 

Nous savons quels sont les paramètres, la fonction pour les appliquer est Setup_Output_Channel. Elle prend trois arguments : le numéro du timer, le numéro du channel et les paramètres. Essayons nous à un petit example : programmons la carte pour que les quatres leds aient des intensités de 25%, 50%, 75% et 100% grâce aux channels. On peut même réaliser une led qui change en continu son intensité. Pour cela, changeons la valeur de comparaison grâce à la fonction Set_Compare qui prend en argument le numéro du timer, celui du channel et la nouvelle valeur de comparaison (un Unsigned_32).
 


 

Voilà, vous devez maintenant être devenu des experts des timers. N’oubliez pas de pratiquer pour mieux comprendre. N’hésitez pas non plus à lire des parties de la documentation de la carte ou de l’API.

A+,

Julien