Le monde des micro contrôleurs

Schémas

Périphériques des micro contrôleurs

Éléments de programmation du 12F509

Éléments de programmation du 12F675

Éléments de programmation du 16F1825

Création d'un projet sous MPLAB IDE

Périphériques

Sous le nom "périphériques", je décris ici les dispositifs commandés ou lus par le MCU.


1 - La LED

C'est la première utilisation à laquelle on pense lorsqu'on veut réaliser une animation : allumer différentes lumières. Il existe des ampoules à filament miniatures, mais le composant le plus utilisé dans ce rôle est la diode électroluminescente, plus connue sous son acronyme anglais "LED" (Light Emitting Diode).

Contrairement à une ampoule, une LED est polarisée, c'est à dire qu'une de ses broches, appelée "anode", doit être reliée au pôle positif, l'autre, appelée "cathode", au pôle négatif. En général, la broche la plus longue est l'anode (broche positive).

Pour pouvoir émettre de la lumière, une LED doit être traversée par un courant constant. Ce courant est une des données techniques de la LED, l'autre donnée à prendre en compte est la chute de tension à ses bornes (UL).

Les LEDs standards travaillent avec un courant compris entre 10 et 20 mA et une valeur de UL d'environ 2 V. Ces valeurs varient suivant la couleur, elles sont fournies avec la fiche descriptive de la LED.

Pour limiter le courant alimentant une LED, quand on a une tension supérieure à UL, le moyen le plus simple est d'intercaler une résistance, du côté positif ou de l'autre, ça n'a pas d'importance.

Pour calculer la valeur de cette résistance, on applique la loi d'Ohm sous sa forme :

R = U / I

Dans notre cas, la tension U à prendre en compte est en réalité la tension d'alimentation (Ubat) diminuée de UL. La formule devient donc :

R = (Ubat - UL) / I

où R est en ohms, Ubat et UL en volts et I en ampères.
La valeur de R sera arrondie à la valeur supérieure dans la série E12 (10, 12, 15, 18, 22, 27, 33, 39, 47, 56, 68, 82), pour s'approvisionner facilement.

Prenons un exemple concret : nous avons une LED rouge, fonctionnant avec une intensité (I) de 10 mA (0.01 A) et ayant une chute de tension (UL) de 2 V. Nous l'alimentons avec une tension (Ubat) de 5 V.

La valeur de la résistance sera : (5 - 2) / 0.01 = 300 que nous arrondissons à 330 ohms.

Le schéma électronique correspondant est le suivant :


2 - Le transistor MOSFET

Le MOSFET (Metal Oxide Semiconductor Field Effect Transistor) est un transistor, il a trois broches, appelées G (Gate ou Grille), S (Source) et D (Drain). Une tension appliquée entre les broches G et S permet de commander le passage d'un courant entre les broches S et D.

Comme on peut le voir sur le schéma, la grille est isolée électriquement des autres électrodes, ce qui donne une impédance (résistance) d'entrée très élevée, de l'ordre de 1018 ohms (1 suivi de 18 zéros).

En pratique, on pourrait considérer que le transistor est commandé par une tension et non par l'intensité entre grille et source. Ce n'est pas tout à fait le cas : l'entrée G est en réalité un condensateur de faible capacité et un courant circule au moment où la tension est appliquée. On n'en tient pas compte en général, mais, si on veut que le transistor réagisse très rapidement, il faut en tenir compte et ne surtout pas intercaler une résistance sur l'entrée, qui jouerait le rôle d'un retardateur.

Par contre, ce condensateur est le seul point faible du MOSFET : l'isolant est très mince et peut être détruit si on applique une tension trop élevée entre les broches G et S (20 V pour un BS170).

La courbe de gain (variation de la tension d'entrée commandant le courant de sortie) est très abrupte, passé un certain seuil de tension, le transistor laisse passer le courant avec une résistance de passage très faible, de l'ordre du 1/10ème d'ohm. En utilisant les sorties d'un MCU (0 ou 5 V), le transistor se conduit comme un interrupteur.

Avec un boîtier de taille réduite, le MOSFET permet de faire passer des intensités relativement élevées, par exemple 500 mA en continu pour le BS170 en boîtier TO92.

Du fait de son impédance d'entrée élevée et de sa faible résistance de passage, il est possible de brancher plusieurs MOSFETs en parallèle pour augmenter l'intensité commandée, les intensités de sortie se répartissent en fonction de la résistance de passage de chaque transistor et aucun composant additionnel n'est nécessaire.

L'examen du schéma montre qu'une diode est intégrée entre les broches S et D, elle sert de protection lorsqu'on commande une charge inductive, telle qu'un électro-aimant, un relais ou un moteur électrique.

Les tensions d'entrée et de sortie peuvent être de sources différentes, mais les masses doivent être communes, comme dans le schéma ci dessous :


3 - Le branchement des LEDs

Nous avons vu que brancher une seule LED sur une sortie du MCU est facile. Le problème se complique un peu lorsqu'on veut en brancher plusieurs.

Il existe deux possibilités : le branchement en série ou le branchement en parallèle.

Pour les exemples ci-dessous, j'utilise les mêmes LEDs rouges que précédemment, avec UL = 2 V et I = 0.01 A (j'en ai tout un stock).

Le branchement en série

Ce branchement est à utiliser quand toutes les LEDs ont les mêmes caractéristiques de tension (UL) et d'intensité, la valeur de la résistance intercalaire est donnée par la formule :

R = (Ubat - (UL * nb_LED)) / I

Il y a toutefois une limite : la tension Ubat doit être supérieure ou égale à la somme des tensions UL, lorsque la tension Ubat est égale à la somme des UL, la résistance n'est pas nécessaire :

Le branchement en parallèle

C'est le schéma de base, chaque LED a sa propre résistance, et peut donc avoir des caractéristiques différentes des autres.

On peut vouloir économiser toutes ces résistances et n'en utiliser qu'une seule, dans le cas où toutes les LEDs sont de mêmes caractéristiques :

La valeur de la résistance intercalaire est donnée par la formule :

R = (Ubat - UL) / (I * nb_LED)

Il y a, là encore, une limitation qui est la puissance maximale que peut dissiper la résistance. Cette puissance est calculée par la formule :

P = U * I

P est en watts (W), U est la tension aux bornes de la résistance (Ubat - UL) en volts (V) et I est l'intensité en ampères (A).

La puissance maximale que peut dissiper une résistance standard est 0.5 watt. Dans le schéma ci-dessous, la somme des intensités est égale à 0.06 A, La tension aux bornes de la résistance est égale à 10 V, la puissance qu'elle doit dissiper est donc :

10 V * 0.06 A = 0.6 W, ce qui est trop important, pas de beaucoup, mais la résistance va chauffer et se détériorer dans le temps.

Donc, sauf cas particulier, il est préférable d'utiliser une résistance par LED lors des branchements en parallèle.


4 - Le choix d'un MOSFET

Jusqu'à présent, je n'ai parlé que d'un modèle de transistor : le BS170, un MOSFET de type canal N.

