Microcontrôleur – Chapitre 3 – Les périphériques

Les entrées/sorties

La plupart des broches d’un microcontrôleur (en général toutes, sauf GND, VCC et RESET) ont comme comportement par défaut la possibilité d’être utilisée comme entrée ou sortie numérique.

On parlera des I/O (ou GPIO pour General Purpose Input/Output) ou E/S en français. Ces broches sont rassemblées communément par port. Il y a souvent plusieurs ports par microcontrôleur, et leurs broches ne sont pas nécessairement côte-à-côte.

Ces I/O sont accessibles au travers de plusieurs registres ; rassemblés par port. On trouve principalement les registres suivants :

  • le registre de direction, où on indique dans quelle direction est utilisé la broche (entrée ou sortie)
  • le registre de sortie, où on peut écrire l’état souhaité d’une broche
  • le registre d’entrée, qui permettra de lire les états des broches d’un port

Il est possible de définir une interruption lors du changement d’état sur un port, ce qui permet au développeur de ne pas avoir à surveiller l’état des broches en permanence.

Il convient de réfléchir néanmoins au projet que l’on souhaite créer en utilisant les I/O, car presque toutes les broches ont des fonctions cumulées, c’est à dire qu’une broche en particulier peut servir d’I/O mais aussi faire partie d’un bus de communication, une autre peut être une entrée analogique, etc.

La réduction de la taille des microcontrôleurs poussent les constructeurs à faire des sacrifices ; et il existe des alternatives, que l’on verra surement plus tard, qui permettent d’étendre les ports.

Les compteurs

Pièce maitresse de nombreux mécanismes au sein d’un microcontrôleur, le compteur (parfois appelé timer) est un outil puissant qui se contente d’incrémenter un registre (ou plusieurs) et de déclencher une interruption à partir d’un certain seuil. Selon les microcontrôleurs, vous trouverez des compteurs de 8bit, 16bit ou autres, avec généralement les paramètres suivant :

  • le seuil ; c’est à dire le moment ou le microcontrôleur va déclencher l’interruption
  • la valeur de départ
  • le sens (descendant ou montant)
  • le diviseur ; qui permettra de définir des compteurs plus long en terme de cycle (par un exemple un incrément n’aura lieu que tous les 64 ou 1024 cycles). Ceci permet de ralentir un compteur pour permettre de passer du MHz au kHz voire en dessous pour de multiples applications (créer des sons, des effets visibles à l’œil donc inférieurs à 25Hz, …).
  • le mode (en montée, en montée puis descente, etc.)

Ainsi, avec un processeur cadencé à 1Mhz, si je configure un compteur 16bit pour déclencher une interruption dès qu’il atteint 976, lorsque son diviseur est à 1024, j’obtiendrai une interruption au bout d’une seconde.

Ces paramètres dépendent fortement du constructeur et du modèle de microcontrôleur utilisé, et comme beaucoup d’autres concepts, il est obligatoire de disposer de la documentation.

Les convertisseurs analogique vers numérique

Souvent abrégé CAN en France (à ne pas confondre avec le bus CAN), on parle d’Analog to Digital Converter en anglais (ADC). Il s’agit d’un montage dont le but est d’approximer une grandeur sur une échelle de taille déterminée.

Par exemple, un convertisseur de résolution 10bit pourra donner une valeur entre 0 et 1023. Si je cherche à mesurer une tension entre 0V et 5V, j’aurai une précision au mieux de 4,88mV. Aussi, si la valeur réelle est de 3,82V, j’obtiendrai un résultat de 782 ce qui équivaut en réalité à 3,818V.

La conversion est un processus qui demande beaucoup de cycles pour être précise. Il existe plusieurs méthodes de conversion (je ne vais pas les détailler) et chaque microcontrôleur va pouvoir réaliser un certain nombre de conversions en un temps donné, avec une résolution donnée. On parlera ici d’échantillon (sample en anglais). Par exemple, un convertisseur peut fournir 15ksps avec une résolution de 12bit, soit 15000 échantillons par seconde, chacun compris entre 0 et 4095. Mais dans le cas d’une conversion avec une résolution de 16bit, la vitesse peut être de 1ksps.

Dans la pratique, il s’agira de configurer la conversion, de la lancer, puis d’attendre une interruption (ou une valeur particulière dans un registre) pour ensuite lire la valeur obtenue.

Les convertisseurs numérique vers analogique

On parlera plus communément de PWM (Pulse Width Modulation, soit Modulation de largeur d’impulsion). Ce mécanisme va permettre d’obtenir une pseudo-valeur de tension analogique depuis une valeur numérique.

Pourquoi pseudo ? Parce qu’en réalité le microcontrôleur ne peut fournir que la masse GND (0V communément), ou la tension d’alimentation VCC (par exemple 5V). Donc quand on utilisera un PWM, pour obtenir 1V, on devra faire en sorte qu’une impulsion sur cinq soit à VCC tandis que les autres seront à GND.

Pour faciliter la configuration, les sorties PWM sont liées à des compteurs (parfois plusieurs sorties sur un même compteur), les paramètres sont donc similaires, mais on peut en ajouter quelques uns :

  • seuil de bascule ; le moment ou la sortie PWM passera de GND à VCC ou vice versa
  • le mode (à compteur triangulaire ou en dent de scie, …)

Il est important de garder en tête que le signal PWM tel qu’obtenu reste un signal numérique, et qu’il peut atteindre une tension égale à celle qui alimente le microcontrôleur. Afin d’éviter d’endommager des composants sensibles (par exemple un composant qui ne supporterait une tension que d’1.5V alors que le microcontrôleur est alimenté en 5V), il peut être nécessaire de filtrer la sortie (avec un montage R/C) pour passer d’une tension en créneaux à une courbe plus juste.

On peut également isoler galvaniquement les circuits (c’est à dire qu’il n’y a aucun contact électrique entre alimentation et pilotage) ; il faudra cependant ajouter une alimentation indépendante du microcontrôleur, mais ceci permettra d’obtenir des tensions bien différentes (piloter du 12V depuis un circuit 5V).

Les comparateurs

Il n’y a pas de démystification à faire ici, puisqu’un comparateur se contente de comparer deux entrées analogiques, sans nécessairement les mesurer.

Il suffit de lier un circuit analogique avec une masse commune à chaque du comparateur (variateur, photorésistance, capteur analogique, …), il sera alors possible de lire directement dans un registre quel circuit délivre la plus grande tension.

Il est également possible de déclencher une interruption sur un changement d’état.

L’interruption RESET

La plupart des microcontrôleurs (et beaucoup de circuits intégrés) dispose d’une broche RESET. Celle-ci, comme son nom l’indique, permet de réinitialiser l’appareil.

Si on applique la masse à cette broche, alors on déclenche la réinitialisation ou un état particulier (écoute de la programmation en SPI par exemple).

Le chien de garde

Ce mécanisme, appelé Watchdog en anglais, permet de s’assurer qu’un microcontrôleur n’est pas figé. Le concept est simple ; lorsque le chien de garde « surveille » l’exécution, un registre doit être mis à jour avant un certain délai continuellement. Si une période trop longue s’écoule sans mise à jour, le watchdog va lever une interruption.

La plupart du temps, il va s’agir de redémarrer le microcontrôleur comme un RESET. Il est possible de changer ce comportement (par exemple forcer l’arrêt d’un moteur en cas de défaut du programme avant le redémarrage, ou allumer une diode d’erreur), et de faire varier le délai d’alerte (par exemple 60ms, 250ms ou 8s).

Les autres périphériques

Si on monte en gamme, on peut trouver au sein du microcontrôleur des fonctionnalités plus pointues comme :

  • un superviseur d’alimentation
  • un thermomètre
  • une horloge temps-réel (RTC)
  • un générateur de fréquences
  • une unité de chiffrement
  • une unité de signature

Microcontrôleur – Chapitre 2 – Le processeur

Le processeur est un composant complexe et incontournable. Après son invention à la fin de la Seconde Guerre Mondiale, sa taille a fortement diminué (de plusieurs dizaines de mètres de large à quelque millimètres) pour donner naissance au microprocesseur dans les années 1970.

Depuis, la densité de transistors présente au sein d’un processeur n’a cessé de croitre au point de doubler tous les deux ans (selon la célèbre loi de Moore). Ainsi les processeurs actuels (en 2015) contiennent un million de fois plus de transistors que les premiers microprocesseurs mis sur le marché quarante ans auparavant.

Le processeur d’un microcontrôleur est caractérisé par :

  • son architecture qui lui fournira un jeu d’instructions ainsi qu’une taille de registre (8bit, 16bit, …)
  • ses registres internes
  • la cadence de son horloge, qui peut être configurée de quelque kHz à plusieurs dizaines de MHz.

Les instructions disponibles peuvent être en nombre très variable selon les architectures. Elles sont en générales très primaires ; il s’agit :

  • d’opérations arithmétiques et logiques qui peuvent être effectuées sur les registres internes du processeur ou sur les mémoires ou entre les deux
  • d’opérations sur des adresses pointant vers des secteurs de mémoire
  • des sauts d’exécution vers des endroits de la mémoire

L’horloge

