CH32V003 : introduction aux timers

Ce que nous allons voir

Les timers sont un des périphériques les plus importants, il est difficile d'imaginer une application réelle qui n'en ait pas besoin. Ils offrent de nombreuses fonctionnalités, ce qui en fait des outils complexes et donc d'un abord difficile pour un débutant qui voudrait comprendre leur fonctionnement et leur utilisation en lisant le manuel de référence du micro-contrôleur.

Nous allons passer en revue en les simplifiant à l'extrême les constituants essentiels d'un timer afin de vous permettre de bien comprendre leurs fonctions et leurs relations. Les notions abordées seront générales, donc transposables à tout micro-contrôleur.

Nous terminerons en parlant en termes généraux des timers du CH32V003. A la fin de ce cours, vous serez en mesure de comprendre les chapitres du manuel de référence relatifs aux timers. Les cours suivants aborderont de manière pratique et détaillée différents cas d'utilisation des timers.

Le timer le plus simple du monde

Fondamentalement, un timer est un compteur programmable sur lequel viennent se greffer plus ou moins de fonctionnalités supplémentaires.

Un compteur est un registre dont le contenu est modifié au rythme d'un signal d'horloge, en l'incrémentant à chaque impulsion. Lorsque La valeur du registre atteint le maximum représentable (ex. 255 pour un registre de 8 bits), la prochaine impulsion le fera revenir à 0. Le terme anglais pour désigner cet événement est "counter wrap".

Un compteur programmable permet de déclencher le retour à 0 à une valeur différente du maximum représentable, la "reload value". Si on fixe cette valeur à 131, toujours pour un compteur de sur bits, lorsque le compteur contiendra 131 et recevra une nouvelle impulsion d'horloge il passera à 0 au lieu de 132.

Un compteur peut aussi compter "vers le bas", c'est-à-dire partir de la reload value et la décrémenter jusqu'à 0, puis recharger la reload value lors de l'impulsion d'horloge suivante.

L'événement de "counter wrap" peut soit provoquer le changement d'état d'une broche de sortie du micro-contrôleur, soit déclencher une interruption afin d'exécuter un traitement périodique.

On retrouve ces éléments dans le diagramme ci-dessous. Les flèches brisées représentent des événements. Ici, elles marquent les counter wraps vers le haut ou vers le bas. Le U est l'initiale de "Update event", événement de mise à jour du compteur.

Le signal d'horloge commandant le comptage peut provenir de différentes sources internes et externes. On utilise souvent l'horloge principale du micro-contrôleur comme horloge pour le timer. Dans le cas du CH32V003, celle-ci est au maximum de 48MHz.

Il peut cependant arriver qu'on ait besoin de générer des interruptions à un rythme relativement lent. Or, les timers du CH32V003 utilisent un compteur sur 16 bits, ce qui fait que le délai le plus long entre deux interruptions consécutives est de 65536 / 48000000 ≈ 1.37ms. Si on veut un délai de l'ordre de la seconde, on devra réduire la fréquence d'horloge du timer sans toucher à celle du micro-contrôleur.

On ajoute pour cela au timer un autre compteur programmable qui aura pour fonction de pré-diviser l'horloge du micro-contrôleur avant de la fournir au timer. Le terme anglais pour ce diviseur de fréquence est "prescaler". Le timer le plus simple du monde tout en restant utile ressemble donc au diagramme suivant :

Timers multifonctions