Les MOSFETs canal N sont les plus courants et les plus faciles à mettre en oeuvre : à partir d'une tension de commande faible, ils agissent comme des interrupteurs entre la tension de référence (0 V) et le dispositif à commander, sans nécessiter de composant supplémentaire.

Il existe aussi des MOSFETs de type canal P, complémentaires des types N, dont je parle plus loin.

Si vous voulez approfondir la théorie, vous pouvez lire l'article sur les MOSFETs dans Wikipedia. Mais cela n'a rien d'une obligation, les composants fonctionneront quand même !

Revenons au BS170 qui peut commander un courant jusqu'à 500 mA. Si vous avez besoin d'un courant supérieur, il vous donc faut choisir un autre composant.

Les principaux critères de choix sont l'intensité et la tension maximales. Il vous faut donc, en première approche, sélectionner quelques modèles dans le catalogue de votre fournisseur préféré, puis rentrer dans les détails en consultant leurs fiches de caractéristiques (datasheets en anglais). Il n'existe pas de datasheet en français, mais il n'y a pas besoin d'être un expert pour les lire.

Les caractéristiques à prendre en compte sont :

  • VDS : tension maximum d'alimentation entre la Source et le Drain.
  • VGS : tension maximum entre la Gate et la Source.
  • ID : intensité maximale supportée, il vaut mieux prévoir large !
  • IDSS : intensité consommée lorsque la Gate est fermée (0 V), c'est le courant de fuite lorsque l'interrupteur est ouvert, il faut qu'elle soit la plus basse possible.
  • RDS(on) : résistance de passage lorsque l'interrupteur est fermé.
    • Cette valeur est importante car elle n'est pas nulle, lorsque le transistor conduit, il va dissiper de la puissance sous forme de chaleur. Pour connaître cette puissance, en watts, on applique la formule P = RDS(on) * I2.
    Toutes autres valeurs égales par ailleurs, il vaut mieux choisir la plus faible valeur possible pour cette caractéristique.
  • VGS(th) : tension de déclenchement de la Gate, plus elle est basse, meilleur c'est pour nos applications en faible tension.

D'autres caractéristiques sont à vérifier, qui concernent les temps de fermeture ou d'ouverture de la liaison Source/Drain (td(on) , tr, tf et td(off)), tant qu'elles sont exprimées en nanosecondes, c'est bon.

Bonne recherche dans les datasheets !


5 - Le transistor MOSFET canal P

Nous avons vu que le MOSFET canal N conduit lorsqu'on applique une tension positive sur la broche G et qu'il agit comme un interrupteur polarisé sur le 0 V. Cela convient pour la plupart des cas, mais, quelquefois, il est nécessaire que le transistor conduise lorsque la tension de commande est inférieure à + V, par exemple pour interrompre une alimentation du côté positif ou pour commander un montage de pont en H (dont je parle plus loin).

Le composant permettant cela est le MOSFET canal P, qu'on appelle complémentaire du canal N. Lorsqu'on regarde sa datasheet, on constate que les tensions (et les intensités) sont exprimées avec des valeurs négatives. Avec ce transistor, la tension de référence (0 V) est mesurée à partir du pôle positif de l'alimentation. Lorsque cette tension de référence est appliquée à la Gate, le transistor est bloqué, pour qu'il conduise, il faut lui appliquer une tension inférieure à sa caractéristique -VGS(th). Le composant à commander sera branché entre le pôle négatif de l'alimentation (- V) d'un côté et au Drain du transistor de l'autre côté.

J'en vois dans le fond qui ont l'air de n'avoir pas tout compris bien !


Faisons le parallèle avec un MOSFET canal N, utilisé pour commander un moteur, le schéma est le suivant :

Lorsqu'on applique une tension positive sur la broche G, le transistor conduit et le moteur reçoit du 0 V sur la borne reliée à la broche D. Comme l'autre borne est reliée au + V, le moteur tourne.


Voyons maintenant le schéma pour commander ce même moteur avec un MOSFET canal P :

Comme on peut le voir, tout est inversé par rapport au canal N : la source est branchée côté +0 V et le drain côté -5 V (à travers le moteur).

(À noter que j'ai écrit +0 V pour bien marquer que cette ligne est reliée au pôle positif de l'alimentation et donc le -5 V est relié au pôle négatif.)

Le transistor conduit lorsque la broche G reçoit une tension négative par rapport à la référence 0 V. Le moteur va alors recevoir du 0 V sur la borne reliée à la broche D et du -5 V sur l'autre, il va donc tourner !

À part les inversions des signes de polarité, les deux schémas sont très proches.


Par contre, les choses vont se compliquer lorsqu'on utilise un MOSFET canal P dans un circuit ayant deux alimentations de tensions différentes et dont les masses (pôles négatifs de l'alimentation) sont communes. Dans ce cas, la tension de commande risque de ne pas être assez proche du +0 V pour bloquer le transistor. Il est alors nécessaire d'ajouter une résistance dite de "pullup" qui va forcer le blocage en l'absence de signal de commande et un interrupteur, activé par la tension de commande, servant à envoyer le -V sur la broche G :

Lorsque l'interrupteur S est ouvert, la résistance de pullup de 10 K envoie du +0 V sur la broche G et le transistor est bloqué.
Lorsque l'interrupteur S est fermé, il envoie du -V sur la broche G et le transistor conduit.

Plutôt que d'employer un interrupteur mécanique commandé par une tension, nous allons utiliser un petit MOSFET canal N, qui est tout indiqué pour cet emploi :


6 - Les pilotes (drivers) de MOSFET

Avant d'entrer dans le vif du sujet, je reviens sur le dernier schéma du MOSFET canal P pour indiquer les tensions sous une forme plus conventionnelle :

Dans ce schéma, le petit MOSFET et la résistance agissent comme un pilote de MOSFET (MOSFET driver) inverseur :

Le montage est dit "inverseur" car lorsqu'on applique une tension de 0 V sur l'entrée "in", on obtient une tension positive, égale à la "haute" tension, sur la sortie "out", de même qu'une tension positive sur l'entrée produit une tension de 0 V sur la sortie.

Ce montage peut être repris tel quel pour piloter un gros MOSFET canal N, comme dans le schéma ci-dessous :

Mais pourquoi utiliser un pilote sur un MOSFET canal N, alors qu'il fonctionne déjà très bien en appliquant une tension de +5 V sur la broche G ?

Il y a plusieurs bonnes raisons pour faire cela :

  • On peut vouloir isoler électriquement notre précieux MCU de la haute tension utilisée par le dispositif à commander. S'il y a un problème, c'est le pilote qui claquera.
  • Tous les MOSFETs n'ont pas une tension VGS(th) aussi faible que +5 V, certains nécessitent une tension plus élevée.
  • Un petit MOSFET donne des temps de montée et descente de la tension de sortie bien nets, de l'ordre de quelques nanosecondes.
  • On peut vouloir commander simultanément un MOSFET canal P et un MOSFET canal N avec le même signal.