Un microcontrôleur possède généralement un cristal interne (quartz), qui jouera le rôle d’horloge par effet piézoélectrique. Il est souvent possible d’exploiter à la place un cristal externe, notamment pour utiliser des fréquences plus élevées que le cristal interne.

L’horloge va cadencer le processeur ; un tic correspondra à une instruction élémentaire. Une fréquence basse ralentira forcement l’exécution d’un processus, et empêchera d’utiliser des protocoles nécessitant une fréquence plus élevée. Elle permettra néanmoins de consommer beaucoup moins d’énergie. Une fréquence élevée permettra d’être plus efficace mais consommera plus d’énergie, ce qui peut également dégager de la chaleur.

Certains microcontrôleurs permettent de modifier leur fréquence pendant l’exécution ; c’est une fonctionnalité très intéressante qui permet d’économiser énormément d’énergie en mettant le montage en veille, en attendant par exemple une interruption (appui sur un bouton, détection de lumière, …). La fréquence passera alors à quelques kilohertz, remontant à plusieurs MHz lors d’une utilisation active.

La fréquence peut également être amplifiée par un mécanisme appelé « boucle à verrouillage de phase » ( PLL ; Phase-locked loop en anglais). On peut attendre ainsi des fréquences très élevées (2 à 10 fois la fréquence maximale), au prix de la stabilité et de la fiabilité du microcontrôleur.

Les registres

Il faut bien différencier ici deux concepts :

– les registres internes du processeur

– les registres externes en mémoire

On parlera dans les deux cas de registres, mais on se réfèrera la plupart du temps a des registres externes.

les registres internes

Un registre (interne) est un emplacement de mémoire particulier au sein du processeur. La quantité de registres est très limitée (quelques dizaines tout au plus). Leur taille est une constante du processeur ; un processeur 16-bit aura des registres de 16 bits.

Il y a plusieurs registres internes significatifs :

  • le compteur ordinal : il s’agit du registre qui pointe vers l’instruction en cours
  • le registre d’instruction : il contient l’instruction en cours
  • les registres « données » : leur utilité est principalement de charger des données qui vont être manipulées dans les prochains cycles, afin de minimiser le temps de traitement
  • les registres « adresses » qui font office de pointeurs vers des zones de la mémoire
  • (il existe beaucoup d’autres types que je ne détaillerai pas ici)

Dans le cadre du développement sur microcontrôleur, leur utilisation est totalement transparente, sauf à vouloir utiliser des commandes assembleurs brutes.

les registres externes

Ces registres, stockés en mémoire, sont liés à l’état d’un composant ou d’un périphérique de façon directe. Ils peuvent être en accès libre, en lecture seule ou en écriture seule.

Dans le cadre d’un microcontrôleur, les registres commandent directement les principales fonctionnalités ; par exemple :

  • changer l’état d’une broche reviendra à changer un bit dans un registre
  • récupérer la valeur d’un compteur reviendra à lire un registre
  • écrire dans la mémoire reviendra à écrire dans un registre l’adresse dans laquelle on souhaite écrire, afin que le registre suivant dans lequel on écrira la valeur pointe vers ce secteur de la mémoire
  • lire une valeur analogique reviendra à écrire dans un registre pour déclencher l’évaluation d’un échantillon, attendre qu’un bit dans un autre registre atteigne une valeur particulière, et enfin lire un troisième registre (voire un quatrième registre selon la résolution) pour récupérer la valeur de l’échantillon
  • communiquer sur un port série reviendra à configurer une liaison dans plusieurs registres, puis à veiller un registre d’état de l’entrée de la liaison, pour enfin lire un registre de données jusqu’à ce que le registre d’état indique que la communication est terminée.

Vous l’aurez compris, les registres sont indispensables pour piloter un microcontrôleur.

Si vous voulez vous en convaincre, je vous encourage à lire les sources fournies avec les principales bibliothèques de développement pour microcontrôleur. Vous remarquerez, par exemple, que la fonction C utilisée pour changer la valeur d’une sortie va pointer vers un alias qui se contentera de changer la valeur d’un registre en particulier, ledit alias n’étant rien d’autre qu’une adresse en mémoire qui changera selon le modèle de microcontrôleur choisi lors de la compilation.

les registres de configuration

On les trouve très souvent sous le nom de « fuses » (fusibles) dans les documentations, car, historiquement, il s’agissait de petits fusibles rassemblés au sein d’une grille. On appliquait une tension de 12V à ceux que l’on voulait faire « claquer ». Cela rendait leur écriture définitive. Ils ont depuis été remplacés par de simples cases en mémoire.

Les registres de configuration sont utilisés pour configurer le comportement du microcontrôleur dès la mise sous tension ; on y trouvera des paramètres comme :

  • la configuration de l’horloge (interne ou externe)
  • la protection en écriture et/ou lecture
  • le comportement du watchdog
  • l’activation du débogage
  • l’activation de la broche RESET

Ces registres doivent parfois être configuré à part. Il est important de prendre toutes les précautions en les manipulant ; car une erreur de configuration pourrait rendre le microcontrôleur inutilisable.

Les bus internes

Pour assurer la communication du processeur avec sa RAM, sa ROM et ses périphériques, trois bus sont reliés:

  • le bus d’adresses, où le processeur va indiquer l’adresse qu’il souhaite accéder (en écriture ou en lecture)
  • le bus de données transporte les données selon l’adresse demandée
  • le bus de contrôle, qui permet aux périphériques d’annoncer une interruption et au processeur d’indiquer la direction (écriture ou lecture) d’une opération

Ici aussi, tout est transparent, mais il est toujours intéressant de savoir qu’ils existent.

Les interruptions

Ce concept est important à saisir, car une fois correctement exploitées, les interruptions vont permettre d’effectuer des opérations en réaction à un évènement particulier :

  • un appui sur un bouton
  • l’expiration d’un délai
  • la réception de données sur un bus de communication
  • la fin d’activité d’un périphérique
  • le déclenchement explicite d’une interruption

Les opérations ainsi déclarées dans une interruption permettront d’économiser énormément de cycles au processeur ; car il n’aura pas besoin de gérer de façon logicielle l’éventualité de ces évènements. Ceci permettra également d’alléger considérablement le code source, et même souvent le programme envoyé au microcontrôleur.

Comme dit un peu plus haut, les interruptions transitent sur le bus de contrôle. Une fois arrivée au processeur, celui-ci interrompt son exécution en cours, et exécute une routine particulière déclarée explicitement (par le développeur) ou implicitement (par exemple un chien de garde ou watchdog chargé de redémarrer un microcontrôleur en cas d’état figé). Une fois sa routine terminée, il reprend l’exécution ou il s’était arrêté (ou non, selon ce que décide le développeur). Chaque processeur possède son catalogue d’interruptions disponibles, il est donc nécessaire de se référer à la documentation pour comprendre chaque cas d’utilisation.

Microcontrôleur – Chapitre 4 – Les mémoires

Afin de stocker un programme, des données temporaires ou des données persistantes, le microcontrôleur dispose de plusieurs types de mémoires, ayant chacune des caractéristiques particulières.

La mémoire vive (ou RAM)

Il s’agit d’un espace de stockage où va se dérouler l’exécution d’un programme. Elle doit être rapide en lecture et écriture. Elle est généralement volatile, c’est à dire qu’elle est remise à zéro lorsqu’elle n’est plus sous tension.

Ses caractéristiques la rendent chère, c’est pourquoi elle est en disponible de façon très limitée au sein d’un microcontrôleur (de quelques dizaines d’octets à plusieurs kilo-octets pour l’entrée de gamme). Cette limitation encourage particulièrement l’optimisation du code afin de l’utiliser et la nettoyer efficacement.

En utilisant un compilateur, le developpeur n’a pas à s’occuper de la RAM, il doit seulement veiller à ne pas la gaspiller. En revanche, si on souhaite écrire un programme en assembleur, il devient necessaire de se familiariser avec les instructions d’ecriture et lecture en RAM, ainsi qu’avec l’adressage de ses secteurs.

La mémoire morte (ou ROM)

Cette mémoire est beaucoup plus lente mais permet de rester persistante même hors-tension. Un microcontrôleur va utiliser deux types de mémoire morte :

  • la mémoire « programme », qui va contenir le code assembleur à executer sur le processeur lors de la mise sous tension
  • la mémoire « données », où le programme pourra stocker des données de façon persistante. Elle est souvent appelée EEPROM pour Electrically-Erasable Programmable Read-Only Memory (mémoire effaçable électriquement, programmable en lecture seule).

Les mémoires peuvent être stockées sur un même composant ; on distingue ensuite deux architectures :

  • Harvard, qui va séparer l’accès (par les instructions) à la mémoire programme de la mémoire données, au point que les bus utilisés soient séparés. On peut ainsi transférer simultanément des instructions depuis la mémoire programme et des données vers la mémoire données
  • Von Neumann, où une unité de contrôle gère les entrées et sorties vers une mémoire unifiée. Les instructions y sont donc des données manipulables comme celles-ci.
    (il y a bien entendu d’autres subtilités, mais on restera sur celles-ci)

    Bien que ROM signifie à l’origine « Read-Only Memory » (soit mémoire en lecture seule), il est possible d’y écrire et y réécrire, même depuis le programme lui-même. Sa taille varie beaucoup en fonction de la gamme, et peut aller de quelques centaines d’octets à plusieurs millions.

    Il est possible d’utiliser également des mémoires externes, au travers des bus de communication. Celle-ci sont  très lentes comparées à la ROM interne, mais permettent d’étendre la mémoire disponible pour des programmes très gourmands. Il faudra par contre gérer au niveau du logiciel l’écriture et la lecture depuis ces composants.