En développement embarqué, on a très souvent besoin de faire certaines choses pour lesquelles un timer est indispensable, au point où il est pertinent de réaliser ces fonctions au niveau du hardware sous forme d'extensions du timer minimal que nous venons de voir. Ces fonctions sont :

  • La mesure de durées. Exemples :

    • Un capteur de distance à ultra-sons fournit une impulsion de durée proportionnelle à la distance mesurée.

    • Le décodage du signal d'une télécommande infra-rouge nécessite de mesurer la largeur de différentes impulsions.

  • La génération de signaux. Exemples :

    • On utilise très fréquemment la modulation de largeur d'impulsion (PWM, Pulse Width Modulation), par exemple pour contrôler la vitesse d'un moteur ou la position d'un servo.

      La PWM consiste à faire varier le rapport cyclique ("duty cycle" en anglais) d'un signal de fréquence fixe. Le rapport cyclique est le quotient de la durée pendant laquelle le signal est haut par la période du signal.

    • On a parfois besoin de produire une impulsion unique de largeur précise. Utiliser un timer permet à la fois de décharger le programme principal et de garantir la précision de la durée.

  • La mesure d'une position angulaire.

    On utilise souvent des encodeurs rotatifs pour mesurer la position angulaire relative de l'axe d'un moteur. Ces dispositifs envoient 2 impulsions en quadrature (décalées d'un quart de période) qui servent d'horloge au timer et permettent de savoir de combien l'axe du moteur a tourné et dans quel sens.

Les timers présents dans la plupart des micro-contrôleurs sont donc en réalité des timers multifonctions très configurables. C'est le fait d'intégrer ces extensions au timer qui fait sa complexité, ou en tous cas celle de sa documentation.

La fonction PWM (ou "output compare")

Dans la fonction PWM, la ligne de GPIO associée au timer est utilisée en sortie, sur laquelle on produit un signal de fréquence fixe dont on contrôle le rapport cyclique. On part d'un état initial (0 ou 1) sur lequel on reste jusqu'à un point de basculement où on inverse l'état de la sortie.

Sur ce diagramme, arr représente la valeur du "auto-reload register" du timer simple, c'est-à-dire la valeur après laquelle le compteur revient à 0, et ccr la valeur du compteur à laquelle la sortie change d'état. On voit donc qu'il suffit de faire varier ccr pour modifier le rapport cyclique du signal de sortie. On notera aussi que c'est la valeur du prescaler multipliée par arr qui définit la fréquence du signal PWM par division de la source d'horloge du timer.

Le diagramme suivant décrit la structure de la fonction PWM. Le bloc marqué "Counter" représente le compteur de notre timer minimal utile précédent, dont le prescaler et l'auto-reload register n'ont pas été représentés pour ne pas sucharger le dessin.

Ce que nous avons appelé ccr sur le chronogramme correspond ici à la valeur du "compare shadow register". Lorsque le compteur du timer atteint ou dépasse cette valeur, la sortie change d'état.

On utilise ici 2 registres, le "preload" et le "shadow" pour pouvoir choisir quand le shadow register est mis à jour. Si la mise à jour est immédiate, les conséquences sur l'état de la sortie sont indéfinies. Par exemple, si on augmente la valeur de ccr alors que la sortie a déjà changé d'état, elle reviendra à l'état initial et changera de nouveau d'état au cours du même cycle de comptage.

Si la sortie PWM sert à commander la luminosité d'une LED, ça n'a aucune importance, mais on souhaite généralement se prémunir contre de tels aléas et on choisit de synchroniser la mise à jour du shadow register avec le retour à 0 du compteur, d'où l'utilité de ces deux registres.

La fonction "input capture"

Dans la fonction de capture, la ligne de GPIO associée au timer est utilisée en entrée et on mesure le temps écoulé entre deux événements successifs du signal d'entrée.

Le diagramme suivant décrit la structure minimale de la fonction capture. Le bloc marqué "Counter" représente le compteur de notre timer minimal utile précédent, dont le prescaler et l'auto-reload register n'ont pas été représentés pour ne pas surcharger le dessin.

Le bloc marqué "Edge detector" détecte les changements d'états de l'entrée. Par configuration, on choisit de mesurer soit le temps entre 2 fronts montants successifs, soit entre 2 fronts descendants. Pour mesurer le temps entre un front montant et le front descendant suivant (ou l'inverse), il faudrait utiliser 2 canaux de capture traitant le même signal d'entrée.

Là encore, l'utilisation de 2 registres "preload" et "shadow", mais dans l'autre sens, cette fois, permet de démarrer une nouvelle capture tout en préservant la dernière valeur lue pour que le programme puisse en prendre connaissance.

A chaque fois qu'un événement de capture survient, la valeur du compteur est mémorisée et une interruption est déclenchée. La routine d'interruption lit alors la valeur du compteur, calcule le temps écoulé depuis la dernière capture et mémorise la valeur du compteur pour la prochaine interruption.

Si on veut capturer des durées longues, il faut en outre compter les débordements ("wrap") du compteur et utiliser ce compte dans le calcul des durées. Nous le ferons dans un des cours suivants.

Dans la pratique, il arrive souvent que les signaux d'entrée soient perturbés. On fait donc précéder le bloc edge detector par un filtre dont la fonction est de s'assurer que le signal d'entrée reste stable pendant un certain nombre (configurable) de cycles d'horloge avant de l'utiliser.

Le diagramme de la fonction de capture, toujours très simplifié mais un peu plus proche de la réalité, devient donc :

Les timers du CH32V003

Le CH32V003 dispose de 2 timers :

  • Le general-purpose timer (GPTM), un timer à usage général, également appelé TIM2.

  • Le advanced control timer (ADTM), également appelé TIM1, un surensemble du GPTM adapté à la commande de dispositifs de puissance.

Chaque timer dispose de 4 canaux, c'est-à-dire que 4 ensembles indépendants de PWM/capture se partagent le même compteur. Cela signifie qu'on peut sur un même timer avoir, par exemple, un canal configuré en PWM et un autre en capture. C'est ce que montre le diagramme suivant extrait du chapitre du manuel de référence sur le ADTM :

Le ADTM dispose de toutes les fonctionnalités du GPTM plus :

  • Le PWM du ADTM offre des sorties complémentaires sur ses 3 premiers canaux, avec temps mort programmable. Les sorties complémentaires sont inversées l'une par rapport à l'autre et le temps mort est le petit délai qui sépare les changements d'état des sorties complémentaires.

    Sur ce diagramme, OCxREF représente l'état interne du canal PWM, OCx la broche de sortie "positive", OCxN la broche de sortie "négative" (ou complémentaire), et "delay" correspond au temps mort inséré pour éviter que les 2 broches de sortie soient au même état pour un bref instant au moment des transitions, ce qui causerait des court-circuits fatals dans les circuits de puissance commandés par ces sorties.

  • Le PWM dispose d'une fonction "brake", qui consiste en une entrée de GPIO reliée à un détecteur d'anomalies et/ou un arrêt d'urgence. Lorsque cette entrée est activée, les sorties PWM sont forcées dans un état de sécurité ("safe state") jusqu'à nouvel ordre.

  • Le compteur principal de l'ADTM est associé à un "repeat count register" qui compte le nombre de fois où le compteur principal déborde (wrap) et qui génère un événement lorsqu'il atteint une valeur configurée.

  • L'ADTM dispose de routines d'interruptions spécialisées :

    • TIM1_BRK_IRQHandler : événements "arrêt d'urgence" (brake)
    • TIM1_UP_IRQHandler : événements de mise à jour
    • TIM1_TRG_COM_IRQHandler : événements de déclenchement du compteur (trigger)
    • TIM1_CC_IRQHandler : événements de capture

    alors que pour le GPTM, tout est géré par la même routine d'interruption globale, TIM2_IRQHandler. Ce point n'apparaît pas clairement dans la documentation et ne fait l'objet d'aucun exemple dans le SDK, donc il était important de le signaler ici.

  • L'ADTM est sur le bus interne APB2 alors que le GPTM est sur le bus interne APB1. Ceci affecte la mise en service du périphérique, c'est-à-dire l'appel de la fonction RCC_APB1PeriphClockCmd() ou RCC_APB2PeriphClockCmd().

Ce que nous avons appris

Vous savez maintenant :

  • Ce qu'est un timer et quelles sont ses fonctions et utilisations.

  • Ce qu'est le PWM et comment ça fonctionne.

  • Ce qu'est la capture et comment ça fonctionne.

  • Ce que les timers du CH32V003 ont de particulier.

Félicitations, vous êtes maintenant capable de lire les chapitres sur les timers dans le manuel de référence et de les comprendre ! :)


Copyright (c) 2024 Vincent DEFERT - Tous droits réservés