Toutes ces raisons (et d'autres) font qu'il y a, sur le marché, environ un millier de références de pilotes de MOSFETs en circuits intégrés. L'intégration permet de produire des composants de faible taille, d'excellentes caractéristiques et de plus faible coût (pour le fabricant, pas forcément pour le client ! ) que les composants qu'il remplace.

Parmi cette multitude, j'ai choisi de vous parler de la série des TC4426, TC4427 et TC4428, fabriqués par Microchip. D'abord parce que j'en ai un sous les yeux sur mon bureau, ensuite parce qu'ils ont une architecture relativement plus récente que certains autres modèles.

Le diagramme fonctionnel, extrait de la datasheet, est le suivant :


Nous y trouvons le MOSFET canal N en entrée, la résistance est remplacée par un générateur de courant constant, tout un tas d'autres composants (il faut bien que les concepteurs justifient leurs salaires) et une sortie de type CMOS.

Il y a deux pilotes par boîtier. Le TC4426 a deux pilotes inverseurs, le TC4427 deux pilotes non inverseurs et le TC4428 un pilote inverseur et un pilote non inverseur.

En utilisant la moitié d'un circuit TC4426, le schéma du circuit avec le MOSFET canal P devient le suivant :

L'entrée "in 1" commande la sortie "out 1". Si l'entrée "in 2" n'est pas utilisée, il faut la relier à la masse.


7 - Le pont en H (H-bridge)

Les commandes par MOSFETs que nous avons vues jusqu'à présent ne laissent passer le courant que dans un sens, dans le cas d'un moteur électrique, par exemple, je ne peux donc le faire tourner que dans un sens ou l'arrêter. Le montage que je propose maintenant permet d'inverser la tension au bornes de la charge.

En électromécanique, cela se réalise avec deux interrupteurs inverseurs suivant ce schéma :

Quand I1 est en bas et I2 en haut, le moteur tourne dans un sens, I1 en haut et I2 en bas, le moteur tourne dans l'autre sens, quand I1 et I2 sont tous les deux en haut ou tous les deux en bas, le moteur ne tourne pas.

Ce montage s'appelle un pont en H et des tonnes de littérature ont été écrites à son sujet.

Comme je veux faire un montage avec des MOSFETS, qui ne savent pas inverser les tensions, je modifie ce schéma électromécanique en utilisant des interrupteurs couplés mécaniquement, comme ceci :

Pour obtenir ce genre de montage en électronique, nous allons utiliser, dans chaque branche du H, un MOSFET canal N et un MOSFET canal P. Leur entrées G seront reliées ensemble et leurs sorties D aussi, suivant ce schéma :

Lorsque l'interrupteur S est ouvert, la broche G du MOSFET canal P reçoit du +12 V (+0 V dans sa logique), via la résistance de 10 K et le transistor est bloqué (il ne conduit pas), la broche G du MOSFET canal N reçoit elle aussi du +12 V et le transistor conduit, nous trouvons donc du 0 V sur la sortie "out".

Lorsque l'interrupteur S est fermé, la broche G du MOSFET canal P reçoit du 0 V (-12 V dans sa logique) et le transistor conduit, la broche G du MOSFET canal N reçoit elle aussi du 0 V et le transistor est bloqué, nous trouvons donc du +12 V sur la sortie "out".

Bien évidemment, nous n'allons pas utiliser un interrupteur mécanique, mais un petit MOSFET, comme dans les exemples précédents :

Le schéma complet, avec les petits MOSFETs et les résistances est le suivant :

Lorsque l'entrée "in 1" reçoit du +5 V et l'entrée "in 2" du 0 V, le moteur tourne dans un sens, quand on inverse ces tensions sur les deux entrées, le moteur tourne dans l'autre sens.

Lorsque les deux entrées reçoivent les même tensions, les deux bornes du moteur sont mises en court circuit et, s'il tournait, il est freiné.

Pour terminer, voici le schéma utilisant le pilote de MOSFETs TC4426, permettant de s'affranchir des petits MOSFETs et des résistances :

Avec un boîtier huit broches et quatre MOSFETs, il va être difficile de faire plus dépouillé !


8 - Le bouton poussoir

Quand on parle de périphériques, on pense tout d'abord à ceux de sortie. Un MCU peut aussi recevoir des entrées de l'utilisateur. Le cas le plus fréquent est le bouton poussoir, servant à configurer un mode de fonctionnement, par exemple.

Pour lire la position d'un bouton, on pourrait penser qu'il suffit de configurer un port en entrée, sur lequel on brancherait le bouton et de tester la valeur reçue, 0 ou 1. Les choses ne sont pas aussi simples.

Il faut tout d'abord décider de la tension à appliquer lorsque le bouton est enfoncé. Si on choisit que ce sera le +V, il faut maintenir le port à 0 V quand le bouton est relâché, ce qui se fait en branchant une résistance de quelques kilohms entre le 0 V et le port. Cette résistance s'appelle "pull-down" (tirer vers le bas) et nous fait ajouter un composant supplémentaire.

Heureusement, la plupart des MCU ont la possibilité d'activer des résistances internes, dites de "pull-up" (tirer vers le haut) sur les ports en entrée. Comme leur nom l'indique, elles sont connectées au +V et évitent donc tout composant supplémentaire. Le bouton sera alors connecté au 0 V et, quand il sera enfoncé, le port sera mis à zéro.

Pour activer cette résistance sur le port GP0 d'un PIC 12F675, les instuctions suivantes sont nécessaires :
    nGPPU = 0;                                  // GPIO pull-ups are enabled
    WPU = 0b0000001;                            // enable pull-up on GP0

Un autre problème se pose : un bouton est un élément électromécanique et le contact ne s'établit pas instantanément, mais par rebonds. Pour être sûr que le contact soit bien établi, on peut brancher une résistance et un condensateur sur le port, pour lisser et stabiliser la tension. Ce qui nous fait deux composants supplémentaires. On peut éviter cela par programme en attendant quelques millisecondes pour que la tension sur le port soit bien stable. C'est le but du sous-programme "bounce()" ci-dessous :

unsigned char bounce (unsigned char port)
{
    if (GPIO & port)                            // if port is on
        return 1;                               // return state of port
    __delay_ms (50);                            // wait 50 msec
    return (GPIO & port);                       // return state of port
}

La fonction de bibliothèque "__delay_ms()" attend que le nombre de millisecondes qui lui est passé en paramètre soit écoulé.


9 - Le clavier matriciel

Un clavier est un autre périphérique d'entrée utilisable par un MCU. Il se présente sous la forme d'un panneau de plusieurs touches, qui actionnent des boutons poussoirs.

Voici, par exemple, un clavier de 12 touches qui va nous servir pour la suite :

Les touches sont rangées en quatre rangées, appelées R1 à R4, sur trois colonnes, appelées C1 à C3.

Pour économiser les connexions, les boutons sont branchés en parallèle sur les conducteurs des lignes et des colonnes, suivant le schéma ci-dessous :

Ce genre de montage s'appelle une matrice d'interrupteurs et ce genre de clavier est un clavier matriciel (ou matricé). Il n'y a que 7 connexions, au lieu de 24 si tous les boutons poussoirs étaient indépendants.

En réalité, sur ce clavier particulier, il y a 9 connexions car les colonnes C1 et C2 sont doublées, pour des problèmes de réalisation. Son brochage est indiqué sur la photo ci-dessous :

Pour trouver le bouton qui a été enfoncé, le programme va appliquer successivement une tension sur chacune des rangées et il va lire chaque colonne jusqu'à retrouver la tension correspondante. Pour éviter des composants supplémentaires, nous allons utiliser le 0 V et activer les résistances de "pull-up" sur les entrées C1, C2 et C3.

Par exemple, si j'appuie sur la touche "8", lorsque le programme envoie du 0 V sur la rangée R3, il va trouver du 0 V sur la colonne C2 et seulement sur celle-là.

Le sous-programme ci-dessous a été écrit pour un PIC 16F1825, qui a suffisamment de broches pour cet usage. Tous les bits du port C sont configurés en sortie et connectés aux rangées R1 à R4. Les bits 3, 4 et 5 du port A sont configurés en entrée et connectés aux colonnes C1, C2 et C3, leurs résistances de "pull-up" sont activées. Le sous-programme de décodage se présente comme suit :

static uint8_t read_keyboard (void)
{
    uint8_t r, c, row, port;
/*                             RC0      RC1      RC2      RC3           */
    const uint8_t rport[] = {0b111110,0b111101,0b111011,0b110111};
/*                             RA3      RA4      RA5                    */
    const uint8_t cport[] = {0b001000,0b010000,0b100000};

    WPUA = 0b111000;                            // enable weak internal pull-ups on port A digital inputs

    for (r = 0, row = 1; r <= 4; r++, row += 3) {
        PORTC = rport[r];                       // clear successive bits on PORTC to look at one keypad in a row
        for (c = 0; c < 3; c++) {               // read successive bits on PORTA
            if (!bounce (cport[c]))             // if port is off
                return row + c;                 //   keypad is in column c
        }
    }
    return 0;
}

La valeur de retour sera zéro si aucune touche n'est enfoncée ou le numéro de la touche, dans l'ordre "1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0" et "#".


10 - Le potentiomètre

Un potentiomètre est un autre périphérique d'entrée utilisable par un MCU. Il se présente sous diverses formes, tailles et types. En voici quelques exemples :

C'est une résistance variable avec trois bornes, les deux bornes extérieures de la résistance sont reliées à une source de tension, la borne centrale est relié à un curseur, permettant de délivrer une tension variable en fonction de la position du bouton de commande.

Le type qui nous intéresse pour nos applications est le type linéaire, qui délivre sur le curseur une tension proportionnelle à la position angulaire. Nous utiliserons un modèle ayant une résistance comprise entre 5 et 50 kOhms.

La tension aux bornes peut ne pas être strictement proportionnelle à la position de l'arbre, il s'agit, là encore, d'un dispositif électro-mécanique, imparfait du point de vue électronique. De plus, il peut y avoir des variations d'un modèle à l'autre dans une même série.

Pour lire la tension présente sur le curseur, nous allons utiliser un convertisseur analogique vers numérique (ADC), présent dans la plupart des MCUs.

Nous allons brancher les bornes extérieures du potentiomètre entre le 0 V et le + V et le curseur sur une entrée analogique du MCU.

Le principe étant le même pour la plupart des MCUs, je vais utiliser le PIC 12F675 pour les explications. La précision maximum du convertisseur de ce circuit est de 10 bits, soit une valeur variant de 0 à 1023. Il dispose de quatre entrées analogiques, mais d'un seul circuit de conversion, ce qui oblige à lire les entrées les unes après les autres.

Dans l'exemple, je vais n'utiliser qu'une seule entrée, appelée AN0 qui se trouve sur la broche GP0.

Pour commencer, il faut initialiser le convertisseur :

    TRISIO = 0b000001;                          // GP0 as input, all other as outputs
    ANSELbits.ANS = 0b0001;                     // use AN0 (GP0) as analog input
    ANSELbits.ADCS = 0b101;                     // duration of analog sampling time set to Fosc / 16 (4 usec)
    VCFG = 0;                                   // voltage reference for analog input is VDD
    ADFM = 1;                                   // conversion result right justified
  • TRISIO est le registre qui indique les ports (analogiques ou numériques) utilisés en entrée ou en sortie.
  • ANSELbits.ANS permet de définir de 1 à 4 entrées analogiques utilisées.
  • ANSELbits.ADCS définit le temps que va durer la conversion, plus ce temps sera élevé, plus la conversion sera précise.
  • VCFG indique la tension de référence à utiliser. J'utilise ici VDD (+ V). Quand cette tension sera présente sur la broche d'entrée, la valeur de la conversion sera au maximum (1023), quand il y aura 0 V sur la borne d'entrée, la valeur sera 0.
  • ADFM indique comment le résultat est rangé en mémoire. La valeur convertie est rangée dans deux octets, si je veux utiliser les dix bits, je dois ranger le résultat à droite (deux bits dans le premier octet, les huit bits suivants dans le deuxième octet), par contre, si un résultat sur huit bits (de 0 à 255) est suffisant, je range le résultat à gauche et je n'utilise que le premier octet.

Pour lire la valeur d'une entrée analogique, le sous-programme suivant est utilisé :

unsigned int get_ADvalue (unsigned char ADch)
{
    unsigned int value;

    ADCON0bits.CHS = ADch;                      // set analog channel
    ADON = 1;                                   // AD converter on
    GO_nDONE = 1;                               // start of conversion
    while (GO_nDONE);                           // wait for end of conversion
    value = ADRESH << 8 | ADRESL;               // get value on 10 bits
    ADON = 0;                                   // AD converter off
    return value;
}
  • ADCON0bits.CHS indique la broche à utiliser comme entrée.
  • ADON met en route ou arrête le convertisseur.
  • GO_nDONE démarre la lecture de la broche. Il repasse automatiquement à zéro lorsqu'elle est terminée.
    Plutôt qu'attendre que la conversion soit terminée en utilisant une boucle, comme dans cet exemple, il est possible d'activer une interruption (ADIE) qui positionnera le bit ADIF lorsque GO_nDONE passera à zéro et activera le sous-programme d'interruption.
  • ADRESH et ADRESL sont les deux octets dans lesquels le résultat est rangé. Dans ce MCU, ils ne sont pas consécutifs en mémoire, ce qui oblige à utiliser cette expression barbare. Avec d'autres MCUs, on peut lire directement le résultat dans le registre ADRES.

Comme dit précédemment, du fait des imprécisions du système mécanique, le résultat ne sera pas une valeur angulaire précise au 1/1024ème. Le programme devra tenir compte de cela.


11 - L'encodeur rotatif

Un encodeur rotatif est un périphérique d'entrée qui envoie une information en fonction de la position angulaire de son arbre. Contrairement au potentiomètre, qui est limité dans sa possibilité de rotation, l'encodeur rotatif n'a pas de butée et son arbre peut effectuer un nombre infini de tours. Un de ses usages courants est la roulette de la souris d'un ordinateur ou le bouton de recherche de stations sur un autoradio. Il est parfois couplé à un ou plusieurs boutons poussoirs.

Il existe deux types d'encodeurs :

  • L'encodeur incrémental, qui envoie des signaux en quadrature (c'est à dire décalés de 90°) sur deux broches à chaque changement de position de l'arbre, à charge pour le programmme de détecter le sens de rotation (droite ou gauche) et entretenir un compteur. Ce type d'encodeur ne se souvient pas de sa position, il est juste capable d'indiquer dans quel sens son arbre est déplacé.
  • L'encodeur absolu, qui indique directement la valeur angulaire de l'arbre, entre 0 et sa valeur maximum sur un tour complet. Il en existe qui comptent le nombre de tours effecués et conservent cette information dans une mémoire non volatile, mais, en général, cela est pris en charge par le programme.