Microcontrôleur – Chapitre 1 – Introduction aux microcontrôleurs

Un microcontrôleur est un circuit intégré qui réunit :

  • un processeur,
  • des mémoires,
  • des interfaces,
  • et des périphériques.

Depuis l’avènement de l’informatique embarquée, on le retrouve partout (industrie, téléphonie, automatisation, automobile, …). Ses applications sont infinies.

Nous allons approcher quelques concepts qui permettront de nous lancer dans le développement d’applications utilisant un maximum de fonctionnalités.

Caractéristiques

Il existe une immense variété de microcontrôleurs ; chaque modèle est décrit par :

  • l’architecture de son processeur (PIC, AVR, ARM, MSP430, …) voire sa sous-architecture
  • sa taille de registre (8bit, 16bit, 32bit, 64bit, …)
  • son nombre de broches (8, 12, 32, 48, 64, 100, …)
  • son format (DIP, SMD, VQFN, …)
  • la plage de fréquence de son processeur (par exemple, de 32768Hz à 16MHz)
  • sa plage de tension de fonctionnement (par exemple, 1.8V à 6V)
  • la taille (128o, 2ko, 256ko) et le type de ses mémoires (RAM, SRAM, FRAM, ROM, EEPROM, Flash, …)
  • ses périphériques internes :
    • des canaux convertisseurs (analogique vers numérique), décrits par leur précision (10bit, 12bit, 16bit, …) et leur vitesse
    • des compteurs (aussi appelé timers) de 8bit, 16bit, …
    • des sorties PWM
    • des comparateurs
    • divers outils (débogueur, chien de garde, RESET, …)
  • ses bus de communication (SPI, I2C, CAN, UART, USB, …)

D’autres caractéristiques sont implicitement liées aux composants intégrés :

  • sa plage de température de fonctionnement (par exemple -40°C à 85°C)
  • sa consommation (par exemple 2microA au repos, 30mA en activité maximale)
  • son prix (de quelques centimes d’euros à plusieurs centaines)
  • la fiabilité de ses mémoires (par exemple 10000 cycles d’écriture pour de la mémoire Flash)

Utilisation

De par sa faible consommation et son volume réduit, le microcontrôleur est un élément clef dans l’industrie de l’informatique embarquée. Le support de différents bus de communication lui permet d’être connecté à presque tout ce qui peut contenir un courant électrique :

  • périphériques de sortie (écran, diode, moteur, haut-parleur, …)
  • interfaces d’entrée (clavier, souris, micro, écran tactile, boutons, …)
  • appareils de mesure (thermomètre, tachymètre, voltmètre, horloge, …)
  • interfaces de communication (GSM/GPRS/HSDPA, GPS, liaison radio, Bluetooth, WiFi, Ethernet, …)
  • extensions (mémoires, entrées/sorties, bus de communication, …)
  • d’autres microcontrôleurs

Il n’est donc pas étonnant de le retrouver partout, par exemple :

  • dans l’industrie pour piloter des machines
  • dans l’automatisation, dans des télécommandes
  • dans l’automobile pour diagnostiquer un véhicule en temps réel
  • dans le monde du spectacle pour piloter l’éclairage
  • dans l’aéronautique pour exploiter des données de vol

Rien que chez vous, vous disposez et utilisez des dizaines de microcontrôleurs sans le savoir :

  • dans une télécommande
  • dans un réveil
  • dans un lave-vaisselle
  • dans un interphone
  • dans un four
  • dans une cafetière
  • dans une voiture
  • dans un baladeur

Bien que l’interface avec un microcontrôleur parait technique, il est en réalité assez facile d’appréhender la programmation sur ces machines.

Le faible coût des modèles d’entrée de gamme ainsi que la possibilité de disposer de kit de développement à prix cassé chez certains constructeurs (TI et son Launchpad MSP430 à moins de 20€, Atmel avec ses ATTiny et ATMega présents dans les Arduino, leurs dérivés et leurs clones de 1€ à 100€, Microchip et ses PIC minimalistes et éprouvés) donnent une magnifique occasion pour se lancer dans ce monde !

Microcontrôleur – Chapitre 5 – Les bus de communication

Beaucoup d’applications nécessitent de pouvoir communiquer avec d’autres périphériques ou systèmes. Selon la gamme du microcontrôleur, on peut trouver un ou plusieurs bus de communication. Nous allons parler des principaux bus et de leurs protocoles respectifs.

Qu’est ce qu’un bus ?

Un bus est une topologie de réseau où tous les nœuds sont reliés à un seul lien, appelée le bus. Ce bus peut contenir plusieurs lignes, toutes communes à chaque nœud du réseau.

Ainsi, toute donnée émise sur un bus est reçue par tous les nœuds en même temps (tant que le bus est constitué d’un lien physiquement court).

Série ou parallèle ?

Une liaison série est une liaison où les données se suivent. On l’oppose à la liaison parallèle, ou chaque bit est envoyé sur une broche, ce qui permet un débit plus important mais augmente le risque de parasites à cause d’un nombre de lignes plus élevé.

Une analogie ? Une liaison série serait une conversation avec une personne, qui épèle chaque mot lettre par lettre, tandis qu’une liaison parallèle serait une conversation avec huit personnes épelant chacune une lettre en même temps (les lettres arrivent donc 8 par 8). La première conversation serait assez limpide, par contre la seconde pourrait poser problème si :

  • une personne parle plus fort que les autres (i.e. parasite leur signal)
  • une personne est en retard sur le reste du groupe (i.e. souffre d’une latence due à la ligne).

Les principaux bus

Il existe énormément de bus, et certains ont émergés suite à de véritables guerres de standards entre les constructeurs. La plupart des composants

I²C

I²C (ou I2C) est un bus série de bas niveau développé par Philips Semiconductor en 1982. I²C signifie Inter-Integrated Circuit. Puisqu’il s’agit d’une marque déposée, beaucoup de constructeurs parlent de TWI (Two-wire interface) lorsqu’ils se réfèrent à ce bus. Il est de courte portée (quelque mètres) et fonctionne généralement à 100kb/s (des versions plus récentes fonctionnent toutefois à plus de 3Mb/s).

On utilise ici deux lignes (en plus d’une masse commune) pour communiquer sur ce bus :

  1. une ligne d’horloge, appelée SCL (Serial Clock Line, parfois abrégée SCK ou CLK)
  2. une ligne de données, appelée SDL (Serial Data Line, qu’on écrit aussi SDA ou DATA)

Le protocole utilisé par I2C est dit :

  • maitre-esclaves, car seul un nœud maitre peut communiquer avec les esclaves du réseau (1 ou tous)
  • multi-maitres, car plusieurs maitres peuvent être présents sur le réseau
  • synchrone, car on utilise un mécanisme de requête/réponse
  • half-duplex, car on peut communiquer dans les deux sens, mais pas simultanément

On utilise un adressage sur 7 bits pour identifier les nœuds, ce qui permet (en théorie) jusqu’à 128 nœuds sur le réseau. Certaines adresses sont réservées pour le broadcast (communiquer vers tous les nœuds en même temps). En pratique, les constructeurs on tendance à fixer l’adresse de certains nœuds, ce qui peut causer des collisions.

Ce protocole de communication a de nombreuses applications ; écrans, mémoires, horloges temps-réel, …

SPI

Une liaison SPI (Serial Peripheral Interface) est un bus un peu particulier car si 3 lignes sont bien communes à chaque nœud du réseau (en plus de la masse), il faut ajouter une ligne entre chaque esclave et le maitre. Par exemple un réseau entre un maitre et trois esclaves aura besoin de 6 lignes (plus la masse).

Les lignes communes sont :

  1. une ligne d’horloge, appelée SCLK (Serial Clock, ou SCK, CLK)
  2. une ligne de données maitre vers esclave : MOSI (Master Output, Slave Input ; SIMO/MDO/SDI/DO/DOUT/SI/MTSR)
  3. une ligne de données esclave vers maitre : MISO (Master Input, Slave Output ; SOMI/SDO/MDI/DI/DIN/SO/MRST)

À chaque nœud esclave est ajoutée une ligne SS : Slave Select (choix de l’esclave, souvent abrégé CS ou SYNC). On ajoute généralement une barre au dessus du sigle pour préciser qu’il s’agit d’une entrée active à l’état bas ; en effet, l’esclave n’acceptera de répondre que si sa ligne SS est mise à la masse.

Cette particularité du bus SPI rend l’adressage inutile, il est cependant envisageable d’utiliser un registre à décalage pour piloter un grand nombre d’esclave avec un nombre limité de broches sur un microcontrôleur.

Le bus SPI est :

  • à maitre unique, il n’est pas possible d’avoir plusieurs maitres sur le réseau
  • synchrone
  • full-duplex, car maitre et esclave peuvent transmettre simultanément

Sa portée est de quelques mètres également, par contre son débit est bien plus élevé ; il est possible d’atteindre 100Mbit/s en SPI. Dans notre cas, des débits de 1 à 10Mbit/s seront suffisants.

RS-232

Le RS-232 est un bus de périphériques conçu en 1962. Contrairement aux liaisons SPI et I2C, il est possible de l’utiliser sur plusieurs dizaines de mètres, au détriment du débit (de 2400 à 115200b/s et même au delà dans certaines conditions).

Si vous avez disposé d’un ordinateur avant l’avènement de l’USB, vous avez forcément branché une souris ou un modem sur un port RS-232. On l’appelait à l’époque « port série » ou « port COM ». On l’appelle parfois UART voire USART, du nom d’un composant, souvent présent dans les microcontrôleurs, qui permet de transmettre et recevoir des données sur une liaison série.

Le bus fonctionne sur au moins deux lignes (plus la ligne de masse) :

  1. une ligne d’émission TxD ou TX
  2. une ligne de réception RxD ou RX

(D’autres lignes sont disponibles pour améliorer la fiabilité de la liaison, mais ne sont pas obligatoire)

Cette topologie ne permet de lier que deux périphériques entre eux en mode duplex, car les lignes d’émission et de réception sont croisées (chaque broche d’un périphérique est connectée à l’autre broche sur le périphérique distant).

De plus, l’absence d’horloge partagée peut provoquer des erreurs ; leur probabilité augmente avec le débit souhaité.

Une liaison RS-232 fonctionne :

  • de pair-à-pair ; il n’y a ni esclave ni maitre défini au niveau du protocole
  • de façon synchrone ou asynchrone ; c’est au logiciel de gérer comment se déroule la communication
  • en full-duplex ; les deux pairs peuvent communiquer en même temps.

Du fait de sa simplicité à mettre en œuvre tant au niveau matériel (trois fils suffisent), que logiciel, et de sa disponibilité quasi immédiate sur PC, c’est un bus de choix pour des petits projets.

De plus, de nombreux périphériques se pilotent également par liaison série : GPS, lien Bluetooth, modem, …

1-Wire

Mis au point par Dallas Semiconductor Corp, le bus 1-Wire, comme son nom l’indique, fonctionne avec une seule ligne (plus une masse).

Cette caractéristique lui procure une immense portée (jusqu’à plusieurs centaines de mètres), mais ne lui permet de fonctionner qu’avec un débit réduit (16,3kbit/s). Ce bus peut supporter plusieurs dizaines de noeuds.

1-Wire fonctionne :

  • d’un maitre vers plusieurs esclaves
  • de façon synchrone
  • en half-duplex

Les nœuds sont adressés sur 64 bits (ce qui donne plus de 18 milliards de milliards d’adresses possibles). Une commande d’énumération permet de faire l’appel pour ainsi détecter tous les nœuds d’un réseau très rapidement.

1-Wire est basé sur un protocole propriétaire ; les implémentations de nœuds maitres sont libres, par contre les nœuds esclaves sont prohibés ; ceci permet à la société de décider quels sont les périphériques utilisables sur son bus. Dans la théorie, rien n’empêche d’implémenter un nœud esclave avec un microcontrôleur.

Malgré cela, il s’agit d’un bus très simple à mettre en œuvre, car une seule broche suffit, et elle peut être gérée de façon logicielle sans grand impact sur la mémoire du programme.

Plusieurs types composants sont disponibles en tant qu’esclave 1-Wire :

  • des horloges temps-réel
  • des mémoires dont certaines peuvent être chiffrées
  • des thermomètres
  • des bascules
  • des convertisseurs

USB

Hérité du port série, l’Universal Serial Bus est apparu en 1996. Contrairement à la plupart des autres bus, où il est possible de choisir une tension au sein d’une plage, l’USB ne fonctionne qu’avec une tension de 5V.

Son rayon d’action est court (quelques mètres, rarement plus de 5) et ses évolutions lui on permis d’atteindre des débits de plusieurs gigabits par seconde.

Ces performances sont atteintes par l’USB 3, et sont totalement hors de portée des microcontrôleurs d’entrée de gamme. Néanmoins, il est possible de communiquer jusqu’à quelques mégabits par seconde avec un microcontrôleur approprié.

Un port USB standard est composé de quatre lignes :

  1. la masse GND
  2. la ligne D-
  3. la ligne D+
  4. et l’alimentation VCC, à 5V

On n’abordera pas ici le cas de l’USB 3, avec ses lignes supplémentaires.

L’USB permet des communications :

  • entre pairs
  • synchrones et asynchrones
  • en half-duplex

Sa mise en œuvre est plus complexe car il est nécessaire de déclarer plusieurs champs afin de pouvoir communiquer :

  • un identifiant fournisseur
  • un identifiant produit
  • un identifiant texte
  • un descripteur

L’implémentation d’une liaison USB nécessite un article plus approfondi.

Les autres

Bien sûr, il existe beaucoup d’autres type de liaisons, mais vous ne les retrouverez pas forcement au sein d’un microcontrôleur.

En voici quelques uns :

  • Ethernet : très connu en informatique, il est dédié au réseau, principalement pour les protocoles Internet (dont IP).
  • CAN : ce bus de terrain se retrouve notamment dans l’industrie et surtout dans l’automobile. Il équipe tous les véhicules automobiles depuis les années 1990, en tant que bus de pilotage et de diagnostic. L’accès au bus CAN permet de manipuler tous les composants d’un véhicule, les phares, l’injection, les serrures, les airbags,…
  • Bluetooth : liaison sans fil entre deux périphériques appairés, il peut être facile à mettre en œuvre au travers d’une liaison série. Présent dans tous les téléphones, ordinateurs portables et tablettes depuis le milieu des années 2000, il permet de connecter très facilement et sans-fil des projets avec un appareil faisant office d’interface.
  • ISM, Z-Wave, LIN, …

Limitations physiques

On a pu voir rapidement que les bus ont un champ d’action et un débit limité. Ces limitations sont généralement liées aux caractéristiques physiques des câbles ainsi qu’a leur blindage et leur agencement au sein de la ligne.

La limite électronique

La particularité d’un bus de communication est que l’ensemble d’une ligne (comprendre un câble) ne peut être qu’à un seul état électrique à un moment donné : VCC ou GND. Or, les électrons ne circulent pas instantanément dans les câbles ; par exemple on estime leur vitesse à environ 273 000 km/s dans le cuivre.

Ce qui veut dire que pour un câble de 273 000 km, un signal aura besoin d’une seconde pour être propagé de bout en bout. Si les données sont envoyées « par paquet », la latence aura peu d’impact. Mais dans le cadre de protocoles synchrones (SPI, I2C, …), la longueur du câble peut avoir une incidence sur le fonctionnement.

Bien entendu, on utilisera plus souvent des lignes beaucoup plus courtes, mais les limitations existent également en fonction des types de bus. Ainsi, un bus I2C ou SPI ne fonctionnera pas correctement au-delà de quelque mètres.

L’atténuation du signal

Lors de la circulation des électrons au sein du câble, de l’énergie est perdue sous forme de chaleur à cause de la résistance du câble ; on appelle cela l’effet Joule. S’il est négligeable en dessous d’un mètre il est important de le prendre en compte pour de plus grande distance.

L’induction et la permittivité jouent également un rôle dans l’atténuation.

On exprimera cette atténuation en décibel, et on peut la mesurer entre les tensions en entrée et en sortie du bus.

Les interférences internes

L’induction est un phénomène électromagnétique qui peut provoquer l’apparition d’un courant dans un conducteur à proximité d’un autre conducteur alimenté. Il est ainsi possible que des lignes interférent entre elles. Dans le cas de l’altération d’un signal, on parle de diaphonie. Pour éviter cela, on utilise des blindages et des paires torsadées.

Les interférences externes

Les faibles tensions utilisées en électronique sont d’autant plus sensibles lorsqu’elles sont utilisées dans des milieux assez hostiles, ou en mouvement (électrostatique).

Les hautes tensions ou les champs magnétiques à proximité peuvent fortement altérer le fonctionnement de tout dispositif électrique, dont les bus de communication.

Solutions techniques

Le blindage

Indispensable sur de longues distances, le blindage est présent principalement dans les câbles USB et dans certaines catégories de câbles Ethernet. Il s’agit le plus souvent d’un matériau métallique tressé.

Les paires torsadées

Comme son nom l’indique, une paire torsadée est constituée de deux lignes enroulées l’une autour de l’autre. Cette configuration permet de réduire de manière sensible les interférences entre les lignes.

la transmission différentielle

Au lieu de comparer la différence de potentielle entre une masse et une tension, on va comparer la différence de potentielle entre une tension et sa valeur opposée. Ce concept est très souvent utilisé notamment avec les paires torsadées : on considère en effet que les lignes sont si proches, qu’une interférence extérieure affecterait les deux lignes exactement de la même façon.