Voici une photo de deux codeurs de type incrémental, celui de droite dispose de 5 boutons poussoirs :

Fonctionnement de l'encodeur incrémental

Il y a un certain nombre de positions pour un tour complet de l'arbre, dépendant des modèles. À chaque changement de position, les valeurs sur les broches de sortie changent suivant un ordre bien précis. La valeur de ces broches est vu par le MCU comme un nombre sur deux bits, pouvant donc prendre les valeurs "00", "01", "10" et "11".

Voyons comment cela se passe avec le dessin ci-dessous :

Les lignes rouges indiquent les changements de position, les nombres dans les rectangles verts sont le résulat de la valeur précédente multipliée par 4 et additionnée à la valeur courante. Nous voyons que pour chaque sens de rotation, il n'y a que 4 valeurs possibles :

  • "1101", "0100", "0010" ou "1011" (13, 4, 2 ou 11 en décimal) pour une rotation à droite.
  • "1110", "0111", "0001" ou "1000" (14, 7, 1 ou 8 en décimal) pour une rotation à gauche.
Toutes les autres valeurs, en particulier celles générées par les rebonds, sont invalides. Lorsque l'encodeur est utilisé à la main, pour faire varier une valeur, les rebonds ne sont pas gênants. Par contre, si l'encodeur est entraîné mécaniquement, par exemple par un moteur pas-à-pas, il faut les éliminer, soit par un condensateur extérieur, soit par programme.