Ainsi, si au lieu de recevoir -5V et 5V, je reçois 2V et 12V, en faisant la différence des deux tensions, on obtient 10V à chaque fois.

Une règle POV commandée par une application Android

Mais c’est quoi ?

Une règle POV (Persistence of vision : Persistance rétinienne) est une petite barre de 8 diodes (ou plus (ou même moins)) qui clignotent suivant un schéma précis. En déplaçant rapidement la règle, ce scintillement « dessine » des points qui permettent alors d’écrire un petit texte.

Il y a une foule d’exemples sur Internet ; motorisés, montés sur un ventilateur, une roue de vélo, un chien, un nunchaku, …

Voici ce que ça donne :

DSC01560-1920x1080Oui, ceci est une photographie, sans aucun montage ni aucune retouche (hormis le recadrage). Il suffit d’ouvrir l’obturateur (mode BULB), de faire son dessin, puis de le refermer. Il est nécessaire de disposer d’une télécommande pour cela.

J’ai choisi d’en fabriquer une simple, dont on peut changer le texte en utilisant un smartphone Android, par Bluetooth.

Fabriquer sa règle POV

Un peu de réflexion

Puisque les diodes représentent une colonne d’un caractère à un instant précis, il est nécessaire de « traduire » les caractères en colonnes de diodes. J’ai choisi ici de fixer la taille des caractères à 4 colonnes. Chaque colonne sera ainsi représentée par un nombre de 8-bits, le pixel du bas étant le bit de poids faible, et le pixel du haut le bit de poids fort.

Avec un tableur et une petite demie-heure, on peut dessiner pixel par pixel chaque caractères, pour les convertir en 4 nombres de 8-bits.

lettreOn construit de cette façon le tableau suivant de 380 octets, contenant tous les caractères ASCII affichables :

Calibrer le délai

Une étape importante lors du développement du programme est de choisir un délai entre deux colonnes d’un caractère. S’il est trop long, le caractère sera plat. S’il est trop court, le caractère sera étroit.

Une expérience simple permet de calibrer ce délai. Il suffit d’un chronomètre, et de votre bras :

  1. Chronométrez vous pendant un temps défini (10 secondes par exemple) et comptez des mouvements avec votre bras, comme si vous utilisiez cette règle. Par exemple, basculez le de 60 degrés puis revenez à votre position.
  2. Pour ma part, en 10 secondes, je fais 30 mouvements aller et retour, soit 60 trajets.
  3. Estimez le nombre de caractères que doit comporter un trajet. J’ai choisi 20.
  4. Maintenant, appliquer la formule suivante :
    D\'{e}lai = \frac{Temps_{total}}{Nombre_{trajets} \times Nombre_{caract\'{e}res}  \times Nombre_{colonnes}} = \frac{10}{60 \times 20 \times 4} \approx 2 ms

Le schéma

pov_bb

Le montage est trivial, même si, j’utilise un registre à décalage pour économiser des broches sur mon micro-contrôleur. Cela parait idiot puisqu’il y a largement de quoi faire avec les broches présentes sur un ATMega.

Pour le module Bluetooth, il s’agit d’un modèle chinois basé sur la puce BC417. Il n’y a aucune documentation officielle disponible, mais en cherchant « linvor » on trouve quelques informations utiles. Pour le configurer, je l’ai connecté au port série d’un PC, puis je lui ai envoyé les commandes AT suivantes :

 

 Le code pour le microcontrôleur

Je n’ai pas ajouter de commentaires, il est relativement lisible.

 L’application Android

Ici, j’ai triché un peu ; je me suis servi d’AppInventor du MIT. Il s’agit d’un outil en ligne permettant de fabriquer une application Android sans connaissance particulière.

pov_appJe vous fourni le fichier installable (APK), ainsi que le projet « source ». Il n’y a aucune garantie livrée avec ceux-ci.

Notez qu’il est nécessaire d’appairer votre smartphone avec le module Bluetooth avant de démarrer l’application.

Mise à jour: J’ai modifié mon code pour qu’il écrive le message reçu depuis le port série en mémoire, pour pouvoir le charger au redémarrage.

AVR – Chapitre 9 – Créer son périphérique USB

L’usine à gaz USB

L’Universal Serial Bus est une norme de transfert de données en série (c’est à dire que les données sont transmises les unes à la suite des autres ; à opposer au mode parallèle où les données sont transmises via plusieurs canaux). Créé en 1994 par un consortium de constructeurs, l’USB est maintenant incontournable depuis le début des années 2000. Tout fonctionne maintenant par USB : claviers, souris, stockages de données, caméras, imprimantes, protocoles sans fil,.. L’idée de pouvoir rejoindre cette communauté doit vous sembler excitante !

Il est important de saisir certains concepts avant de se jeter à l’eau. Par ailleurs, je ne traiterai ici que des périphériques de version USB 1.1. Cette limitation est due aux caractéristiques techniques des micro-contrôleurs.

Les caractéristiques du bus

L’USB 1 et 2, se déploit sur quatre fils :

  • L’alimentation +5V VCC en rouge (parfois orange)
  • Le câble « données » D- en blanc (parfois jaune)
  • Le câble « données » D+ en vert
  • et la masse GND en noir (parfois bleu)

Communément, un périphérique USB 1.x peut être alimenté par un courant de 150mA (500mA pour l’USB 2.0). Si vous avez besoin de plus de puissance, il sera nécessaire de prévoir votre propre source de courant (batteries, alimentation, …).

Les connecteurs à chaque bout peuvent avoir différentes formes, les plus courants étant les suivantes :

Broches Type A Type B Mini-A Mini-B Micro-A Micro-B
VCC 1 1 1 1 1 1
D- 2 2 2 2 2 2
D+ 3 3 3 3 3 3
GND 4 4 5 5 5 5

La topologie

Le bus se dessine suivant une topologie en arbre, l’hôte étant la racine. Il est possible de connecter jusqu’à 127 périphériques, sur 5 niveaux (derrière des hubs).

L’USB peut fonctionner jusqu’à 3 mètres et même au-delà en utilisant des répétiteurs alimentés (ceci est néanmoins déconseillé).

Les identifiants

Chaque périphérique s’identifie grâce à deux codes :

  • le code fournisseur (vendor ID) sur 2 octets (soit 65536 possibilités). Ce code est distribué par l’USB Implementers Forum. Il est nécessaire de payer entre 2500 et 5000 US$ pour disposer du sien de façon officielle. Heureusement, il existe des codes pour les « bricoleurs » comme nous.
  • le code produit (product ID) sur 2 octets  (soit 65536 possibilités). Ce code est défini par le fournisseur à sa guise. Généralement, tous les produits d’une même génération partagent le même code produit.

On les représente communément sous forme hexadécimale, sur 4 chiffres, en les séparant par un deux-points. Voici quelques exemples :

  • Appareil photo Sony NEX-5R : 054c:066e
  • Téléphone Nexus 4 : 18d1:4ee1

Ce couple ne permettent pas d’identifier de manière unique un individu, seulement sa nature. Pour l’identifier précisément, il sera nécessaire de le prévoir de manière logicielle, ou en utilisant son chemin de connexion dans le réseau (par exemple, hub 2 > hub 2.1> hub 2.1.4 > port 2.1.4.5).

Par ailleurs, il existe une identification textuelle représentée par une chaîne de caractères.

Les classes USB

Vue la myriade de possibilités qu’offre la norme USB, il est nécessaire de regrouper les périphériques par classes, afin de factoriser l’effort à fournir pour le support de nouveaux matériels.

Ainsi, les souris, claviers, joysticks, etc. sont syndiqués sous la classe « Human interface device » (HID): 0x03. Les périphériques de stockage sont sous la classe « Mass storage » : 0x08. Cette page (en anglais) résume les classes officiellement déclarées.

Suivant la classe, les systèmes d’exploitation ont bâti des pilotes (drivers) afin de rendre plus ou moins transparente l’intégration des périphériques USB. Ainsi, la plupart des périphériques de classe HID sont supportés sans la nécessité de fournir des pilotes. C’est cette classe qui va nous intéresser, car il s’agit de la plus simple à mettre en œuvre.

Le « Report Descriptor »

Dans le cas de la classe HID, il est nécessaire de fournir un « report descriptor ». Il s’agit d’une chaîne d’entiers qui décrit le format de données que le périphérique va retourner à l’hôte.

Prenons l’exemple d’une souris ; celle-ci doit retourner les informations suivantes :

  • l’état de ses boutons (gauche, droite, milieu, molette vers le haut et molette vers le bas)
  • un mouvement sur l’axe X
  • un mouvement sur l’axe Y

La classe HID « mouse »

Voici une façon de représenter ces données, sur 3 octets :

octet bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
Boutons -     -     -     molette bas molette haut clic droit clic milieu clic gauche
Axe X entier relatif entre -128 et 127
Axe Y entier relatif entre -128 et 127

Voici à quoi correspond le rapport :

 

Pour aider à la rédaction de ce rapport, il est possible d’utiliser HID Descriptor Tool disponible ici. Voici à quoi cela ressemble :

HIDtoolJe ne vais pas  vous faire un cours sur le contenu détaillé de ces rapports. Il suffit simplement de savoir qu’ils existent et à quoi ils servent. Pour plus de détails, voici une bonne introduction (en anglais).

La classe HID « Custom »

Dans mon tutoriel, je vais utiliser une version allégée qui n’utilise pas de donnée formatée :

L’utilisation d’un tel rapport permet de s’affranchir des limitations du format HID, tout en étant facile à « installer » sur l’hôte. Cependant, il devient nécessaire d’utiliser un logiciel sur l’hôte pour se servir du périphérique.

La bibliothèque V-USB

AVR-GCC ne fournit pas de méthode pour utiliser l’USB, et ce n’est pas son but.

Il est donc nécessaire de charger une bibliothèque (library) tierce. V-USB est une bibliothèque qui propose une implémentation logicielle de la norme USB 1.1 pour les micro-contrôleurs AVR.

Celle-ci est proposée sous double licence : GPL version 2 ou commerciale. Cela implique que vos projets qui incluent cette bibliothèque doivent être publiés en accord avec la GPL (et donc avec distribution des sources), ou nécessite l’achat d’une licence commerciale (9.90€ pour une licence « Hobbyiste », 500€ pour une licence professionnelle).

Il existe des alternatives, mais la plupart nécessitent des micro-contrôleurs « puissants » ou des contrôleurs USB matériel. V-USB est utilisable sur des micro-contrôleurs modestes, dont l’ATTiny85.

Que contient-elle ?

La version actuelle (20121206) contient le répertoire usbdrv, dans lequel vous trouverez (entre autres) :

usbdrv.h les définitions nécessaires
usbdrv.c le cœur du pilote
usbdrvasm.S code assembleur utilisé par le pilote
usbdrvasm*.inc fichier appelé depuis usbdrvasm.S
asmcommon.inc fichier appelé depuis usbdrvasm.S
oddebug.c fournit plusieurs fonction de débogage
oddebug.h Les définitions requises par oddebug.c
usbportability.h Des définitions utilisés par le compilateur
usbconfig-prototype.h un exemple de fichier de configuration

Pour le bon fonctionnement du projet, il est important de copier l’intégralité du répertoire dans vos sources.

Comment fonctionne la bibliothèque ?

La configuration

Toute la configuration se trouve dans le fichier usbconfig.h, que vous devez ajouter à vos sources. Pour cela, vous pouvez copier le fichier usbconfig-prototype.h. Voici les points importants à adapter à votre projet :

Macro Description Exemple
USB_CFG_IOPORTNAME Port du microcontrôleur utilisé pour la communication en USB B
USB_CFG_DMINUS_BIT Numéro de broche utilisé par D- 0
USB_CFG_DPLUS_BIT Numéro de broche utilisé par D+ 2
USB_CFG_IS_SELF_POWERED Indique si votre périphérique dispose de sa propre source (0 : Non/ 1 : Oui) 0
USB_CFG_MAX_BUS_POWER Courant maximum que consomme votre périphérique (en mA) 50
USB_CFG_IMPLEMENT_FN_WRITE Indique si vous souhaitez utiliser la fonction usbFunctionWrite() ou non 0
USB_CFG_IMPLEMENT_FN_READ Indique si vous souhaitez utiliser la fonction usbFunctionRead() ou non 0
USB_CFG_LONG_TRANSFERS Indique si vous souhaitez faire des transferts de plus de 254 octets 0
USB_CFG_VENDOR_ID Octet faible et octet fort qui définissent l’identifiant fournisseur 0xc0, 0x16
USB_CFG_PRODUCT_ID Octet faible et octet fort qui définissent l’identifiant produit 0xdc, 0x05
USB_CFG_VENDOR_NAME Nom du fournisseur (il est demandé d’y mettre un nom de domaine) ‘c’,’i’,’c’,’a’,’t’,’r’,’i’,’c’,’e’,’.’,’e’,’u’
USB_CFG_VENDOR_NAME_LEN Taille de la macro fournisseur 12
USB_CFG_DEVICE_NAME Nom du produit ’I’,’R’,’-‘,’L’,’e’,’d’
USB_CFG_DEVICE_NAME_LEN Taille de la macro produit 6

Bien entendu, il reste beaucoup de macros, je vous laisse découvrir leur utilité dans les commentaires du code.

Les callbacks

V-USB implémente plusieurs « callbacks » (fonctions de retour), qui sont appelées à la suite d’événements précis. Cela ressemble à des interruptions, sauf que celles-ci sont purement logicielles :

  • usbFunctionSetup  : appelée à l’arrivée d’une requête ; c’est ici que vous définirez ce qu’il doit se passer au niveau du micro-contrôleur.
  • usbFunctionWrite  (nécessite USB_CFG_IMPLEMENT_FN_WRITE à 1) Cette fonction est appelée quand la fonction usbFunctionSetup retourne USB_NO_MSG et si la requête est de type USBRQ_HID_SET_REPORT (0x09).
  • usbFunctionRead  (nécessite USB_CFG_IMPLEMENT_FN_READ à 1) Cette fonction est appelée quand la fonction usbFunctionSetup retourne USB_NO_MSG et si la requête est de type USBRQ_HID_GET_REPORT (0x01).

Il y en a d’autres mais ce sont les principales. Ce diagramme résume l’utilisation que vous pouvez faire de ces méthodes.

USBlibs

La bibliothèque LibUSB

Comme je l’ai indiqué lors du chapitre sur les « report descriptors », nous allons avoir besoin d’un programme sur la machine hôte qui se chargera de dialoguer avec le micro-contrôleur branché en USB. Je vais utiliser ici libusb.

Ne voulant pas m’embêter avec la signature des pilotes sous Windows (obligatoire depuis la version 8) j’ai décidé de tout faire sous Linux (j’utilise une Fedora 20).

LibUSB est une bibliothèque dont le but est de fournir des méthodes de gestion et d’accès aux périphériques branchés en USB.

Le programme se découpe en deux parties :

  • une partie d’initialisation, où l’on cherche le périphérique USB qui nous intéresse
  • une partie d’exécution, où l’on effectue les actions nécessaires, notamment grâce à la méthode  libusb_control_transfer()  que vous avez pu apercevoir dans mon diagramme plus haut.

La structure du projet

Pour mon exemple, je vais utiliser un petit projet qui permet à mon PC de servir de télécommande pour une bande de LED RGB.

Aperçu

Voici comment doit se présenter votre projet :

  \dirtree{% .1 projet. .2 commands. .3 send.c. .3 Makefile. .2 usbdrv. .3 .... .2 Makefile. .2 remote.c. .2 requests.h. .2 usbconfig.h. }

Vous verrez dans beaucoup d’exemples que les fichiers remote.c , requests.h ,  usbconfig.h  et Makefile (celui du micro-contrôleur) se trouvent dans un répertoire firmware. Vous pouvez ranger ces fichiers comme bon vous semble, l’important est que cette structure soit claire pour vous.

Le schéma

Avant de parler du code, voici un exemple de branchements :ir_rgb_schémaIl n’y a pas grand chose à dire sur ce schéma. Les deux diodes Zener doivent limiter la tension à 3.3V.

 

Les définitions

usbconfig.h

Comme décrit plus tôt, il s’agit de la configuration pilote V-USB. Celui est chargé automatiquement, à partir du moment où votre projet inclus le fichier usbdrv.h . Il sera également inclus lors de la compilation du programme hôte.

 

requests.h

Ce fichier contient les requêtes USB personnalisées. Comme pour le fichier précédent, il sera inclus lors de la compilation.

 

Le programme du micro-contrôleur

remote.c

Il s’agit du code principal, qui sera envoyé sur le micro-contrôleur.

 

Makefile

Ici il faut faire quelques ajouts, pour prendre en compte la bibliothèque. J’ai mis en surbrillance les lignes qui diffèrent par rapport à d’habitude.

 

Le programme hôte

Ici, tout se passe sur une machine sous Fedora 20.

commande-hote.c

Comme vous pouvez le voir, ce code à besoin d’inclure les fichiers  ../requests.h  et ../usbconfig.h .

Makefile

Je ne sais pas ce qu’il en ait quant à l’outil pkg-config en dehors de Fedora, mais celui est plutôt pratique puisqu’il génère les options tout seul.

Utiliser un ATTiny85

Le problème que pose l’utilisation d’un ATTiny, est qu’il faut utiliser une horloge cadencée au minimum à 12MHz. Or l’ATTiny ne fonctionne correctement à 8MHz maximum. On va utiliser une astuce pour le passer à 16.5MHz sans cristal externe : la PLL (Phase-Locked Loop : boucle à verrouillage de phase).

Sans entrer dans les détails, il s’agit d’un dispositif qui permet d’augmenter la fréquence d’un micro-contrôleur (basée sur un cristal), au prix de sa stabilité. Il est ainsi possible de faire fonctionner un ATTiny au delà de 80MHz ! Mais dans notre cas, 16.5MHz suffiront.

Pour cela il est nécessaire d’harmoniser la fréquence au début de l’exécution. Ceci se fait en définissant un « hook » dans le ficher usbconfig.h , en y ajoutant la fonction hadUsbReset() , qui s’occupera d’appeler une autre fonction ( calibrateOscillator() ), qui calibrera l’oscillateur PLL.