Voici un exemple de sous-programme, il peut être appelé par une interruption déclenchée par un changement d'état sur les broches d'entrée ou par un timer :

signed int turncount = 0;

void read_quadrature (void) {
    static unsigned char val = 3;
                           /*    0  1 2 3 4 5 6  7  8 9 10 11 12 13 14 15    */
    static const char state[] = {0,-1,1,0,1,0,0,-1,-1,0,0, 1, 0, 1,-1, 0,};

    val = ((val & 0b11) << 2) | (PORTA & 0b11);    // get current value and add it to the previous value
    turncount += state[val];
}

Les broches de l'encodeur sont connectées sur les broches RA0 et RA1 d'un MCU disposant d'un port A. Le tableau "state[ ]" contient les indications de direction, rangées dans l'ordre des valeurs définies précédemment. Il n'y a pas de détection des rebonds dans ce bout de code. Une solution pourrait être de vérifier que les entrées restent dans le même état pendant un certain temps avant de les utiliser, ce temps étant à déterminer en fonction de l'utilisation.

Fonctionnement de l'encodeur absolu

Nous entrons là dans le domaine professionnel et il y a une multitude d'offres, la plupart à des tarifs laissant rêveur !

Il existe pourtant un composant accessible aux amateurs, appelé EAW ACE-128 et fabriqué par Bourns. Il est vendu environ 8.50 €.

Comme son nom l'indique, il y a 128 positions pour un tour complet de l'arbre et la valeur absolue de la position est disponible sur 8 broches, 2 autres broches sont utilisées pour donner la référence 0 V. C'est un composant électro-mécanique, avec toutes ses imperfections (rebonds) et il a une durée de vie donnée pour 50 000 tours minimum. Ce n'est pas spécifié, mais il est fort probable qu'une vitesse de rotation élevée doit l'abréger.

Puisqu'il y a huit broches pour lire la valeur, on aurait pu penser qu'on retrouverait directement un résultat variant séquentiellement de 0 à 127. Il n'en est rien et il faut utiliser une table de transcodage pour retrouver une valeur humainement logique. Le code nécessaire se trouve ci-dessous :

unsigned char read_absolute (void)
{
    static const unsigned char position[] = {
    0xff,0x38,0x28,0x37,0x18,0xff,0x27,0x34,0x08,0x39,0xff,0xff,0x17,0xff,0x24,0x0d,
    0x78,0xff,0x29,0x36,0xff,0xff,0xff,0x35,0x07,0xff,0xff,0xff,0x14,0x13,0x7d,0x12,
    0x68,0x69,0xff,0xff,0x19,0x6a,0x26,0xff,0xff,0x3a,0xff,0xff,0xff,0xff,0x25,0x0e,
    0x77,0x76,0xff,0xff,0xff,0x6b,0xff,0xff,0x04,0xff,0x03,0xff,0x6d,0x6c,0x02,0x01,
    0x58,0xff,0x59,0xff,0xff,0xff,0xff,0x33,0x09,0x0a,0x5a,0xff,0x16,0x0b,0xff,0x0c,
    0xff,0xff,0x2a,0x2b,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x15,0xff,0x7e,0x7f,
    0x67,0xff,0x66,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x5b,0xff,0xff,0xff,0xff,0xff,
    0x74,0x75,0xff,0xff,0x73,0xff,0xff,0xff,0x5d,0x5e,0x5c,0xff,0x72,0x5f,0x71,0x00,
    0x48,0x47,0xff,0x44,0x49,0xff,0xff,0x1d,0xff,0x46,0xff,0x45,0xff,0xff,0x23,0x22,
    0x79,0xff,0x7a,0xff,0x4a,0xff,0xff,0x1e,0x06,0xff,0x7b,0xff,0xff,0xff,0x7c,0x11,
    0xff,0xff,0xff,0x43,0x1a,0xff,0x1b,0x1c,0xff,0x3b,0xff,0xff,0xff,0xff,0xff,0x0f,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x05,0xff,0xff,0xff,0x6e,0xff,0x6f,0x10,
    0x57,0x54,0xff,0x2d,0x56,0x55,0xff,0x32,0xff,0xff,0xff,0x2e,0xff,0xff,0xff,0x21,
    0xff,0x53,0xff,0x2c,0x4b,0xff,0xff,0x1f,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x20,
    0x64,0x3d,0x65,0x42,0xff,0x3e,0xff,0x31,0x63,0x3c,0xff,0x2f,0xff,0xff,0xff,0x30,
    0x4d,0x52,0x4e,0x41,0x4c,0x3f,0xff,0x40,0x62,0x51,0x4f,0x50,0x61,0x60,0x70,0xff,
    };
    return position[PORTC];
}