L’exemple donné dans cet article prend déjà en compte ces modifications.

Dans le cas d’un micro-contrôleur capable d’utiliser une horloge à 16.5MHz, il n’y a pas besoin de ce dispositif, et donc des fonctions hadUsbReset() ,  calibrateOscillator()  ainsi que des lignes suivantes dans usbconfig.h  :

 

 Et voilà !

Voici les sources du projet : RGB remote (fichier 7z).

Lorsque vous branchez votre périphérique, les messages suivants doivent apparaitre dans dmesg.

Une fois le programme hôte compilé, vous pouvez envoyer des commandes au micro-contrôleur avec les commandes suivantes :

 

Fonctionnement et émulation USB d’une télécommande infrarouge pour LED RGB (Partie 1)

En traînant un peu sur le net, je suis tombé sur des bandes de diodes RGB (rouge/vert/bleu) contrôlées par télécommande (44 boutons) pour une trentaine d’euros les dix mètres. Il s’agit d’un clone chinois des lampes Living Colors de Phillips.

Après l’avoir installé et joué un peu avec l’éclairage, j’ai cherché à savoir comment celui-ci fonctionne ; l’idée étant de pouvoir contrôler le tout depuis un PC.

J’aurais pu démonter le boitier de commande de la bande de diodes, mais cela m’aurait forcer à tirer un second câble (en plus de l’alimentation) du PC jusqu’à la bande de diode, ce qui n’est pas pratique. Alors pour le défi, j’ai cherché à savoir comment fonctionne la télécommande, et comment émuler ses fonctions.

Télécommande RGB

S’équiper à moindre frais

Si vous disposez d’un PC avec une entrée pour micro, vous n’aurez pas besoin d’investir dans beaucoup de matériel. Prenez une prise jack mâle, une diode infrarouge réceptrice, une résistance de 4.7kΩ et un peu de fil.

irda_schema

Puis, téléchargez le logiciel Audacity (open-source). Il s’agit d’un éditeur de signal sonore, dont l’interface est un peu rude, mais il est très simple à utiliser.

Il suffit maintenant de choisir la bonne source de son, et de démarrer la capture. Un petit clic sur un des boutons de la télécommande, et hop !

irda_large

C’est quoi ce bazar ?!

Ici, on récupère le signal émis par la télécommande, mais seulement, celui-ci est échantillonné a 44100Hz. C’est bien pour « lire » le signal, mais ceci ne suffit pas à l’émuler.

Il est nécessaire de comprendre comment fonctionne, en théorie, le signal infrarouge.

La théorie (oui, il en faut un peu tout de même)

L’infrarouge est un signal très rudimentaire, qui à la fâcheuse tendance d’être sensible à la chaleur. En effet, toute source de chaleur (corps vivant, soleil, ampoule, …) émet naturellement des rayonnements infrarouges. Cela peut interférer avec le transfert d’un signal. Il n’est donc pas possible de considérer la présence d’un rayonnement comme un [ccie]1[/ccie] et l’absence comme un [ccie]0[/ccie].

On va alors utiliser un signal pulsé à une fréquence particulière. Ainsi, un [ccie]1[/ccie] sera un signal pulsé d’une durée déterminée, tandis qu’un [ccie]0[/ccie] sera un silence de même durée. Ceci implique de synchroniser le signal sur une fréquence particulière.

Rendered by QuickLaTeX.com

Pour déterminer cette fréquence, la méthode du microphone atteint ses limites ; il faudrait ici un oscilloscope avec une fréquence d’échantillonnage d’au moins une centaine de kilohertz. À défaut, il faut chercher un peu.

Prenons le signal acquis, et observons le. On a :

  • une longue crête de ~9ms
  • une longe crête de ~4.5ms
  • 4 paquets de 16 crêtes de ~0.5ms ou ~1.5ms

En fouillant, je suis tombé sur cet article (en anglais). Il s’agit du protocole infrarouge NEC.

On y est presque !

Le protocole NEC

Il s’agit d’un code, utilisant une fréquence de 38kHz (soit une modulation de 26.3µs) et dont le protocole est :

  • initialisation par un signal pulsé de 9ms (342 modulations), suivi d’un silence de 4.5ms
  • adresse du matériel (ici 0x00) sur 8 bits
  • inverse de l’adresse sur 8 bits
  • commande (ici 0x02 pour « POWER ») sur 8 bits
  • inverse de la commande sur 8 bits

Pour la transmission de l’adresse et de la commande, on code chaque bit de la façon suivante (le bit fort en premier) :

  • un signal pulsé de 560µs (21 modulations), suivi d’un silence de même durée pour [ccie]0[/ccie]
  • un signal pulsé de 560µs, suivi d’un silence de 1690µs pour [ccie]1[/ccie]

Voici ce que donne donc la trame qui permet l’allumage (ou l’extinction) des diodes :

Rendered by QuickLaTeX.com

Mettons cela en pratique

Il suffit de prendre un microcontrôleur (un ATTiny85 peut suffire, mais j’ai préféré prendre un ATMega168 pour ajouter une connexion série par la suite) et d’y brancher une diode infrarouge (émettrice cette fois).

Voici un code minimaliste qui se contente d’allumer et d’éteindre toutes les secondes. Vous y trouverez également les commandes disponibles en macro.

[cce_c]
#include <avr/io.h>
#include <util/delay.h>
#include <avr/sleep.h>

#define IR_PORT    PORTD
#define IR_DDR     DDRD
#define IR_PIN    PD2

#define SIGNAL_BOTTOM()    IR_PORT |= (1<<IR_PIN)
#define SIGNAL_TOP()    IR_PORT &= ~(1<<IR_PIN)
//38kHz
#define PULSE()        {SIGNAL_TOP();_delay_us(13.2);SIGNAL_BOTTOM();_delay_us(13.2);}

#define CMD_UP        0x3A
#define CMD_DOWN    0xBA
#define CMD_PLAY    0xB2
#define CMD_POWER    0x02

#define CMD_RED        0x1A
#define CMD_GREEN    0x9A
#define CMD_BLUE    0xA2
#define CMD_WHITE    0x22

#define CMD_DEEPORANGE    0x2A
#define CMD_LIGHTGREEN    0xAA
#define CMD_DEEPBLUE    0x92
#define CMD_HOTWHITE    0x12

#define CMD_ORANGE    0x0A
#define CMD_LIGHTBLUE    0x8A
#define CMD_PURPLE    0xB2
#define CMD_WARMWHITE    0x32

#define CMD_BROWN    0x38
#define CMD_TURQUOISE    0xB8
#define CMD_PINK    0x78
#define CMD_COLDWHITE    0xF8

#define CMD_YELLOW    0x18
#define CMD_CYAN    0x98
#define CMD_MAGENTA    0x58
#define CMD_ICEWHITE    0xD8

#define CMD_RED_UP    0x28
#define CMD_GREEN_UP    0xA8
#define CMD_BLUE_UP    0x68
#define CMD_QUICK    0xE8

#define CMD_RED_DOWN    0x08
#define CMD_GREEN_DOWN    0x88
#define CMD_BLUE_DOWN    0x48
#define CMD_SLOW    0xC8

#define CMD_DIY1    0x30
#define CMD_DIY2    0xB0
#define CMD_DIY3    0x70
#define CMD_AUTO    0xF0

#define CMD_DIY4    0x10
#define CMD_DIY5    0x90
#define CMD_DIY6    0x50
#define CMD_FLASH    0xD0

#define CMD_JUMP3    0x20
#define CMD_JUMP7    0xA0
#define CMD_FADE3    0x60
#define CMD_FADE7    0xE0

void init_sequence(){
uint8_t i;
for(i = 0; i<255; i++) PULSE();
for(i = 0; i<88; i++) PULSE(); //too lazy too swap to uint_16t (love your memory)
_delay_us(4500);
}

void send(uint8_t bit){
int8_t i;
for(i = 0; i<21; i++) PULSE();
if (bit & 0x01) _delay_us(1130);
_delay_us(560);
}

void send_command(uint8_t address, uint8_t command){
init_sequence();
int8_t i;
for(i=7;i>=0;i–) send(address >> i);
for(i=7;i>=0;i–) send(~address >> i);
for(i=7;i>=0;i–) send(command >> i);
for(i=7;i>=0;i–) send(~command >> i);

}

int main(){
IR_DDR = (1<<IR_PIN);
for(;;){
send_command(0x00,CMD_POWER);
_delay_ms(1000);
}
}
[/cce_c]

Ou quelque chose de plus funky :

[cce_c]
int main(){
IR_DDR = (1<<IR_PIN);
for(;;){
send_command(0x00,CMD_TURQUOISE);
_delay_ms(100);
send_command(0x00,CMD_ORANGE);
_delay_ms(100);
send_command(0x00,CMD_PURPLE);
_delay_ms(100);
}
}
[/cce_c]

AVR – Chapitre 6 – Lire des valeurs analogiques (ADC)

Analogique vs. numérique