Il existe aussi quelques encodeurs magnétiques sans contact qui offrent de meilleures performances mécaniques (durée de vie supérieure à cent millions de tours et un régime de rotation jusqu'à 10 000 tours par minute), avec une résolution pouvant aller jusqu'à 1024 positions par tour. Leurs prix (à partir de 35.00 € HT) sont relativement modérés, mais s'éloignent quand même d'un budget amateur.


12 - Le servomoteur

L'usage habituel d'un servomoteur (servo) est d'être branché sur une prise d'un récepteur de radiocommande. Mais on peut également le brancher sur une broche de sortie d'un MCU et lui envoyer le signal qu'il attend.

Ce signal est une impulsion de largeur variable (PWM = Pulse Width Modulation), variant entre 900 et 2100 µs, répétée à intervalle régulier, généralement de l'ordre de 20 000 µs (50 hz). Certains servos de type numérique peuvent accepter une fréquence de répétition plus rapide, mais il vaut mieux expérimenter auparavant.

Le neutre est indiqué par une impulsion de 1500 µs.

Les variateurs pour moteurs à balais et les contrôleurs pour moteurs sans balai se commandent aussi avec ce genre de signal.

L'impulsion de commande est générée en mettant à l'état 1 la broche durant le temps voulu et en la remettant à l'état 0 durant le temps restant pour l'impulsion de répétition. Ceci implique donc l'utilisation d'un timer.

Je donne deux exemples de code. Dans les deux, le timer 1 est réglé pour compter directement en microsecondes. Le premier est un sous-programme appelé régulièrement à chaque intervalle de répétition, généré par ailleurs par un moyen que je ne détaillerai pas :

#define NEUTRAL 1500

static volatile bit clock;
unsigned int pulse = NEUTRAL;

void generate_frame (unsigned int pulse)
{
    TMR1ON = 0;                                 // stop timer 1
    clock = 0;                                  // reset time indicator
    TMR1 = 10 - pulse;                          // set timer 1
    GP5 = 1;                                    // set port
    TMR1ON = 1;                                 // start timer 1
    while (!clock);                             // wait until time has elapsed
    GP5 = 0;                                    // clear port
}

static void interrupt routine (void)
{
    if (TMR1IE && TMR1IF) {                     // timer 1 interrupt
        TMR1IF = 0;                             // clear interrupt flag
        clock = 1;                              // set time elapsed indicator
    }
}

Dans l'exemple ci-dessus, le sous-programme d'interruption sert juste à positionner un indicateur lorsque le temps est écoulé.

Le deuxième exemple génère l'impulsion et l'intervalle de répétition directement dans le sous-programme d'interruption :

#define REPEAT 20000
#define NEUTRAL 1500

unsigned int pulse = NEUTRAL;

static void interrupt routine (void)
{
    static bit pulsenum = 0;
    static unsigned int p = NEUTRAL;

    if (TMR1IE && TMR1IF) {                     // timer 1 interrupt
        TMR1IF = 0;                             // clear interrupt flag
        TMR1ON = 0;                             // stop timer 1
        pulsenum = ~pulsenum;                   // invert pulse number
        GP5 = pulsenum;                         // set GP5 according to pulse number
        if (pulsenum) {                         // if first pulse
            p = pulse;                          // get length of pulse
            TMR1 = 10 - p;                      // set timer to length of pulse
        }
        else {
            TMR1 = (10 - REPEAT) + p;           // set timer to length of long pulse
        }
        TMR1ON = 1;                             // start timer 1
    }
}

Cet exemple permet de ne pas bloquer pas le reste du programme, comme le faisait le premier avec la boucle d'attente de l'indicateur de temps écoulé.


13 - Le moteur pas-à-pas (stepper motor)

Un moteur pas-à-pas est un moteur sans balai (brushless) qui ne se déplace que d'une fraction de tour à la fois. On peut ainsi le positionner à un angle précis et lui faire effectuer autant de tours que nécessaire.

Il est constitué d'un arbre rotatif portant des aimants permanents, appelé rotor, et d'une partie fixe supportant des électro-aimants, appelée stator.

  • Note : dans les explications qui suivent, je décris un moteur avec un seul aimant, ayant une résolution d'un quart de tour par pas, à des fins de démonstration. Dans le monde réél, ce ne serait pas un moteur très pratique pour la plupart des applications. La résolution des moteurs pas-à-pas (le nombre de fractions de tour par pas) est plus élevé que cela.

Modes de fonctionnement

Il y a 4 modes de fonctionnement :

  1. Le mode appelé "pas-entier" (full-step) est le plus simple à comprendre. Voyons comment s'effectue une rotation complète dans ce mode :

    • Dans la première position, l'aimant du rotor est attiré par l'électro-aimant "A", qui est actif (une tension lui est appliquée).
    • Pour déplacer le rotor vers la droite, l'électro-aimant "A" est désactivé et l'électro-aimant "B" est activé, ce qui provoque le déplacement du rotor qui s'aligne avec lui.
    • Ce processus est répété de la même manière avec les électro-aimants "C" puis "D" jusqu'à revenir à la position de départ.

  2. Le mode appelé "double-phase" (two-phase-on full-step) permet d'augmenter le couple du moteur en activant deux électro-aimants consécutifs à chaque pas, doublant ainsi la force d'attraction :

    • Dans la première position, les deux électro-aimants "A" et "B" sont activés en même temps, ce qui provoque le positionnement du rotor entre les deux.
    • Puis, en deuxième position, l'électro-aimant "A" est désactivé et les électro-aimants "B" et "C" sont activés, puis "C" et "D", puis "D" et "A".

    Ce mode de fonctionnement augmente le couple de 30 à 40%, mais double la consommation.

  3. Le mode appelé "demi-pas" (half-stepping) permet de doubler la résolution du moteur. C'est un mélange entre les modes "pas-entier" et "double-phase". Le dessin ci-dessous montre le principe :

    • Dans la première position l'électro-aimant "A" est actif, et le rotor est aligné sur lui.
    • Dans la deuxième position, les deux électro-aimants "A" et "B" sont actifs en même temps, ce qui provoque le positionnement du rotor entre les deux.
    • En troisième position, l'électro-aimant "A" est désactivé et le rotor s'aligne sur l'électro-aimant "B".
    • Dans la quatrième position, les deux électro-aimants "B" et "C" sont actifs en même temps, ce qui provoque le positionnement du rotor entre les deux.
    • Ce processus est répété pour l'ensemble de la rotation.

  4. Le mode appelé "micro-pas" (microstepping) permet d'augmenter la régularité de la rotation. Au lieu de changer brutalement de tension entre chaque pas, ce qui donne des à-coups et génère du bruit, on diminue progressivement le courant de l'électro-aimant actif, en augmentant tout aussi progressivement le courant de l'électro-aimant suivant.
    Des circuits spécialisés existent pour gérer ces variations, par exemple le A3967, fabriqué par Allegro. Des modules utilisant ce composant, appelés "EasyDriver", sont également disponibles.

Le choix du mode de fonctionnement dépend de l'utilisation envisagée :

  • Le mode "pas-entier" est le plus simple à programmer, il est adapté aux faibles vitesses. Quand il y a un changement de pas, l'aimant dépasse légèrement la position prévue et revient en arrière, puis repart en avant, il rebondit ainsi plusieurs fois de suite. Lorsque la vitesse est élevée, ce rebond peut être tel qu'il peut désynchroniser le moteur.
  • Le mode "double-phase" atténue ce phénomène, car l'aimant est mieux "tenu" entre les deux électro-aimants.
  • Le mode "demi-pas" atténue encore plus le rebond, puisque la distance pour passer d'un pas à l'autre est diminuée de moitié. Ce mode est à utiliser pour les vitesses élevées.
  • Le mode "micro-pas" supprime complétement le phénomène, puisque le courant accompagne le déplacement de l'aimant et le bruit est également éliminé.

Types de moteurs

Il existe 2 types de moteurs pas-à-pas :

  1. Les moteurs à 5 ou 6 fils, ayant 4 électro-aimants séparés, comme ceux des exemples ci-dessus, sont appelés "unipolaires". Les électro-aimants "A" et "C" sont branchés en série et il y a un fil branché au milieu, de même pour les électro-aimants "B" et "D". Dans les moteurs 6 fils, ces points milieux sont disponibles sur deux fils différents. Dans les moteurs 5 fils, les 2 points milieux sont reliés ensemble sur le même fil.

    Les électro-aimants peuvent être commandés simplement avec 4 transistors ou MOSFETs : les points milieux sont connectés à la borne positive et les 4 autres fils sont connectés aux sorties des transistors. Des circuits intégrant ces transistors existent, par exemple le réseau de transistors ULN2003 utilisé ici avec un moteur 5 fils :

  2. Les moteurs à 4 fils, ayant 2 électro-aimants, chacun constitué de 2 bobines disposées à 180° et connectées en série, sont appelés "bipolaires" :

    La mise en oeuvre est plus compliquée qu'avec les moteurs unipolaires, parce que le circuit de commande doit être en mesure d'inverser la polarité dans les enroulements à chaque changement de pas. Un double pont en H est donc nécessaire pour commander ce type de moteurs :

    Heureusement, des circuits intégrant ce double pont existent, par exemple le driver L293B pour les petits moteurs ou le L298 qui délivre une intensité un peu plus forte.

    Des modules utilisant ces composants sont disponibles. Certains sont même vendus, port compris, moins cher que le prix courant du composant seul en Europe.

Programmation

Pour faire tourner le moteur, le programme de commande doit donc exécuter les instructions d'activation des électro-aimants dans le bon ordre et avec la bonne temporisation, pour que le rotor ait le temps de se positionner entre deux pas.

  • Pour faire tourner dans le sens horaire, la séquence est "A", "B", "C" et "D".
  • Pour faire tourner en sens inverse, la séquence est "A", "D", "C" et "B".
  • Pour faire tourner dans le sens horaire avec des demi-pas, la séquence est "A", "A+B", "B", "B+C", "C", "C+D", "D" et "D+A".
  • Pour faire tourner en sens inverse avec des demi-pas, la séquence est "A", "A+D", "D", "D+C", "C", "C+B", "B" et "B+A".

Dans le cas d'un moteur unipolaire, les sorties "A" à "D" sont directement reliées à des transistors commandant les électro-aimants.

Dans le cas d'un moteur bipolaire, les sorties "A" à "D" sont reliées aux entrées des deux ponts en H, pour pouvoir activer avec la bonne polarité les couples de bobines "A+C" et "B+D".

Dans le programme, les valeurs sont rangées dans un tableau, indiquant les bits du port à positionner. Le tableau est parcouru dans l'ordre croissant pour le sens horaire et dans l'ordre décroissant pour le sens inverse. Par exemple :

extern bit half_step, two_phase;
static signed int position = 0;
static void delay_step (void);

signed int one_step (signed char direction)
{
    static const char step_2[4] = {0b0011, 0b0110, 0b1100, 0b1001,};
    static const char step_4[4] = {0b0001, 0b0010, 0b0100, 0b1000,};
    static const char step_8[8] = {0b0001, 0b0011, 0b0010, 0b0110, 0b0100, 0b1100, 0b1000, 0b1001,};

    position += direction;
    if (half_step)
        PORTA = step_8[position & 0b0111];
    else if (two_phase)
        PORTA = step_2[position & 0b0011];
    else
        PORTA = step_4[position & 0b0011];
    delay_step ();
    return position;
}

Ce sous-programme déplace le moteur d'un pas à chaque appel, il est appelé autant de fois que nécessaire. Le bit 0 du port est relié à "A", le bit 1 à "B", le bit 2 à "C" et le bit 3 à "D".

  • L'argument "direction" ne peut prendre que les valeurs -1, 0 ou 1, il augmente ou diminue la position courante, suivant son signe.
  • La variable "position" définit la position courante du moteur. Sa valeur est renvoyée au programme appelant.
  • Dans le cas d'un fonctionnement en demi-pas, indiqué par le bit "half_step", les bits du port à activer sont pris dans le tableau "step_8", indexé par les 3 derniers bits (valeur variant de 0 à 7) de la variable "position".
  • Dans le cas d'un fonctionnement en double-phase, indiqué par le bit "two_phase", les bits du port à activer sont pris dans le tableau "step_2", indexé par les 2 derniers bits (valeur variant de 0 à 3) de la variable "position".
  • Sinon, ils sont pris dans le tableau "step_4", indexé par les 2 derniers bits (valeur variant de 0 à 3) de la variable "position".
  • Le sous-programme "delay_step()" introduit un temps d'attente propre au modèle de moteur utilisé et à la vitesse désirée.

Deux programmes complets se trouvent dans les descriptions de montage pour le PIC 12F675. Ils utilisent le moteur 28BYJ-48, que l'on trouve un peu partout à moins de $3.00, port compris, avec son driver basé sur un circuit ULN2003, sa prise et 4 LEDs de contrôle :

Évidemment, à ce prix, il ne faut pas s'attendre à une grande qualité de réalisation. Voici deux photos de l'intérieur :


14 - L'afficheur 7 segments

Un afficheur 7 segments est un périphérique de sortie utilisable par un MCU, permettant d'afficher une valeur numérique. Il se présente sous divers types, tailles et couleurs. Il peut être composé d'un seul afficheur ou de blocs de 2, 3, 4 (ou plus) afficheurs.

Il existe deux modèles dans les blocs de 4 afficheurs : un pour réaliser des instruments de mesure, où il y a 4 points décimaux et un autre pour réaliser une horloge, où les deux diodes superposées entre les deux afficheurs du milieu remplacent les points décimaux.

Voici, par exemple un afficheur quadruple, monté sur une plaquette d'essais :

Il est constitué de huit LEDs, nommées selon le dessin ci-dessous :

Lorsqu'on a affaire à un bloc d'afficheurs, les cathodes ou les anodes des LEDs de chaque afficheur sont reliées ensemble, comme dans les schémas ci-dessous :

Le schéma du haut montre un montage à cathodes communes (CC, Common Cathode), celui du dessous un montage à anodes communes (CA, Common Anode).

Le dessin ci-dessous montre le brochage vu de dessus d'un bloc de 4 afficheurs à cathodes communes :

Programmation

Pour afficher un nombre à plusieurs chiffres, nous devons afficher chaque chiffre successivement, en alimentant pendant un certain temps les LEDs des segments A à DP et la broche commune du chiffre à afficher. Si la vitesse de changement est suffisamment élevée, la persistance rétinienne ne permet pas de distinguer de clignotement. Par exemple, ce sous-programme, activé par un timer, permet de piloter un bloc de quatre afficheurs à cathodes communes :

void update_display (void)
{
    static unsigned char i = 3;

    i = (i+1) & 0x03;                           // i varying from 0 to 3
    PORTB = 0b00010000 << i;                    // activate each display according to i
    display_digit (i);                          // write the digit[i] on the ith display.
}

Pour dessiner correctement un chiffre, nous avons besoin d'une table indiquant les segments à allumer. C'est ce qui est réalisé dans le sous-programme suivant, toujours pour des afficheurs à cathodes communes :

void display_digit (unsigned char i)
{
     static const unsigned char drawing[] =
/*        .GFEDCBA                                    */
    {   0b00111111,        // 0        *       A
        0b00000110,        // 1        *     -----
        0b01011011,        // 2        *    |     |
        0b01001111,        // 3        *  F |     | B
        0b01100110,        // 4        *    |  G  |
        0b01101101,        // 5        *     -----
        0b01111101,        // 6        *    |     |
        0b00000111,        // 7        *  E |     | C
        0b01111111,        // 8        *    |  D  |
        0b01101111,  };    // 9        *     -----   o DP

    PORTC = drawing[digits[i]];
    RC7 = dpoint[i];
}

Réalisation pratique

Chaque LED nécessite un courant d'une dizaine de mA, il faut donc intercaler une résistance de limitation entre la sortie du MCU et la broche de l'afficheur. Il existe des réseaux de huit résistances, qui se présentent sous la forme d'un circuit intégré de 16 broches et évitent donc d'avoir à positionner et souder chaque résistance une à une.

La broche commune peut nécessiter un courant pouvant aller jusqu'à 90 mA, ce que le MCU pourrait ne pas être capable de fournir. Il faut donc intercaler, dans ce cas, un petit MOSFET entre les sorties du MCU et chaque broche commune, comme dans le schéma ci-dessous :


15 - Les interfaces de communication

Jusqu'à présent, nous avons vu des périphériques qui ne fournissent ou ne nécessitent que peu d'informations. Il y en a toutefois d'autres qui ont besoin de plus, par exemple un afficheur graphique ou un module GPS.

Pour satisfaire ce besoin, plusieurs interfaces ont été développées au cours du temps, permettant d'envoyer ou recevoir beaucoup d'informations tout en minimisant le nombre de connexions.

Je vais en énumérer quelques unes, compatibles avec les MCUs, sans trop entrer dans les détails. J'approfondirai par la suite, si nécessaire.

  • L'interface SSI (Synchronous Serial Interface) est la plus simple de toutes, elle ne nécessite que deux lignes (plus une masse commune). Une des lignes, appelée "clock", transporte un signal d'horloge et l'autre ligne, appelée "data", les données, synchronisées par le signal d'horloge. Il y a un dispositif maître, généralement le MCU, qui envoie le signal d'horloge et un dispositif esclave qui le lit. La vitesse de l'horloge n'a aucune importance et elle n'a pas besoin d'être stable, mais plus elle sera rapide, plus vite les données seront échangées. Les données peuvent être envoyées indifféremment par le maître ou l'esclave. Il n'y a pas de protocole d'échange particulier, tout dépend de la programmation des dispositifs.

    Sur ce dessin, voyons comment transitent les données en fonction de l'horloge :

    Au départ, la ligne "clock" est au niveau haut.

    • À l'instant "t1", la ligne "clock" passe au niveau bas, signalant la mise en route de l'interface.
    • À l'instant "t2", la ligne "clock" passe au niveau haut, le dispositif émetteur positionne la ligne "data" à une certaine valeur.
    • À l'instant "t3", la ligne "clock" passe au niveau bas, le dispositif récepteur peut alors lire la ligne "data".
    • À l'instant "t4", la ligne "clock" passe au niveau haut, le dispositif émetteur peut écrire une nouvelle valeur.
    • Le cycle se reproduit jusqu'à ce que toutes les données aient été envoyées.
    • À l'instant "tn", lorsque l'envoi de données est terminé, la ligne "clock" passe au niveau haut et y reste.

    Les données échangées n'ont aucune signification pour cette interface et n'importe quel nombre de bits (0/1) peut être envoyé, ces bits n'ont de sens que pour les dispositifs émetteur et récepteur.

    Si l'on veut utiliser plusieurs esclaves, il faut ajouter pour chacun une ligne appelée "CS" (Chip Select), permettant d'indiquer avec quel dispositif on dialogue.

  • L'interface I²C (inventée par Philips au début des années 80) ne nécessite également que deux lignes et ressemble beaucoup à l'interface SSI, mais elle peut supporter plusieurs dispositifs esclaves, branchés en parallèle sur les deux lignes. La ligne d'horloge s'appelle "SCL" et celle de données "SDA". Chaque esclave a son adresse personnelle, codée sur 7 ou dix bits.

    Pour que le maître puisse dialoguer avec les esclaves, un protocole a été défini et les échanges, vus du programme, ne se font plus bit à bit, mais par octets (8bits). Le maître envoie une commande à une adresse particulière ou à l'adresse 0 (utilisée pour envoyer un message général à tous les esclaves) et les esclaves répondent en envoyant des données ou simplement en signalant qu'ils ont reçu la commande.

    Ce protocole est pris en compte par un module interne dans beaucoup de MCUs et le programme n'a plus alors qu'à activer l'interface, puis envoyer et recevoir des données, en écrivant ou en lisant dans des registres.

    Des sous-programmes implémentant cette interface existent, permettant de l'utiliser sur tous les MCUs. Voir, par exemple, le bout de code écrit en pseudo-C, dans l'article "I2C" sur Wikipedia.

  • L'interface SPI
  • L'interface RS232 a été inventée alors que les MCUs n'existaient pas encore.

    À l'origine, les tensions sur les lignes variaient entre -15 V et + 15 V et il y avait plusieurs lignes de commande pour permettre aux dispositifs de dialoguer entre eux.

    Les dispositifs proposés actuellement sont généralement au standard "TTL", où la tension varie entre 0 et + V. Ces nouveaux dispositifs ne fonctionnent qu'avec deux lignes, appelées "TX" et "RX". Les lignes doivent être croisées : le signal "TX" d'un dispositif étant branché sur le signal "RX" de l'autre et réciproquement.

    Contrairement aux autres interfaces, il n'y a donc pas de signal d'horloge commun et les deux dispositifs doivent avoir des fréquences d'horloge stables. Il faut aussi qu'ils utilisent une vitesse d'échange (bauds) et un format de données (nombre de bits, parité, etc.) identiques

    Cette interface est présente sur beaucoup de MCUs sous le nom "UART" ou "USART".

    Des sous-programmes implémentant cette interface existent, permettant de l'utiliser avec des MCUs ne disposant pas d'UART.