Les valeurs analogiques sont à opposer aux valeurs discrètes et logiques. Voici quelques comparaisons entre les deux mondes, avec des exemples pratiques :

Vision analogique Vision logique (numérique)
la porte est à peine ouverte (0.01) la porte est ouverte (1)
le réservoir est presque vide (0.1) le réservoir contient de l’essence (1)
le premier a parcouru 99% du chemin (0.99) le premier n’est pas encore arrivé (0)
il reste 17% de batterie (0.17) il reste de la batterie (1)

Oui, il est frustrant de ne pas atteindre le niveau de détails qui nous semble trivial au quotidien, au sein d’un programme purement logique.

L’ADC

Pour palier à ce manque, il a été ajouté aux microcontrôleurs un ou plusieurs canaux convertisseurs analogique vers numérique. Ceux-ci sont représentés par les broches ADC d’un microcontrôleur, pour « Analog to Digital Converter ».

L’ATTiny85 peut supporter jusqu’à 4 canaux ADC, chacun ayant une résolution de 10 bits, soit 1024 valeurs. Comme pour les PWM (Modulation de signal pulsé), leur utilisation dépend d’une horloge et d’un diviseur.

La théorie

Du point de vue de l’exécution, il s’agit d’approximer une tension inconnue (Vin) contenue entre 0 et une tension de référence (Vref).

Ainsi, le résultat est retourné sous la forme d’un entier entre 0 et 1023 :

ADC = [\frac{V_{IN} \times 1024}{V_{REF}}]

La configuration

Les registres de configuration de l’ADC sont ADMUX, ADCSRA et ADCSRB. Je vais faire l’impasse sur le registre ADCSRB, il permet des options avancées non essentielles pour le moment. Bien entendu, vous retrouverez tous ces registres dans la documentation (À partir de la page 134).

Le registre ADMUX

Bit 7 6 5 4 3 2 1 0
ADMUX REFS1 REFS0 ADLAR REFS2 MUX3 MUX2 MUX1 MUX0

Les bits REFS 2 à 0 déterminent la tension de référence Vref utilisée pour la comparaison.

REFS2 REFS1 REFS0 Vref
0 0 0 Vcc (i.e. la tension d’alimentation fournie au microcontrôleur)
0 0 1 Aref (i.e. tension externe fournie sur la broche Aref/PB0 du microcontrôleur)
0 1 0 1.1V
0 1 1 réservé (ne pas utiliser)
1 1 0 2.56V sans condensateur de découplage sur Aref/PB0
1 1 1 2.56V avec condensateur de découplage sur Aref/PB0

Les bits MUX 3 à 0 indiquent quel canal sélectionné (je ne parlerai ici que des canaux simples).

MUX3 MUX2 MUX1 MUX0 Canal
0 0 0 0 ADC0/PB5
0 0 0 1 ADC1/PB2
0 0 1 0 ADC2/PB4
0 0 1 1 ADC3/PB3
1 1 1 1 ADC4 il s’agit d’un capteur de température interne. Surprise !

Enfin le bit ADLAR indique l’alignement du résultat (1 pour gauche, 0 pour droite).

Le registre ADCSRA

Il s’agit du registre de contrôle, qui va permettre de démarrer la conversion et de savoir quand celle-ci est terminée.

Bit 7 6 5 4 3 2 1 0
ADCSRA ADEN ADSC ADATE ADIF ADIE ADPS2 ADPS1 ADPS0

Les bits ADPS 2 à 0 configure le diviseur (page 136 de la documentation). Le bit ADEN doit être à 1 pour que la conversion puisse fonctionner, mais ceci ne la démarre pas.

Passer le bit ADSC à 1 démarre la conversion. Il repasse à zéro dès que celle-ci est finie.

Le bit ADIE permet de déclencher une interruption lors de la fin d’une conversion, et le bit ADIF témoigne de la fin de la conversion. Dans le cas d’une utilisation sans interruption il est important de le positionner à 1 à la fin de la boucle, sinon la lecture restera bloquée sur la même valeur. Lors de l’utilisation de l’interruption, le chargement de sa procédure réinitialisera automatiquement ADIF, il n’y a donc plus besoin d’y toucher.

Le résultat

Puisqu’il s’agit d’un convertisseur 10 bits, il n’est pas possible de rendre le résultat sur un seul registre de 8 bits. Il y a donc deux façons d’organiser le résultat :

  • aligné à gauche (ADLAR = 1) : les 8 bits les plus forts sont dans le registre haut, et les 2 derniers bits dans le registre bas
    Bit 7 6 5 4 3 2 1 0
    ADCH ADC9 ADC8 ADC7 ADC6 ADC5 ADC4 ADC3 ADC2
    ADCL ADC1 ADC0 -   -     -    -     -      -
  • aligné à droite (ADLAR = 0) : les 2 bits les plus forts sont dans le registre haut, et les 8 derniers bits dans le registre bas
    Bit 7 6 5 4 3 2 1 0
    ADCH -   -     -    -     -      -    ADC9 ADC8
    ADCL ADC7 ADC6 ADC5 ADC4 ADC3 ADC2 ADC1 ADC0

Pour choisir quelle format vous sied le mieux, l’alignement à gauche permet de comparer rapidement le résultat sur 8 bit avec une précision moyenne, alors que l’alignement à droite offre une meilleure précision mais contraint à l’utilisation d’un entier codé sur 16 bits (cf. exemple plus loin).

 Un exemple simple : la photorésistance

Comme vous le savez surement, la photorésistance est un composant électronique à deux pôles, dont la résistance dépend de la luminosité ambiante. Voici un schéma pour la mettre en œuvre :

adc_schem

La résistance R3 est à prévoir en fonction de la photorésistance R1. Celles-ci étant montées en diviseur de tension, on mesurera donc une tension comprise entre :

\frac{R_3}{R_{1}_{MAX} + R_{3}} \times V_{REF} et \frac{R_3}{R_{1}_{MIN} + R_{3}} \times V_{REF}

Pour ma part, j’ai utilisé une résistance R3 de 4,7kΩ et une résistance R1 de 20kΩ, sur un circuit de 5V soit :

\frac{4.7k}{20k + 4.7k} \times 5 \simeq 1V et \frac{4.7k}{0 + 4.7k} \times 5 = 5V

Ainsi, la valeur retournée par l’ADC devrait être comprise entre 205 et 1023.

Le but ici est d’activer la diode dès que la photorésistance R1 atteint un certain seuil (i.e. dès qu’il fait trop sombre), et de l’éteindre dès qu’on y voit suffisamment clair, à la manière de l’éclairage public qui s’allume à la tombée de la nuit, et s’éteint quand le soleil brille. On laissera une marge entre les deux pour éviter un va-et-vient lors de la pénombre. Il faudra veiller également à ce que la diode ne soit pas orienté vers la résistance pour ne pas fausser sa mesure.

 

Pour aller plus loin

Je n’ai présenté ici l’utilisation que pour un seul canal simple, mais il est assez facile d’en utiliser plusieurs à la suite ; le choix du canal se faisant avec le registre ADMUX, il est facile de reconfigurer les bits 3 à 0 en fonction du canal désiré, à la fin de l’interruption (juste avant la ligne ADCSRA |= (1 << ADSC); ).

Il est également possible de ne pas utiliser les interruptions. Dans ce cas, il est nécessaire d’ajouter la ligne suivante une fois la valeur lue, au risque de voir la conversion retourner toujours le même résultat :

 

Supprimer le bruit d’un chargeur QI XCSource

Possédant un Nexus 4, je cherchais un chargeur QI (La technologie QI permet de recharger un appareil compatible, sans contact, grâce à l’induction) pour en finir avec les câbles. Je suis tombé sur cette référence chez Amazon : à 20€, là où les autres sont généralement vendus entre 30 et 80€, j’ai voulu faire le test.

Je ne connais pas la marque XCSource, les avis étaient peu nombreux au moment de commander, mais maintenant, tout le monde s’accorde sur le fait que le bruit émis au moment de la mise en charge est insupportable.

Un bip, ca va deux bips…

D’autant plus, lors de la fin de la charge, celui-ci s’arrête automatiquement, ce qui autorise la décharge de la batterie du téléphone qui va de nouveau enclencher le cycle du chargeur, lequel poussera fièrement son cri strident, malgré l’heure (3h du mat) ou le lieu (au bureau).

J’ai donc souhaité l’ouvrir, et faire taire cette bête une fois pour toute.

Ouvrons la bête !

Un tournevis fin et plat suffit à démonter la coque du chargeur, le long de son arête. Voici ce qu’on y trouve.

XSource - entraille 1

En dessous de la puce centrale, vous pouvez apercevoir un capot où se cache ce maudit buzzer.Une flèche sur celui-ci vous indique où insérer votre tournevis pour l’ouvrir.

XSource - entraille 2

Une fois ouvert, vous trouverez un petit disque de métal. C’est lui qui émet ce bruit insupportable en vibrant. Il suffit donc de l’enlever. Ce qui laissera apparaitre la bobine.XSource - entraille 3

Une fois le disque retiré, vous pouvez remettre le capot, et refermer le chargeur.

Vous pouvez maintenant profiter d’un rechargement sans fil et en silence !