Introduction `a Raspberry Pi
Transcription
Introduction `a Raspberry Pi
Introduction à Raspberry Pi Partie 2 : entrées/sorties Résumé B. Q UOITIN D. H AUWEELE G. H UYSMANS Faculté des Sciences Université de Mons L’objectif de cette séance est de permettre l’interaction entre la platine Raspberry Pi et le monde réel au travers des ports d’entrées/sorties (GPIO). Plusieurs petits circuits électroniques simples seront réalisés sur une platine d’expérimentation (breadboard). De courts programmes en shell, Python et/ou C seront écrits afin de contrôler les entrées/sorties. Comme pour la première séance, la plateforme sera utilisée sans écran. Les composants électroniques utilisés seront issus du SparkFun Inventor’s Kit (https://www.sparkfun.com/products/ retired/12060). Table des matières 1 Entrées/sorties GPIO 1 1.1 1.2 1.3 Structure d’une broche GPIO Connecteur GPIO Précautions et limites d’utilisation 1 1 1 2 Application : contrôle d’une LED et d’un bouton poussoir 2 2.1 2.2 2.3 Commande d’une LED Capture d’un bouton-poussoir Réalisation sur plaquette d’expérimentation 2 2 2 3 Contrôle logiciel 3 3.1 3.2 3.3 3.4 Contrôle en Python (RPi.GPIO) Contrôle via sysFS Contrôle via les registres Interruptions 3 5 6 6 4 Extension du nombre de sorties 7 4.1 4.2 4.3 4.4 4.5 Principe de fonctionnement Registre à décalage 74HC595 Réalisation du circuit Logiciel de commande Application : VU-mètre 7 7 7 8 8 TABLE DES MATIÈRES TABLE DES MATIÈRES 5 Modulation en Largeur d’Impulsion (PWM) 10 5.1 5.2 5.3 5.4 Application : contrôle d’une LED Synthèse PWM logicielle Application : contrôle d’un servo-moteur Synthèse PWM avec DMA 10 10 11 11 6 Produire et lire des valeurs analogiques 12 6.1 6.2 6.3 DAC à réseau R-2R DAC à PWM Lire la valeur d’une LDR 12 12 13 7 8 Afficheur LCD alphanumérique Bus SPI, I2 C et 1-wire 15 17 8.1 8.2 8.3 1-wire Inter-Integrated Circuit (I2 C) Serial Peripheral Interface (SPI) 17 18 20 A Annexe - Connecteurs d’entrées/sorties 22 B Annexe - Contrôle des GPIOs via les registres 23 B.1 Exemple complet en C 24 © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 2 / 25 1 1 Entrées/sorties GPIO I2C1 SDA I2C1 SCL GPCLK0 Le processeur ou plus exactement System-on-Chip (SoC) qui équipe la platine Raspberry Pi porte le doux nom de BCM2835. Il possède un grand nombre de portes d’entrées/sorties. Cellesci permettent au SoC d’interagir avec d’autres périphériques ou circuits électroniques. Ces ports d’entrées/sorties sont souvent appelés GPIO pour General Purpose Input/Output. Le comportement et l’état de ces ports d’entrées/sorties est contrôlable par programme. 1.1 Structure d’une broche GPIO Il peut être utile de comprendre la structure des broches d’I/O afin de les utiliser correctement et de ne pas endommager le SoC. Toutes les broches GPIO ont une structure similaire. Chaque broche peut être utilisée comme une entrée ou une sortie digitale. Certaines broches ont des fonctionnalités plus complexes telles que des interfaces de communication (UART, I2 C, SPI) ou PWM. La Figure 1 illustre la structure d’une broche GPIO unique. La partie extérieure à droite du rectangle, illustre la broche, tandis que l’intérieur du rectangle illustre le contenu du SoC permettant de contrôler cette broche. Un buffer de sortie permet de changer la tension de la broche en fonction d’un bit dans un registre du SoC. Ce buffer de sortie n’est actif que si la broche est configurée comme sortie via un bit d’un registre du SoC (voir l’étiquette direction). Un buffer d’entrée permet de lire l’état de la broche. SPI0 MOSI SPI0 MISO SPI0 SCLK 1.2 Connecteur GPIO Le BCM2835 dispose de 54 lignes GPIO. Certaines sont accessibles au travers de l’un des connecteurs de la platine. Les broches GPIO accessibles dépendent de la version de la platine Raspberry Pi. Par exemple, sur les modèles A et B (rev. 1 et 2) 17 GPIO sont accessibles au travers du connecteur P1. Ce connecteur de 26 broches est illustré à la Table 1. Les connecteurs des autres modèles de Raspberry Pi sont illustrés à la Section A en Annexe. Toutes les broches du connecteur ne sont pas des GPIO du processeur. Les broches marquées 3,3V, 5V ou GND (0V) permettent © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 2 4 6 8 10 12 14 16 18 20 22 24 26 5V 5V GND GPIO 14 GPIO 15 GPIO 18 GND GPIO 23 GPIO 24 GND GPIO 25 GPIO 8 GPIO 7 UART TxD UART RxD PCM CLK SPI0 CE0 SPI0 CE1 d’accéder à l’alimentation électrique de la platine Raspberry Pi. Les autres broches sont reliées aux GPIO du processeur. Elles sont étiquettées avec le numéro de broche GPIO et lorsque c’est possible par une fonction alternative. Par exemple, on peut observer sur la Table 1 que les broches 8 et 10 du connecteur correspondent aux GPIO 14 et 15 respectivement mais que ces broches peuvent également être utilisées pour une liaison série (UART TxD et RxD). Lors de la première séance, ce sont ces broches que nous avons utilisées pour y connecter un adaptateur USB/série. Précautions et limites d’utilisation + Finalement, pour chaque broche, des résistances de pull-up ou pull-down peuvent être optionnellement activées individuellement. Les résistances de pull-up ou pull-down sont utilisées avec les broches configurées comme entrées digitales, en l’absence de connection avec un circuit externe ou lorsque celui-ci est en état de haute impédance. Une résistance de pull-up tire le niveau de l’entrée vers le haut, tandis qu’une résistance de pull-down tire le niveau vers le bas. Intuitivement, ces résistances fixent l’état logique par défaut de la broche. 1 3 5 7 9 11 13 15 17 19 21 23 25 TABLE 1 – Connecteur P1, modèles A et B rev.2 1.3 F IGURE 1 – Structure d’une broche d’I/O. 3,3V GPIO 2 GPIO 3 GPIO 4 GND GPIO 17 GPIO 27 GPIO 22 3,3V GPIO 10 GPIO 9 GPIO 11 GND ENTRÉES/SORTIES GPIO Attention ! L’utilisation des broches d’entrées/sorties nécessite de respecter certaines limitations. Dans le cas contraire, la broche d’entrée/sortie voir le SoC peuvent être irrémédiablement endommagés ! Ni la platine Raspberry Pi ni le SoC BCM2835 ne fournissent de mécanisme de protection contre les surtensions ou les courants trop forts. Les contraintes suivantes sont valables pour le modèle B mais devraient être similaires pour les autres modèles. Une broche d’entrée/sortie fonctionne comme une source de tension en 3,3V. Les broches ne tolèrent pas une tension de 5V ! Le courant qui peut être tiré d’une broche est limité par plusieurs facteurs. Le courant tiré d’une broche provient de l’alimentation USB (5V) puis est abaissé à 3V3 par un régulateur, puis il passe par le SoC. — Limite par broche : 16mA (contrainte du SoC). — Limite totale 3V3 : 50mA (contrainte du régulateur 3V3). Cela donne un courant moyen maximum d’environ 3mA par broche. — Limite totale 5V : ? ? ?mA (contrainte de l’alimentation USB). En résumé, tirer le moins de courant possible d’une broche d’entrées/sorties. 1 / 25 2 2 APPLICATION : CONTRÔLE D’UNE LED ET D’UN BOUTON POUSSOIR Application : contrôle d’une LED et d’un bouton poussoir Cette section décrit comment configurer une broche comme entrée ou sortie digitale et comment en contrôler ou lire l’état. Afin de mettre en pratique ce contrôle, la platine Raspberry Pi sera utilisée d’une part pour contrôler l’allumage d’une LED et d’autre part pour lire l’état d’un bouton poussoir. Les Figures 2 et 3 présentent le schéma des circuits correspondants. 2.1 Commande d’une LED Le circuit de commande de la LED (LD1) est articulé autour de 2 résistances (R1 et R2) et d’un transistor bipolaire NPN (Q1). Le transistor est commandé par la broche GPIO 2. L’usage du transistor permet de limiter le courant tiré de cette broche 1 à environ 0,3mA, ce qui est bien en dessous des limites discutées en Section 1.3. La résistance R1 de 330 ohms limite le courant qui traverse la LED 2 à environ 4mA. F IGURE 3 – Capture d’un bouton poussoir avec une entrée digitale. la paquette d’expérimentation. A l’intérieur de la plaquette, les trous sont mis en contact selon un pattern précis. Les lignes de trous sur le haut et le bas de la plaquette sont connectés ensemble suivant les lignes bleues et rouges. Au centre de la plaquette, les trous sont connectés par groupe de 5, en colonnes. La première étape de la réalisation de ce circuit est l’identification des différents composants et de leurs broches. — Résistances R1 et R2. Les résistances ont 2 broches. Leur sens n’a pas d’importance. La valeur d’une résistance est marquée à l’aide d’un code de couleurs. La résistance R1 F IGURE 2 – Commande d’une LED avec une sortie digitale. de 330Ω a le code orange/orange/brun/or tandis que la résistance R2 de 10kΩ a le code brun/noir/orange/or. — LED LD1. La LED a 2 broches : une anode et une cathode. 2.2 Capture d’un bouton-poussoir Celles-ci sont identifiées respectivement avec les lettres ”a” Le circuit de lecture du bouton poussoir (PB1) est articulé auet ”c” sur le schéma. Sur le composant, la cathode est la tour de 2 résistances (R1 et R2). En fonction de l’état du bouton broche la plus courte. poussoir, la tension présente sur la broche GPIO 3 sera proche de — Transistor Q1. Le transistor a 3 broches : une base, un collec0V (état logique bas) ou proche de 3.3V (état logique haut). La teur et un émetteur. Celles-ci sont identifiées respectivement résistance R2 limite le courant à environ 10mA dans le cas où la avec les lettres ”b”, ”c” et ”e” sur le schéma. La Figure 4 broche est configurée en sortie, ce qui reste dans les limites perdonne le brochage du transistor. mises. La résistance R1 est une résistance dite de pull-up qui détermine l’état par défaut de la broche (état logique haut). Cette résistance est optionnelle 3 2.3 Réalisation sur plaquette d’expérimentation La Figure 5 montre comment le circuit de contrôle de la LED peut être implémenté sur une plaquette d’expérimentation (breadboard). + Utilisation d’une plaquette d’expérimentation. Les composants et câbles sont enfichés dans les trous de 1. Le courant traversant la jonction base-émetteur du transistor est égal à Ibe = (3, 3V − 0, 7V )/10kΩ ≈ 0, 26mA. 2. Les caractéristiques de la LED fournie avec l’Arduino SIK ne sont pas connues avec exactitude. Cependant, il est typique pour une LED de ce type de ne pas supporter un courant supérieur à 20mA. La tension nécessaire aux bornes de la LED pour qu’elle commence à conduire (forward voltage, Vf orward ) est égale à environ 2V pour une LED émettant dans le rouge. Le courant qui traverse la LED peut être calculé simplement comme Ice = (3, 3V − Vf orward )/330Ω ≈ 4mA 3. Certains ports comportent déjà une résistance de pull-up externe. De plus, il est possible d’activer une résistance de pull-up interne pour chaque port individuellement. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS F IGURE 4 – Broches du transistor 2N2222. La seconde étape de la réalisation est le placement des composants sur la plaquette d’expérimentation et leur câblage. 2 / 25 3 CONTRÔLE LOGICIEL F IGURE 5 – Commande d’une LED avec une sortie digitale. 3 Contrôle logiciel par seconde. Une fois le circuit implémenté sur la plaquette d’expérimentation et relié à la platine Raspberry Pi, il est nécessaire de s’intéresser à la partie logicielle du contrôle. Cette section discute de plusieurs moyens de contrôler une entrée ou une sortie digitale et des performances de ceux-ci. Le premier moyen sera l’API RPi.GPIO au travers d’un programme écrit en langage Python. Le second moyen sera l’API SysFS, qui permet de manipuler les entrées/sorties comme des fichiers (virtuels). Finalement, le troisième moyen sera l’accès direct aux registres du SoC contrôlant les entrées/sorties, grâce à un programme écrit en langage C. Cette dernière approche est plus complexe et rébarbative, mais elle permet d’une part d’obtenir les meilleures performances et d’autre part de comprendre ce qu’il y a sous le capot. En effet, tous les autres moyens plus simples de contrôler les entrées/sorties se reposent in fine sur l’utilisation des registres du SoC. A titre de comparaison, la Table 2 donne un aperçu des performances qu’il est possible d’obtenir pour le contrôle d’une sortie digitale avec différentes API. Ce tableau a été obtenu par J. Pihlajamaa 4 . On peut constater que l’accès direct aux registres permet de faire changer l’état d’une sortie digitale plusieurs dizaines de millions de fois par seconde alors qu’avec l’API SysFS, il n’est pas possible d’effectuer plus de quelques milliers de changements 4. http://codeandlife.com/2012/07/03/ benchmarking-raspberry-pi-gpio-speed Language Shell Python Python C C C Perl Library SysFS RPi.GPIO wiringPi /dev/mem + mmap BCM 2835 wiringPi BCM 2835 3.1 Contrôle en Python (RPi.GPIO) Un moyen simple de contrôler les entrées/sorties est d’utiliser le langage de programmation Python au travers de l’API RPi.GPIO. Il s’agit d’une API spécifique à la plateforme Raspberry Pi. + Pour en savoir plus sur le langage Python, il est possible de se référer à un tutoriel en ligne tel que par exemple https://docs.python.org/ 2/tutorial/ L’API RPi.GPIO est très simple. Elle est résumée à la Table 3. La documentation complète peut être obtenue à l’adresse http://sourceforge.net/p/ raspberry-gpio-python/wiki. La fonction setmode doit être appelée avant toute utilisation de l’API. Cette fonction spécifie comment les broches d’entrées/sorties sont identifiées. La première possibilité est d’utiliser les numéros de broches du SoC BCM2835 (argument = BCM). La seconde possibilité consiste à utiliser le numéro de broche du connecteur (argument = BOARD). La première approche nous semble préferable et est donc utilisée dans les exemples qui suivent. La fonction setup permet de configurer une broche en entrée ou en sortie. La fonction cleanup permet de libérer l’utilisation d’une broche et de restaurer sa configuration initiale. La fonction input permet de lire l’état d’une broche (configurée en entrée). Version / tested N.A. / July 3, 2012 0.3.0 / August 1, 2012 github / August 14, 2012 N.A. / July 3 and August 14, 2012 1.3 ? / July 3, 2012 not available / August 14, 2012 1.0 / July 3, 2012 Square wave 3.4 kHz 44 kHz 20 kHz 14-22 MHz 4.7 - 5.1 MHz 6.9 - 7.1 MHz 35 kHz TABLE 2 – Comparaison des différentes API pour le contrôle d’une sortie digitale. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 3 / 25 3.1 Contrôle en Python (RPi.GPIO) 3 CONTRÔLE LOGICIEL Fonction Description setmode(mode) Selectionne le mode d’identification des broches : BCM = selon le SoC BCM2835 BOARD = selon le connecteur de la platine setup(broche,fonction) Configure broche comme entrée (IN) ou comme sortie (OUT) cleanup Libère les ressources utilisées par le module et restaure la configuration des broches. input(broche) Lit l’état d’une broche. output(pin,state) Définit l’état d’une broche. TABLE 3 – Résumé de l’API python RPi.GPIO. La fonction output permet de changer l’état d’une broche (configurée en sortie). La suite de cette section présente plusieurs exemples d’utilisation de l’API. Le premier exemple contrôle une sortie digitale et en change régulièrement l’état. Le module RPi.GPIO est importé à la ligne 1. A la ligne 4 la fonction setmode requiert l’usage de la numérotation des broches selon le SoC. La ligne 5 configure la broche GPIO 3 en sortie. La variable booléenne state déclarée à la ligne 7 contient le prochain état de la sortie. Les lignes 9 à 12 contiennent une boucle effectuant 20 itérations. La ligne 10 change l’état de la broche : le nouvel état est celui trouvé dans la variable state. La ligne 11 inverse l’état de la variable booléenne state. La ligne 12 attend durant 1 seconde. La ligne 14 libère les ressources utilisées par le module RPi.GPIO et restaure l’état initial de la broche GPIO 3. + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Usage de l’indentation. En Python, il n’y a pas de délimiteurs de bloc. C’est l’indentation qui permet de déterminer à quel bloc une instruction appartient. Dans l’exemple ci-dessous, les instructions des lignes 10 à 12 font partie de la boucle for. import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) GPIO.setup(3, GPIO.OUT) state= False for i in range(20): GPIO.output(3, state) state= not(state) time.sleep(1) GPIO.cleanup() pi@rpi:˜$ sudo python gpio-out.py (...broche 3 devrait changer...) pi@rpi:˜$ Le second exemple contrôle une broche d’entrée. La ligne 5 configure la broche GPIO 2 en entrée. Le programme est constitué d’une boucle sans fin qui s’étale sur les lignes 9 à 18. La ligne 10 lit l’état de la broche 2 et le stocke dans la variable state. La ligne 11 attend 100ms 5 . Lignes 12 et 13, si l’état lu (variable state) est identique à l’ancien état (variable oldState), alors l’itération courante est terminée : grâce au mot clé continue, le programme retourne immédiatement au début de la boucle. Sinon, l’ancien état devient le nouvel état à la ligne 14. Aux lignes 15 à 18, une chaı̂ne de caractère est affichée avec print selon l’état de la broche. Ce programme ne se termine pas, en raison de la boucle sans fin. Pour l’arrêter, presser les touches Ctrl-C . 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import RPi.GPIO as GPIO import time GPIO.setmode(GPIO.BCM) GPIO.setup(2, GPIO.IN) oldState= None while True: state= GPIO.input(2) time.sleep(0.1) if state == oldState: continue oldState= state if state: print "button released" else: print "button pressed" GPIO.cleanup() Afin d’exécuter le programme ci-dessus, il faut le placer dans L’exemple ci-dessus a un inconvénient : l’appel à la fonction un fichier texte (utiliser l’éditeur nano par exemple). Ici, nous cleanup de la ligne 20 n’est jamais réalisé. La raison est que le supposons que le programme a été sauvegardé sous le nom programme est terminé abruptement par la pression des touches gpio-out.py. Il peut ensuite être exécuté comme suit. L’usage Ctrl-C . Ceci résulte en l’envoi au programme d’un signal d’inde sudo est requis car le contrôle des GPIO nécessite des pri- terruption. Il est possible de capturer ce signal afin d’interrompre le vilèges d’administrateur. 5. Sans cette attente, le programme va en permanence exécuter la boucle et consommer 100% du CPU. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 4 / 25 3.2 Contrôle via sysFS programme tout en libérant correctement les ressources. L’exemple ci-dessous illustre comment cela peut être fait. Tout le code du programme initial n’est pas repris. La clé de la solution est l’usage de la structure try...except qui permet de capturer l’exception KeyboardInterrupt produite lorsqu’un signal d’interruption est reçu durant l’exécution de la boucle. 1 2 3 4 5 6 7 8 9 try: while True: state= GPIO.input(2) time.sleep(0.1) # ... except KeyboardInterrupt: pass GPIO.cleanup() Pour terminer cette section, notons que RPi.GPIO permet d’activer les résistances pull-up ou pull-down intégrées au SoC. Cette fonction est réalisée par la fonction setup. Par exemple, si le programme est utilisé avec le circuit bouton poussoir de la Figure 3, la résistance R1 de 10kΩ doit être utilisée 6 . Cependant, elle peut être omise si la résistance de pull-up interne de la broche est activée. L’extrait suivant illustre comment configurer la broche 22 comme entrée avec pull-up interne activée. 1 2 GPIO.setup(22, GPIO.IN, \ pull up down=GPIO.PUD UP) 3 sous /sys/class/gpio permettent de placer une broche d’I/O particulière sous le contrôle de SysFS. Par défaut, aucune broche n’est sous le contrôle de SysFS. La Table 4 documente les fichiers de ce groupe. En écrivant le numéro d’une broche d’entrée/sortie dans le fichier export, cette broche passe sous le contrôle de SysFS. Il suffit d’écrire ce numéro dans le fichier unexport pour obtenir l’effet inverse. File Access Description export W Pin number to export unexport W Pin number to remove TABLE 4 – Fichiers SysFS sous /sys/class/gpio Lorsqu’une broche d’entrée/sortie passe sous le contrôle de SysFS, de nouveaux fichiers virtuels apparaissent dans le répertoire /sys/class/gpio/gpion où le n final désigne le numéro de la broche. Le fichier direction permet de configurer une broche comme entrée (in) ou comme sortie (out). Le fichier value est utilisé pour lire ou écrire l’état de la broche. Le fichier edge permet de capturer de manière asynchrone des événements de changement d’état d’une broche en entrée. File 3.2 Contrôle via sysFS SysFS est un autre moyen de contrôler les GPIOs 7 . Celui-ci suit la philosphie UNIX selon laquelle tout peut être contrôlé au travers de fichiers. SysFS est un système générique permettant à un programme utilisateur d’accéder au travers du système de fichiers à de l’état maintenu par le noyau. En clair, des fichiers virtuels apparaissent dans le système de fichiers, typiquement sous le chemin /sys. Comme illustré à la Figure 6, lire ou écrire ces fichiers, au travers d’appels système standards tels que read et write, a pour effet de lire ou écrire de l’état maintenu par le noyau. CONTRÔLE LOGICIEL Access Description direction R/W Direction of pin : in or out edge R/W Event trigger : rising, falling, both or none value R/W State of pin : 0 or 1 TABLE 5 – /sys/class/gpio/gpion Pour illustrer le contrôle des GPIOs via SysFS, nous prenons deux exemples. Le premier exemple contrôle la broche GPIO 3 en sortie. Cet exemple est illustré à la Figure 7. La première étape consiste à mettre GPIO 3 sous le contrôle de SysFS en écrivant la valeur 3 dans le fichier export. La commande echo est utilisée pour afficher une chaı̂ne de caractères. Ici, la sortie de echo est redirigée avec le symbole > vers un fichier. Cela a pour effet d’écrire la chaı̂ne de caractères 3 dans le fichier export. Après cette écriture, de nouveaux fichiers (virtuels) sont créés dans le répertoire /sys/class/gpio afin de contrôler la broche 3. La commande ls est utilisée pour montrer le contenu du répertoire /sys/class/gpio/gpio3 ainsi créé. Ensuite, la valeur out F IGURE 6 – Illustration de l’interface SysFS. est écrite dans le fichier direction afin de configurer la broche 3 en sortie. Finalement, en écrivant 1 ou 0 dans le fichier value, l’état de la broche 3 est changé. Le contrôle des entrées/sorties digitales avec SysFS peut être réalisé au travers de deux groupes de fichiers. Les premiers, situés Le second exemple contrôle la broche GPIO 2 en entrée. Cet exemple est illustré à la Figure 8. La différence avec 6. Sauf dans le cas de GPIO 2 et GPIO 3 (modèles rev.2 ou plus) pour lesl’exemple précedent est que la valeur in est écrite dans le fichier quels des résistances de pull-up externe de 1,8KΩ sont présentes sur la platine 7. voir Ottawa Linux Symposium, 2005 - https://www.kernel.org/ direction. De plus, le fichier value est lu afin d’obtenir l’état pub/linux/kernel/people/mochel/doc/papers/ols-2005/ de la broche. mochel.pdf et https://www.kernel.org/doc/Documentation/ gpio/sysfs.txt © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 5 / 25 3.3 Contrôle via les registres 3 CONTRÔLE LOGICIEL pi@rpi:˜$ echo "3" > /sys/class/gpio/export pi@rpi:˜$ ls /sys/class/gpio/gpio3 active_low direction edge power subsystem uevent value pi@rpi:˜$ echo "out" > /sys/class/gpio/gpio3/direction pi@rpi:˜$ echo "1" > /sys/class/gpio/gpio3/value → LED ON pi@rpi:˜$ echo "0" > /sys/class/gpio/gpio3/value → LED OFF pi@rpi:˜$ echo "3" > /sys/class/gpio/unexport F IGURE 7 – Contrôle de la broche GPIO 3 en sortie via SysFS. pi@rpi:˜$ echo "2" > /sys/class/gpio/export pi@rpi:˜$ ls /sys/class/gpio gpio2 gpiochip0 pi@rpi:˜$ echo "in" > /sys/class/gpio/gpio2/direction pi@rpi:˜$ cat /sys/class/gpio/gpio2/value ... returns 1 (button released) or 0 (button pressed) ... pi@rpi:˜$ echo "2" > /sys/class/gpio/unexport F IGURE 8 – Contrôle de la broche GPIO 2 en entrée via SysFS. 3.3 Contrôle via les registres Les API telles que SysFS et RPi.GPIO permettent le contrôle des entrées/sorties au travers de registres spéciaux du SoC BCM2835. Les performances de SysFS et RPi.GPIO sont limitées (voir Table 2). Ces limites de performances proviennent essentiellement de la lourdeur des appels système. Derrière SysFS et RPi.GPIO se trouvent des manipulations des registres spéciaux du SoC dédiés au contrôle des GPIOs. Il est possible d’écrire un programme qui manipule directement ces registres. Cependant, ces manipulations sont relativement complexes pour quelqu’un qui n’est pas familier avec la programmation système en langage C. Pour cette raison, la présentation de l’accès direct aux registres est placée en Annexe, à la Section B. 3.4 Interruptions Il n’est toujours pratique ou efficace de réagir à des événements externes détectés au travers d’une entrée digitale avec les APIs présentées dans les sections précédentes. Il est nécessaire que le programme scrute sans cesse l’état de l’entrée digitale concernée. Il existe pourtant un moyen matériel de détecter des changements d’état d’une broche d’entrée digitale : une interruption matérielle. Cependant, la gestion des interruptions ne peut être réalisée qu’au sein du noyau linux. Heureusement, SysFS fournit un moyen de reporter vers l’espace utilisateur les interruptions GPIO gérées par le kernel, sous la forme d’événements fichiers (urgent data). Il est ainsi possible qu’un programme utilisateur soit bloqué (endormi) jusqu’à ce qu’un tel événement soit reçu ! Les commandes de la Figure 9 illustrent comment il est possible au travers de SysFS de configurer les interruptions matérielles GPIO et la génération d’événements fichiers. L’exemple suivant illustre comment les événements GPIO peuvent être capturés au travers de l’API RPi.GPIO. L’API s’occupe également de configurer 1 2 3 4 5 6 7 8 9 10 11 import RPi.GPIO as GPIO GPIO.setmode(GPIO.BCM) GPIO.setup(2, GPIO.IN) while True: GPIO.wait_for_edge(2, GPIO.BOTH) if GPIO.input(2): print "button released" else: print "button pressed" pi@rpi:˜$ echo "3" > /sys/class/gpio/export pi@rpi:˜$ echo "in" > /sys/class/gpio/gpio3/direction pi@rpi:˜$ echo "both" > /sys/class/gpio/gpio3/edge pi@rpi:˜$ F IGURE 9 – Activation des interruptions pour la broche GPIO 3. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 6 / 25 4 4 Extension du nombre de sorties Le nombre de broches d’entreés/sorties disponibles sur la platine Raspberry Pi est limité. Pour certains projets, davantage de sorties sont nécessaires. Un registre à décalage est un moyen simple d’augmenter le nombre de sorties. 4.1 Principe de fonctionnement Un registre à décalage est une petite mémoire (de par exemple 8 bits) qui a la particularité de pouvoir être chargée bit à bit. Une seule sortie GPIO est nécessaire pour définir le prochain bit à charger dans le registre. Il est également nécessaire de contrôler quand un bit est chargé. Cela est réalisé par un signal d’horloge qui rythme le chargement des bits dans le registre. Ce signal d’horloge est également généré par une sortie GPIO. Par conséquent, l’interfaçage avec un registre à décalage peut ne nécessiter que 2 broches de sorties. 4.2 Registre à décalage 74HC595 Dans cette section le circuit intégré 74HC595 est utilisé. Il s’agit d’un registre à décalage de 8 bits à entrée série et sortie parallèle. Un tel registre permet de disposer de 8 sorties en utilisant seulement 2 (ou 3) sorties sur la platine Raspberry Pi. De plus, plusieurs 74HC595 peuvent être connectés en chaı̂ne, de façon à augmenter plus encore le nombre de sorties. La Figure 10 donne le schéma fonctionnel du circuit intégré 74HC595. Ce circuit contient un registre à décalage (8-stage shift register), un registre de stockage (8-bit storage register) et un tampon de sortie (3-state outputs). Les entrées et sorties du 74HC595 sont décrites ci-dessous. EXTENSION DU NOMBRE DE SORTIES copie a lieu lors d’un flanc montant de l’entrée STCP. Le registre de stockage permet que de nouvelles données soient chargées dans le registre à décalage sans impact sur les sorties du circuit. — L’état des 8 bits du registre de stockage sont disponibles sur les sorties Q0 à Q7. — L’entrée /OE (Output Enable) détermine si les sorties sont connectées ou isolées 9 du registre de stockage par le tampon de sortie. Lorsque la broche /OE est à l’état bas, les sorties sont conenctées au registre de stockage. 4.3 Réalisation du circuit La Figure 11 illustre l’utilisation d’un registre à décalage pour le contrôle de jusqu’à 8 LEDs. Dans ce circuit, les sorties du registre à décalage 74HC595 sont utilisées pour piloter directement les LEDs. Le circuit n’illustre que 4 LEDs connectées aux sorties Q0 à Q3, mais l’extension à 8 LEDs est similaire. Pour chaque LED, une résistance est placée en série de façon à limiter le courant la traversant 10 . 14 DS 11 SHCP 10 MR 8-STAGE SHIFT REGISTER Q7S 12 STCP 9 F IGURE 11 – Commande de 4 LEDs avec un registre à décalage. 8-BIT STORAGE REGISTER Les données sont envoyées à partir de la platine Raspberry Pi en utilisant les broches GPIO 23 à 25. La broche 23 est connectée 13 OE 3-STATE OUTPUTS à l’entrée DS et fournira séquentiellement la valeur de chaque bit Q0 Q1 Q2 Q3 Q4 Q5 Q6 Q7 à stocker dans le registre à décalage. La broche 24, connectée à 15 1 2 3 4 5 6 7 mna554 l’entrée SHCP, fournira après chaque bit une impulsion, de façon à stocker ce bit dans le registre (en en décalant le contenu). La broche F IGURE 10 – Schéma fonctionnel (source : 74HC595 datasheet, 25, connectée à l’entrée STCP, fournira après chaque 8 bits une impulsion de façon à stocker le contenu du registre à décalage dans le NXP) registre de sortie (storage register). Note : d’autres broches GPIO peuvent être utilisées si nécessaire. Il suffira dans ce cas d’adapter — L’entrée DS détermine la valeur d’un nouveau bit à stocker le programme de pilotage. dans le registre à décalage. L’entrée /MR de remise à zéro du registre à décalage est — L’entrée SHCP (SHift-register control input) décale tous les connectée à 3,3V via la résistance de pull-up R1. L’entrée /OE bits du registre à décalage d’un bit. Ainsi, le bit 7 prend la d’activation des sorties est connectée à la masse (GND) via la valeur du bit 7, le bit 6 prend la valeur du bit 5, etc. Le bit résistance de pull-down R2. 1 prend la valeur donnée par l’entrée DS. Ce décalage a lieu Ce circuit peut facilement être réalisé sur une plaquette lorsque l’état de SHCP passe de bas à haut (flanc montant). d’expérimentation. Il est nécessaire pour cela de disposer du — L’entrée /MR (Master Reset) permet de remettre le contenu schéma de brochage du registre à décalage 74HC595. Celui-ci est du registre à décalage à zéro. Cette remise à zéro a lieu exposé à la Figure 12. lorsque l’entrée /MR est à l’état bas 8 . — L’entrée STCP (STorage control input.) permet de copier les 9. On parle d’état haute impédance. 10. Le courant par broche Qi ne doit pas dépasser 35 mA et le courant total ne bits du registre à décalage dans le registre de stockage. Cette 8. La barre qui précède le nom de la broche /MR indique qu’elle est active au niveau bas. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS doit pas dépasser 70 mA. Une résistance de 330 Ωhm devrait convenir, limitant le courant par broche à environ 4 mA et donc le courant total à 32 mA si toutes les LEDs sont allumées simultanément 7 / 25 4.4 Logiciel de commande 4 74HC595 74HCT595 Q1 1 16 VCC Q2 2 15 Q0 Q3 3 14 DS Q4 4 13 OE Q5 5 12 STCP Q6 6 11 SHCP Q7 7 10 MR GND 8 9 Q7S 001aao241 F IGURE 12 – Schéma de brochage (source : 74HC595 datasheet, NXP) 4.4 Logiciel de commande Pour écrire dans le registre à décalage, il faut pouvoir commander les broches DS, SHCP et STCP à partir du Raspberry Pi. L’écriture d’un octet dans le registre à décalage est effectué bit à bit, en commençant par le bit de poids fort.“ Afin d’illustrer la procédure à suivre, la Figure 13 présente une trace des signaux envoyés aux entrées DS, SHCP et STCP lors de l’écriture d’un octet de valeur 0x35 dans le registre à décalage. La ligne intitulée D1 correspond à l’entrée SHCP. On y observe 8 impulsions, une pour chaque bit de l’octet chargé. La ligne intitulée D0 correspond à l’entrée DS. On y observe les 8 bits 00110101 de l’octet 0x35. La lidgen intitulée D2 correspond à l’entrée STCP. On y observe une seule impulsion après le transfert des 8 bits. Le programme suivant illustre comment écrire en octet en utilisant Python et l’API RPi.GPIO. La fonction shift init initialise les GPIOs : 3 sorties (23, 24 et 25) sont utilisées. Les sorties correspondant aux d’horloge SHCP et STCP sont initialisée à 0. La fonction shift pulse génère une impulsion sur une sortie donnée. Cette fonction est utilisé pour générer un flanc montant soit sur SHCP soit sur STCP. La fonction shift send byte effectue l’envoi des 8 bits d’un octet vers le registre à décalage. Les bits sont envoyés séquentiellement en commençant par le bit de poids fort (son poids est égal à 0x80). Après chaque bit, une EXTENSION DU NOMBRE DE SORTIES impulsion est générée sur SHCP. Après l’envoi des 8 bits, une impulsion est générée sur STCP de façon à charger l’octet dans le registre de stockage. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 4.5 import RPi.GPIO as GPIO import sys, time PIN_DS= 23 PIN_SHCP= 24 PIN_STCP= 25 def shift_init(): GPIO.setmode(GPIO.BCM) GPIO.setup(PIN_DS, GPIO.OUT) GPIO.setup(PIN_SHCP, GPIO.OUT) GPIO.setup(PIN_STCP, GPIO.OUT) GPIO.output(PIN_SHCP, False) GPIO.output(PIN_STCP, False) def shift_pulse(pin): time.sleep(0.001) GPIO.output(pin, True) time.sleep(0.001) GPIO.output(pin, False) def shift_send_byte(b): for i in range(8): GPIO.output(PIN_DS, b & 0x80) shift_pulse(PIN_SHCP) b= b << 1 GPIO.output(PIN_DS, False) shift_pulse(PIN_STCP) shift_init() count= 0 try: while True: shift_send_byte(count); time.sleep(1) count= (count + 1) & 0xFF except KeyboardInterrupt: pass GPIO.cleanup() Application : VU-mètre Une application possible de l’extension des sorties avec un registre à décalage est la réalisation d’un VU-mètre. Le nombre de LEDs allumées doit refléter le niveau sonore d’une application. Afin de réaliser une telle application, il est nécessaire de capturer le signal sonore joué par une application. Pour jouer un son, il est possible d’utiliser le programme aplay comme illustré ci-dessous. pi@rpi:˜$ aplay SIREN.WAV F IGURE 13 – Signaux des entrées DS (D0), SHCP (D1) et STCP (D2) lors de l’écriture d’un octet dans le registre à décalage. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS Il est possible de capturer le son d’une application en passant par un périphérique audio virtuel. Le listing suivant montre le contenu du fichier ˜/.asoundrc dans lequel un périphérique audio virtuel nommé capture est créé. Celui-ciduplique les échantillons 8 / 25 4.5 Application : VU-mètre 4 EXTENSION DU NOMBRE DE SORTIES de son produit par l’application et les envoie vers un programme python nommé snd.py. pcm.capture { type plug slave.pcm { type file slave.pcm "hw:0,0" file "|python -u snd.py" format "raw" } } Le programme python montré ci-dessous récupère des échantillons à partir de l’entrée standard (stdin) en détermine le maximum (variable maximum) et affiche une barre verticale composée de caractères # qui reflète le volume sonore mesuré. Le maximum est calculé sur chaque 32 échantillons. Le programme fait l’hypothèse que les échantillons sonores sont encodés sur 16 bits en little-endian (octet de poids fort reçu en premier). Les échantillons sont signés (complément à 2). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import os, sys print "Starting python script" while True: data= sys.stdin.read(64) if len(data) == 0: break samples= [] for i in range(len(data)/2): int16= ord(data[2*i]) + (ord(data[2*i+1]) << 8) if int16 > 32767: int16= -(65536-int16) samples.append(int16) maximum= max(samples) value= maximum print "\r%s%s" % \ ("#"*((20*value)/32768), " "*20), sys.stdout.flush() print print "done." pi@rpi:˜$ aplay -D capture SIREN.WAV Playing WAVE ’SIREN.WAV’ : Signed 16 bit Little Endian, Rate 22050 Hz, Mono Starting python script ######### done. pi@rpi:˜$ © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 9 / 25 5 5 Modulation en Largeur d’Impulsion (PWM) Un moyen classique pour contrôler de façon numérique l’intensité d’une LED ou la vitesse d’un moteur est la modulation en largeur d’impulsion ou encore PWM (Pulse Width Modulation). Cette technique peut aussi être utilisée pour générer un signal audio (voix, musique) ou encore pour contrôler le positionnement de servo-moteurs. Le principe de la modulation en largeur d’impulsion est simple : on génère à partir d’une sortie digitale un signal de fréquence constante, mais de rapport cyclique variable. Le signal est tantôt haut, tantôt bas et le rapport cyclique désigne le rapport entre la durée du signal haut et la période du signal. Le rapport cyclique peut varier entre 0 et 100%. Par exemple, un rapport cyclique de 25% correspond à un signal dans laquelle la partie haute dure 25% de la période. La Figure 14 illustre ce concept avec trois signaux de même fréquence mais 3 rapports cycliques différents. MODULATION EN LARGEUR D’IMPULSION (PWM) cas, les signaux PWM permettent de contrôler l’intensité de chaque couleur séparément. F IGURE 15 – Commande d’une LED par PWM. 5.2 Synthèse PWM logicielle Cette section montre comment générer de façon logicielle un signal PWM via l’API python RPi.GPIO 11 . L’API est résumée à la Table 6. Pour générer un signal PWM à partir d’une broche, il est nécessaire d’associer cette broche à un générateur qui se charge d’en changer automatiquement l’état. Ce générateur est créé avec l’appel 12 à PWM. Cet appel prend deux arguments : le numéro de la broche et la fréquence du signal. Les méthodes start et stop permettent respectivement de démarrer et arrêter la génération du signal. Les méthodes ChangeDutyCycle et ChangeFrequency permettent respectivement de changer le F IGURE 14 – Signaux générés par PWM. rapport cyclique et la fréquence du générateur. (source : electronics.stackexchange.com) Le programme donné en exemple ci-dessous peut être utilisé pour contrôler l’intensité d’une LED connectée à la broche GPIO Afin de générer un signal PWM, plusieurs approches sont pos- 3. Un signal PWM de fréquence égale à 1kHz est généré. Le prosibles. La plupart du temps, ce signal est généré sur un processeur gramme fait varier toutes les 100 ms le rapport cyclique de ce siou microcontrôleur qui dispose d’un périphérique PWM. Dans ce gnal. cas, le signal PWM est généré par le matériel. C’est par exemple le 1 import RPi.GPIO as GPIO cas avec le microcontrôleur ATmega328 au coeur de la plateforme 2 import time Arduino Uno. 3 Le SoC du Raspberry Pi possède aussi un périphérique PWM, 4 GPIO.setmode(GPIO.BCM) cependant il est déjà utilisé par la sortie audio. Pour cette raison, 5 GPIO.setup(3, GPIO.OUT) il est préferable de générer le signal de façon logicielle tel qu’ex6 p= GPIO.PWM(3, 1000) pliqué en Section 5.2. Si la résolution du signal PWM doit être plus 7 p.start(10) grande, il est également possible de s’appuyer sur le contrôleur 8 DMA du SoC, tel qu’expliqué en Section 5.4. 9 dc= 10 10 delta_dc= 10 11 5.1 Application : contrôle d’une LED 12 while True: Cette section montre comment contrôler l’intensité d’une LED. 13 dc+= delta_dc Le signal PWM fait donc clignoter la LED à une fréquence suffi14 if (dc <= 0) or (dc >= 100): samment élevée que pour ne pas être perçue par l’oeil (p.ex. à une 15 delta_dc= -delta_dc fréquence de 1000 Hertz). Moins le rapport cyclique du signal est 16 time.sleep(0.1) grand, moins la LED est éclairée, et inversément. Ceci peut être ex17 p.ChangeDutyCycle(dc) pliqué aisément sur base de la Figure 14 où l’on constate que plus le rapport cyclique est faible (durée de l’impulsion courte), moins la tension moyenne (Vaverage ) est élevée. 11. Voir http://sourceforge.net/p/raspberry-gpio-python/ Le circuit montré à la Figure 15 permet de contrôler une LED via wiki/PWM/ PWM. Il s’agit du même circuit qu’utilisé pour tester la commande 12. Note : l’appel GPIO.PWM crée une instance de la classe GPIO.PWM. Il d’une sortie digitale. Une variante de ce circuit pourrait être uti- s’agit d’un exemple de programmation orienté-objet en python. L’instance de PWM lisée pour contrôler une LED à plusieurs couleurs (RGB). Dans ce est associée à un thread qui se charge de changer l’état de la broche. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 10 / 25 5.3 Application : contrôle d’un servo-moteur 5 MODULATION EN LARGEUR D’IMPULSION (PWM) Fonction Description PWM(broche, f ) p.start(rc) p.stop() p.ChangeDutyCycle(rc) p.ChangeFrequency(f ) Crée un générateur de signal PWM de fréquence f associé à la broche. Démarre le générateur PWM p avec un rapport cyclique rc. Arrête le générateur PWM p. Change le rapport cyclique du générateur PWM p à la valeur rc. Change la fréquence du générateur PWM p à la valeur f . TABLE 6 – API permettant de générer un signal PWM avec RPi.GPIO. 5.3 Application : contrôle d’un servo-moteur Une autre application de la commande PWM est le contrôle de position d’un servo-moteur. Un tel moteur contient un circuit électronique interne qui détermine l’angle de rotation du moteur à partir du signal PWM de commande. L’angle du moteur est une fonction linéaire de la durée de l’impulsion haute du signal PWM. La plupart des servo-moteurs imposent que la période du signal PWM soit d’environ 20ms et que la durée d’impulsion soit comprise entre 1 et 2ms. Ainsi, si l’angle peut varier entre αmin (p.ex. 0°) et αmax (180°), l’angle actuel est obtenu par ∗ (αmax − αmin ) où pulse est la durée de l’imαmin + pulse−1ms 1ms pulsion haute du signal PWM. Le schéma de la Figure 16 illustre comment interconnecter un servo-moteur à la platine Raspberry Pi. Le moteur nécessitant un courant important, il est fortement déconseillé de l’alimenter à partir du Raspberry Pi. Sur le schéma, une alimentation externe est utilisée. Il peut s’agir de piles mises en série, d’une alimentation de laboratoire, etc. F IGURE 16 – Branchement d’un servo-moteur au Raspberry Pi et à une alimentation externe. signal PWM dans un buffer en mémoire. Le contenu de ce buffer est alors transféré par DMA vers les registres qui contrôlent une broche d’entrée/sortie. Cette approche permet de générer un signal PWM beaucoup plus stable et précis que la technique logicielle de la Section 5.2. Le module RPIO n’est pas installé par défaut. Pour l’installer, exécuter les commandes suivantes. pi@rpi:˜$ sudo apt-get install python-setuptools pi@rpi:˜$ sudo easy install -U RPIO L’exemple suivant montre comment l’API peut être utilisée pour commander un servo-moteur. Les durées d’impulsion du tableau sont envoyées à des intervalles de 2 secondes. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from RPIO import PWM import time servo = PWM.Servo() PIN_SERVO = 17 POS = [1200, 1500, 1800] try: i= 0 while True: servo.set_servo(PIN_SERVO, POS[i]) time.sleep(2) i= (i + 1) % len(POS) finally: servo.stop_servo(PIN_SERVO 5.4 Synthèse PWM avec DMA L’API PWM de RPi.GPIO n’est pas très adaptée à la commande de servo-moteur. Le signal digital étant généré de façon logicielle, il peut-être perturbé notamment par l’exécution simultanée d’autres programmes. Il existe une seconde méthode pour générer un signal PWM sur la plateforme Raspberry Pi. Cette méthode repose sur l’utilsiation du DMA (Direct Memory Access). Le SoC BCM2835 contient un contrôleur DMA (matériel) qui peut se charger d’effectuer des copies des données entre différentes zones mémoire sans que le processeur n’intervienne. Le module python RPIO propose une API 13 permettant d’effectuer la génération d’un signal PWM en utilisant le contrôleur DMA. L’idée est de préparer les différents états du 13. https://pythonhosted.org/RPIO/pwm_py.html © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 11 / 25 6 6 Produire et lire des valeurs analogiques La platine Raspberry Pi n’intègre ni convertisseur digital analogique (DAC) ni convertisseur analogique digital (ADC). Il est cependant facile d’en ajouter. La première solution possible est d’utiliser des convertisseurs tout faits. Les sous-sections suivantes présentent plusieurs approches alternatives. 6.1 DAC à réseau R-2R Le circuit de la Figure 17 est un DAC basé sur un réseau appelé R-2R car composé de résistances de valeur R et 2 × R. Le circuit présenté est un DAC à 4 bits permettant de produire une tension allant de 0 à 3,1V en 15 pas de 200mV. Le choix de la tension de sortie (Output) est déterminé par le Raspberry Pi via les 4 sorties x × GPIO 22 à 25. La tension en sortie est donnée par Vout = 16 3, 3V où x est le nombre binaire correspondant aux bits GPIO 22 à 25. 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 PRODUIRE ET LIRE DES VALEURS ANALOGIQUES GPIO.setup(PIN_B2, GPIO.OUT) GPIO.setup(PIN_B3, GPIO.OUT) x= 0 try: while True: GPIO.output(PIN_B0, GPIO.output(PIN_B1, GPIO.output(PIN_B2, GPIO.output(PIN_B3, x= (x + 1) % 16 time.sleep(2) except KeyboardInterrupt: pass x x x x & & & & 1) 2) 4) 8) GPIO.cleanup() La Figure 18 montre l’évolution de la tension en sortie du réseau R-2R. Le signal est celui produit par le programme ci-dessus : la valeur monte progressivement, par pas de 200 mV jusqu’à un maximum, puis elle recommence à 0 V. La Figure montre également les valeurs des entrées du réseau R-2R, i.e. les sorties GPIOs 22 (D0) à 25 (D3) F IGURE 17 – Convertisseur DAC basé sur un réseau R-2R. L’amplificateur opérationnel LM358 du circuit n’est pas strictement nécessaire. Il agit comme tampon entre la sortie et le réseau R-2R (amplificateur à gain unitaire). La tension d’alimentation 14 F IGURE 18 – Signaux des bits d’entrées et de la sortie analogique du réseau R-2R. du LM358 est 5V. Le programme ci-dessous pilote le réseau R-2R. Les GPIO 22 à 25 sont configurées en sortie. La valeur de sortie (variable x) varie de 0 à 15 à raison d’un incrément de 1 par itération. Ce comportement est répété en boucle. Les valeurs des GPIO sont les valeurs 6.2 DAC à PWM des bits représentant x. La circuit de la Figure 19 illustre comment il est possible de générer un signal analogique à partir d’un signal PWM (cf. Sec1 import RPi.GPIO as GPIO tion 5). Le principe consiste à garder la valeur moyenne du signal 2 import time PWM tel qu’illustré par Vaverage à la Figure 14). Le circuit arti3 culé autour de la résistance R1 et du condensateur C1 constitue 4 PIN_B0 = 22 un filtre passe-bas, laissant passer les variations lentes du signal et 5 PIN_B1 = 23 atténuant les variations rapides du signal PWM. 6 PIN_B2 = 24 Le programme ci-dessous permet de générer un signal sinusoı̈dal 7 PIN_B3 = 25 de fréquence 10 Hz et dont l’amplitude de crête est d’environ 8 9 GPIO.setmode(GPIO.BCM) 1,65V. Pour générer la sinusoı̂de, un signal PWM de fréquence 10 1000 Hz est utilisé. Le rapport cyclique de ce signal PWM varie de 11 GPIO.setup(PIN_B0, GPIO.OUT) 0 % à 100% en fonction de l’amplitude de la sinusoı̈de à générer. 12 GPIO.setup(PIN_B1, GPIO.OUT) Pour chaque cycle de la sinusoı̈de, 30 échantillons sont utilisés. 14. Le LM358 est alimenté en 5V plutôt qu’en 3,3V car il n’est pas capable de produire une tension de sortie suffisamment proche de sa tension d’alimentation. S’il était alimenté en 3,3V, il pourrait produire une tension de sortie d’au plus 2V. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 1 2 import RPi.GPIO as GPIO import time 12 / 25 6.3 Lire la valeur d’une LDR 6 PRODUIRE ET LIRE DES VALEURS ANALOGIQUES F IGURE 19 – Convertisseur DAC basé sur un signal PWM et un filtre passe-bas. 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import math F IGURE 20 – Signal en sortie du filtre RC. PWM_FREQ = 1000 SIG_FREQ = 10 NUM_SAMPLES = 30 PIN_PWM = 23 GPIO.setmode(GPIO.BCM) sateur est vide. Le condensateur se charge alors via LDR1. A un certain moment, la tension présente aux bornes du condensateur va dépasser le seuil de tension considéré comme un niveau logique haut, ce qui pourra être détecté par programme. GPIO.setup(PIN_PWM, GPIO.OUT) p= GPIO.PWM(PIN_PWM, PWM_FREQ) p.start(50) t= 0.0 try: while True: x= 0.5 * math.sin(2 * math.pi \ * t * SIG_FREQ) + 0.5 p.ChangeDutyCycle(100.0 * x) sample_duration= 1.0 / (SIG_FREQ \ * NUM_SAMPLES) time.sleep(sample_duration) t+= sample_duration finally: GPIO.cleanup() F IGURE 21 – Lecture d’une photorésistance. La Figure 22 montre une trace de la tension présente aux bornes du condensateur C1. On observe que cette tension augmente progressivement de 0V jusqu’à un peu plus de 2,5 V. A ce moment, le La Figure 20 montre l’évolution de la tension en sortie du filtre programme détecte un niveau haut en entrée de la broche GPIO 4. passe-bas. Les valeurs de R1 et C1 utilisées sont respectivement Dans cette trace, le temps de chargement est environ 8 ms. Le pro1kΩ et 4, 7µF , ce qui donne une fréquence de coupure d’environ gramme décharge ensuite le condensateur. Le temps de décharge 33 Hz. La sinusoı̈de est clairement reconnaissable. On observe ceest très court, mais le programme attend 10 ms. Après quoi, le propendant une légère variation de la tension en dents de scie. Ceci est cessus de charge reprend... DS dû au fonctionnement imparfait du filtre passe-bas utilisé. 6.3 Lire la valeur d’une LDR Bien que la platine Raspberry Pi ne possède pas de convertisseur analogique / digital (ADC), cette section présente un circuit très simple permettant de détecter des variations de luminosité au travers d’un photoresistance (light-dependent resistor – LDR). La Figure 21 présente le circuit utilisé. L’astuce consiste à mesurer le temps nécessaire au chargement du condensateur C1 au travers de la photorésistance LDR1. La photorésistance utilisée a une résistance variant entre < 1kΩ (éclairée) et > 1M Ω (dans l’obscurité). Le principe du circuit est le suivant. La broche GPIO 4 va être configurée d’abord en sortie et mise à un niveau bas, de façon à vider le condensateur C1 au travers de la résistance R1 et la résistance interne du port 4. Ensuite, la broche est re-configurée en entrée. Le niveau logique est actuellement bas car le conden© 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS F IGURE 22 – Evolution de la tension aux bornes du condensateur. 13 / 25 6.3 Lire la valeur d’une LDR Le programme ci-dessous configure alternativement la broche 4 en entrée puis en sortie. Lorsque la broche est configurée en sortie et mise à un niveau bas, le condensateur se vide en un temps assez court. La broche est alors re-configurée en entrée le programme attend en boucle qu’elle passe à un niveau haut. Le temps pour passer du niveau bas au niveau haut dépend de la résistance de la LDR et donc de l’éclairement à laquelle elle est soumise. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 6 PRODUIRE ET LIRE DES VALEURS ANALOGIQUES détectant la direction de déplacement d’une main avec une paire de LDR. En fonction de la direction du mouvement, diverses actions peuvent être prises telles que l’augmentation/diminution du volume, le passage à la plage suivante/précédente, etc. import RPi.GPIO as GPIO import time PIN_LDR = 4 GPIO.setmode(GPIO.BCM) try: while True: GPIO.setup(PIN_LDR, GPIO.OUT) GPIO.output(PIN_LDR, 0) time.sleep(0.01) GPIO.setup(PIN_LDR, GPIO.IN) start= time.time() while not GPIO.input(PIN_LDR): time.sleep(0.0001) end= time.time() print "%f" % (end-start) finally: GPIO.cleanup() La Figure 23 montre un suite d’échantillons mesurés par le programme ci-dessus. L’axe des abscisses représente le numéro de l’échantillon tandis que l’axe des ordonnées représente le temps mesuré par le programme. Le temps est légèrement inférieur à 2 ms lorsque la LDR est éclairée puis passe à plus de 10 ms lorsqu’une main passe devant la LDR (échantillons 190 à 230). F IGURE 23 – Temps de chargement du condensateur C1 à travers la LDR. + A l’aide de ce circuit, il est possible de contrôler le Raspberry Pi via une interface sans contact. Une première application pourrait être l’équivalent d’un Theremin, un instrument de musique dont le son peut être modifié en en approchant ou éloignant la main. Une autre application pourrait être une interface © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 14 / 25 7 7 AFFICHEUR LCD ALPHANUMÉRIQUE Afficheur LCD alphanumérique Les LEDs sont très pratiques pour afficher un état binaire. Cependant, leur capacité à transmettre une information complexe à un utilisateur est limitée. Cette section explore l’utilisation d’un afficheur LCD alphanumérique et sa connexion à la platine Raspberry Pi. L’afficheur utilisé est basé sur un contrôleur très répandu : le HD44780 ou un modèle compatible. La communication avec ce contrôleur passe par une interface parallèle qui nécessite 6 sorties du Raspberry Pi. La Table 7 reprend les différentes broches d’un afficheur LCD alphanumérique. En fonction des modèles, ils possèdent 14 ou 16 broches. Les broches 1 et 2 servent à alimenter l’afficheur et son contrôleur. La broche 3 permet de régler le contraste. La broche 4 indique si l’on transmet une commande ou une donnée vers l’afficheur. La broche 5 sert à indiquer si l’on écrit vers l’afficheur ou si on lit à partir de l’afficheur. Dans notre cas, nous ne ferons que des écritures. La broche 6 permet d’indiquer qu’une donnée doit être prise en compte par l’afficheur. Les données sont transmises sur 4 bits via les broches 11 à 14 (bits 4 à 7) et sont prises en compte lors d’un flanc descendant de la broche 6. Les broches 7 à 10 (bits 0 à 3) sont inutilisées. Les broches 15 et 16 sont présentes sur les afficheurs dotés d’un rétro-éclairage (backlight) et servent à alimenter celui-ci. Afficheur LCD (broche et fonction) Raspberry Pi (broche) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 GND VCC Contraste Command (0) / Data (1) Write (0) / Read (1) Enable (flanc descendant) Bit 0 Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7 GND 5V (voir texte) GPIO 17 GND GPIO 27 — — — — GPIO 22 GPIO 23 GPIO 24 GPIO 25 15 16 Anode backlight Cathode backlight 5V (option) GND (option) TABLE 7 – Brochage d’un afficheur LCD alphanumérique et connection à la platine Raspberry Pi. La Figure 24 présente le circuit interconnectant la platine Raspberry Pi et un afficheur LCD. Les connexions sont établies conformément à la Table 7. Une résistance variable configurée en pont diviseur est connectée à la broche 3 de l’afficheur afin d’en régler le constraste. Les broches 15 et 16, lorsqu’elles existent, peuvent optionnellement être reliées à 5V et GND respectivement afin d’alimenter le rétro-éclairage. Le programme ci-dessous initialise les entrées-sorties du Raspberry Pi afin de permettre la communication avec le contrôleur de l’afficheur. L’afficheur est ensuite initialisé (fonction lcd init en lui envoyant une série de commandes bien déterminées. La © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS F IGURE 24 – Interconnexion de la platine Raspberry Pi et d’un afficheur LCD. description de ces commandes sort du cadre de ce document. L’étudiant curieux pourra en trouver le détail dans la fiche technique du contrôleur HD44780. C’est lors de cette initialisation que le type d’afficheur auquel le contrôleur est connectée doit être renseigné. Il faut spécifier si l’afficheur possède 1 ou 2 lignes ainsi que la taille des caractères (5x8 ou 5x10 pixels). Pour des raisons de facilité, les fonctions lcd cmd et lcd data afin d’envoyer des commandes ou des données au contrôleur. Le programme affiche en boucle des chaı̂nes de caractères définies dans le tableau messages. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import RPi.GPIO as GPIO import time import sys PIN_DATA = 17 PIN_ENABLE = 27 PIN_D4 = 22 PIN_D5 = 23 PIN_D6 = 24 PIN_D7 = 25 NUM_ROWS = 4 NUM_COLS = 20 HD44780_CLEAR_DISPLAY HD44780_RETURN_HOME HD44780_ENTRY_MODE_SET HD44780_DISPLAY_CTRL HD44780_FUNCTION_SET HD44780_SET_DDRAM_ADDR = = = = = = 0x01 0x02 0x04 0x08 0x20 0x80 HD44780_ENTRY_MODE_SET_INC = 0x02 HD44780_ENTRY_MODE_SET_SHIFT = 0x01 HD44780_DISPLAY_CTRL_DISP_ON = 0x04 HD44780_DISPLAY_CTRL_CURS_ON = 0x02 HD44780_DISPLAY_CTRL_BLINK_ON = 0x01 HD44780_FUNCTION_SET_ROWS_2 = 0x08 HD44780_FUNCTION_SET_FONT_5X10 = 0x04 HD44780_LINE_DDRAM_ADDR= \ [0, 0x40, 0x14, 0x54] 15 / 25 7 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 GPIO.setmode(GPIO.BCM) GPIO.setup(PIN_DATA, GPIO.OUT) GPIO.setup(PIN_ENABLE, GPIO.OUT) GPIO.setup(PIN_D4, GPIO.OUT) GPIO.setup(PIN_D5, GPIO.OUT) GPIO.setup(PIN_D6, GPIO.OUT) GPIO.setup(PIN_D7, GPIO.OUT) GPIO.output(PIN_ENABLE, 1) def lcd_write4(n): GPIO.output(PIN_D4, n & GPIO.output(PIN_D5, n & GPIO.output(PIN_D6, n & GPIO.output(PIN_D7, n & time.sleep(0.001) GPIO.output(PIN_ENABLE, time.sleep(0.001) GPIO.output(PIN_ENABLE, 1) 2) 4) 8) 0) 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 AFFICHEUR LCD ALPHANUMÉRIQUE HD44780_LINE_DDRAM_ADDR[y] + x) lcd_init() MESSAGES = ["Hello", "World", "of text"] try: i= 0 while True: msg= MESSAGES[i % len(MESSAGES)] x= (NUM_COLS - len(msg)) / 2 y= i % NUM_ROWS lcd_clear() lcd_goto(x, y) lcd_str(msg) time.sleep(2) i+=1 finally: GPIO.cleanup() 1) def lcd_write(b): lcd_write4(b >> 4) lcd_write4(b & 0xFF) def lcd_data(b): GPIO.output(PIN_DATA, 1) lcd_write(b) def lcd_cmd(b): GPIO.output(PIN_DATA, 0) lcd_write(b) def lcd_init(): GPIO.output(PIN_DATA, 0) time.sleep(0.1) lcd_write4(0x03) time.sleep(0.01) lcd_write4(0x03) time.sleep(0.001) lcd_write4(0x03) lcd_write4(0x02) if NUM_ROWS > 1: lcd_write(HD44780_FUNCTION_SET | HD44780_FUNCTION_SET_ROWS_2) else: lcd_write(HD44780_FUNCTION_SET) lcd_write(HD44780_DISPLAY_CTRL | HD44780_DISPLAY_CTRL_DISP_ON) lcd_write(HD44780_CLEAR_DISPLAY) lcd_write(HD44780_ENTRY_MODE_SET | HD44780_ENTRY_MODE_SET_INC) def lcd_str(s): for c in s: lcd_data(ord(c)) def lcd_clear(): lcd_cmd(HD44780_CLEAR_DISPLAY) def lcd_goto(x, y): x= x % NUM_COLS y= y % NUM_ROWS lcd_cmd(HD44780_SET_DDRAM_ADDR | © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 16 / 25 8 Bus SPI, I2 C et 1-wire 8.1 Le SoC BCM2835 permet la connexion de circuits intégrés périphériques sur des bus série tels que SPI (Serial Peripheral Interface), I2 C (Inter Integrated Circuit) et 1-wire. Cette section explore l’utilisation de ces bus. La Table 8 fournit une première comparaison des types de bus. Bus Vitesse max. Signaux Adressage SPI I2 C 1-wire ≤ 125 Mbps 400 kbps 16.3kbps (142kbps) 4 2 1 signal séparé (/CS) dans le protocole dans le protocole TABLE 8 – Comparaison des bus SPI, I2 C et 1-wire. SPI est destiné à des transferts à relativement haute vitesse entre le SoC et un périphérique. Quatre signaux sont requis pour effectuer ces transferts : MOSI (Master Out Slave In) pour l’envoi de données à partir du SoC, MOSI (Master In Slave Out) pour la réception, CLK (CLocK) pour rythmer le transfert de chaque bit et /CS (Chip Select) pour sélectionner le périphérique auquel le transfert s’adresse. Si plusieurs périphériques sont présents sur un bus SPI, un signal /CS séparé est nécessaire pour chaque périphérique. I2 C est un bus plus complexe, permettant des débits plus faibles. Il n’utilise que deux signaux : SDA (Serial DAta pour l’échange bidirectionnel de données et SCK (Serial ClocK) pour rythmer ces échanges. Le protocole de communication utilisé inclut deux fonctionnalités importantes. La première est l’utilisation de conditions d’acquittement (ACK) permettant de contrôler le bon échange de données. La seconde est l’envoi de l’adresse du destinataire via le bus. Cela permet d’ajouter facilement des périphériques additionnels sur le même bus, sans utiliser de signaux de contrôle propres à chaque périphérique (comme dans le cas de SPI). 1-wire est un bus similaire à I2 C en terme de complexité et de débit. La différence la plus importante est qu’il ne nécessite qu’un signal DQ qui transport les données de manière bidirectionnelle et ne nécessite pas la transmission d’un signal d’horloge séparé. Certains périphériques 1-wire peuvent aussi être alimentés via le signal DQ. On parle dans ce cas d’ alimentation parasite . Les trois bus sont supportés de façon matérielle par le SoC du Raspberry Pi. Ils nécessitent également un support logiciel dans le noyau linux. Ce support est placé dans des modules. Ceux-ci peuvent être chargés au démarrage s’ils sont spécifiés dans le fichier /etc/modules. Ils peuvent également être chargés durant le fonctionnement du noyau en utilisant l’utilitaire modprobe. + Les noyaux linux récents (> 3.18) utilisent un Device Tree pour gérer certaines ressources matérielles, dont les bus SPI, I2 C et 1-wire. Cela nécessite l’ajout de directives au démarrage du noyau, via l’intermédiaire du fichier boot/config.txt. Les détails seront fournis dans les sections suivantes séparément pour chaque bus. 1-wire Le bus 1-wire est un bus composé d’un nombre de lignes très réduit : un signal de données, la masse et une source d’alimentation. Cette dernière est optionnelle car certains périphériques 1-wire utilisent une alimentation ”parasite” tirée de la ligne de données. Le bus 1-wire est majoritairement utilisé pour connecter des capteurs de température tels que les DS18x20. La Figure 25 précise comment les périphériques 1-wire sont connectés au bus. La ligne de données DQ doit être connectée à la broche GPIO 4 qui a la fonction alternative d’interface vers un bus 1-wire. La broche GND doit être connectée à la masse (GND) du Raspberry Pi. La broche VDD doit être connectée à l’alimentation 3,3V du Raspberry Pi. Une résistance de pull-up de 10 kΩhm 15 doit être placée entre DQ et VDD . La Figure 26 donne le schéma de brochage du DS18B20. F IGURE 25 – Schéma de branchement de capteurs de température DS18x20. MAXIM 18B20 1 2 3 1 2 3 (BOTTOM VIEW) GND DQ VDD 8 BUS SPI, I2 C ET 1-WIRE F IGURE 26 – Schéma de brochage du DS18B20. (source : datasheet DS18B20, Maxim) Le noyau linux contient déjà un support pour le bus 1-wire et pour certains périphériques tels que les DS18x20. Pour utiliser le bus 1-wire, il est nécessaire d’insérer des modules noyau supplémentaires avec la commande modprobe comme montré cidessous. Il est aussi possible de spécifier le chargement de ces modules au démarrage via le fichier etc/modules. pi@rpi:˜$ modprobe w1-gpio pi@rpi:˜$ modprobe w1-therm + Device tree (noyau > 3.18) : ajouter la ligne suivante au fichier /boot/config.txt et redémarrer. dtoverlay = w1-gpio 15. La datasheet recommande 4,7 kΩhm. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 17 / 25 8.2 Inter-Integrated Circuit (I2 C) 8 BUS SPI, I2 C ET 1-WIRE pi@rpi:˜$cat /sys/bus/w1/devices/w1 bus master1/w1 master slaves 10-0008014bf476 28-000001a603ea pi@rpi:˜$ cat /sys/bus/w1/devices/10-0008014bf476/w1_slave 27 00 4b 46 ff ff 08 10 52 : crc=52 YES 27 00 4b 46 ff ff 08 10 52 t=19250 pi@rpi:˜$ cat /sys/bus/w1/devices/28-000001a603ea/w1_slave 37 01 4b 46 7f ff 09 10 26 : crc=26 YES 37 01 4b 46 7f ff 09 10 26 t=19437 F IGURE 27 – Interaction avec des capteurs de température via 1-wire. Lorsque les modules liés au bus 1-wire sont correctement installés, les périphériques présents sur le bus et supportés par le noyau sont listés dans le système de fichiers (souvenez-vous de SysFS) sous le chemin /sys/bus/w1/devices. Le fichier virtuel w1 bus master/w1 master slaves contient les numéros de série des deux capteurs de température connectés au bus à la Figure 25. Il est alors possible d’obtenir la température reportée par les capteurs en interrogeant les fichier virtuels <identifiant>/w1 slave. Un exemple d’une telle interaction est montré à la Figure 27. 8.2 Inter-Integrated Circuit (I2 C) Dans cette section, un circuit intégré PCF8574 est connecté au bus I2 C du Raspberry Pi. Ce circuit permet d’étendre le nombre sorties, à la manière du registre à décalage utilisé en Section 4. La Figure 28 illustre comment le PCF8574 peut être connecté au Raspberry Pi et utilisé pour contrôler l’état de 4 LEDs. F IGURE 29 – Brochage du PCF8574 (source : NXP). + Device tree (noyau > 3.18) : ajouter les lignes suivantes au fichier /boot/config.txt et redémarrer. dtparam=i2c1=on dtparam=i2c arm=on Il est ensuite nécessaire de charger les modules i2c-bcm2708 et i2c-dev en utilisant la commande modprobe comme montré ci-dessous. Il est aussi possible de spécifier le chargement de ces modules au démarrage via le fichier etc/modules. pi@rpi:˜$ modprobe i2c-bcm2708 pi@rpi:˜$ modprobe i2c-dev F IGURE 28 – Schéma de branchement d’un PCF8574. Le schéma de brochage du PCF8574 est fourni à la Figure 29. Les broches SDA et SCL servent respectivement pour le transport bidirectionnel de données et comme signal d’horloge. Les données sont transférées au rythme d’un bit par cycle d’horloge. Les broches VDD et VSS sont connectées respectivement à 3.3V et GND. Les entrées A0 à A2 permettent de donner une adresse différente au PCF8574. L’adresse du PCF8574 est 0x20 + la valeur binaire encodée avec A0 à A1. Dans le circuit de la Figure 28, l’adresse configurée est donc 0x20. Les sorties P0 à P7 sont contrôlables via le bus I2 C. L’activation de I2 C sur la platine Raspberry Pi nécessite quelques manipulations. Premièrement, le fichier /boot/config.txt doit être modifié de façon à contenir les lignes suivantes. La platine doit ensuite être redémarrée. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS Finalement, nous allons utiliser les commandes i2cdetect et i2cset. Celles-ci font partie du package i2c-tools qu’il est donc nécessaire d’installer comme montré ci-dessous. pi@rpi:˜$ sudo apt-get install i2c-tools L’utilitaire i2cdetect permet d’énumérer les adresses utilisées sur le bus. Pour cela, l’utilitaire envoie sur le bus un octet aux adresses comprises entre 0x00 et 0xFF. Si un acquittement (ACK) est reçu, c’est qu’un périphérique est présent. Le résultat de cette commande est montrée à la Figure 30. On y observe que l’adresse 0x20 est utilisée. C’est à cette adresse que le PCF8574 est présent. L’option -y de l’utilitaire permet de spécifier le numéro du bus I2 C utilisé. La Figure illustre également l’usage de l’utilitaire i2cset qui permet de faire une écriture vers un périphérique I2 C, en spécifiant 18 / 25 8.2 Inter-Integrated Circuit (I2 C) pi@rpi:˜$ 0 1 00: 10: -- -20: 20 -30: -- -40: -- -50: -- -60: -- -70: -- -pi@rpi:˜$ pi@rpi:˜$ pi@rpi:˜$ 8 i2cdetect -y 1 2 3 4 5 6 7 8 9 -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -- -- --- -- -- -- -- -i2cset -y 1 0x20 0x0F i2cset -y 1 0x20 0x05 i2cset -y 1 0x20 0x0A a -------- b -------- c -------- d -------- e -------- BUS SPI, I2 C ET 1-WIRE f -------- F IGURE 30 – Interaction avec le PCF8574 via I2 C. son adresse. Dans l’exemple, les écritures 0x00, 0x05 et 0x0A dernier échantillon de température. sont effectuées successivement. La première doit allumer toutes les LEDs (un bit à 0 allume la LED correspondante). La seconde Commande Description allume une LED sur deux et la dernière allume les autres LEDs (à 0xEE démarre une/les conversion(s) nouveau une sur deux). 0x22 arrête les conversions La Figure 31 montre une autre application du bus I2 C. Deux 0xAA lit le dernier échantillon capteurs de température DS1624 sont connectés sur le bus. Ils sont configurés avec des adresses différentes. Le capteur de droite a TABLE 9 – Commandes I2 C supportées par le DS1624. les bits d’adresse à 0 tandis que celui de gauche a le bit A1 à 1. Leurs adresses respectives sont 0x48 (adresse de base) et 0x4A (adresse de base + 2). Ceci peut être vérifié en utilisant l’utilitaire i2cdetect. Le schéma de brochage du DS1624 peut être obtenu La valeur de la température lue via I2 C est encodée sur 16 bits. à la Figure 32. Seuls les 13 bits de poids fort sont utilisés, les autres valant toujours 0. Les 8 bits de poids fort constituent la partie entière de la température en degrés Celsius tandis que les 8 bits de poids faible 1 sont des 256 èmes de degré. Les 16 bits sont transmis en 2 octets via 2 I C, l’octet de poids fort en premier. La Table 10 illustre la conversion entre valeurs lues et température. Par exemple, 0x12,0x78 correspond à une température de 18 °C (0x12) et 120 256 = 0, 46875 (0x78). F IGURE 31 – Deux capteurs DS1624 connectés au bus I2 C. Valeurs lues Représentation binaire T°C 0X12, 0x78 0X12, 0x18 0X12, 0x10 00010010 01111000 00010010 00011000 00010010 00010000 18,46875 18,09375 18,0625 TABLE 10 – Interpretation d’échantillons de température. F IGURE 32 – Brochage du DS1624 (source : Maxim). Le DS1624 est un circuit plus complexe que le PCF8574 utilisé dans le premier exemple de cette section. Il comporte notamment plusieurs commandes envoyées par I2 C pour le contrôler. Un sous-ensemble de celles-ci est montré à la Table 9. Les deux premières commandes (0xEE et 0x22) démarrent et arrêtent les conversions de température. La troisième commande (0xAA) lit le © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS L’exemple ci-dessus illustre l’interaction en Python avec le bus I2 C au travers de l’API smbus. Pour utiliser cet exemple, le package python-smbus doit être installé. Le programme démarre la conversion, puis, à chaque seconde il lit un échantillon, le convertit en température et l’affiche. 1 2 3 4 5 6 # -*- coding: utf-8 -*import smbus import time import sys DS1624_DEV_ADDR= 0x4A 19 / 25 8.3 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 Serial Peripheral Interface (SPI) DS1624_READ = 0xAA DS1624_START_CONV = 0xEE DS1624_STOP_CONV = 0x22 bus= smbus.SMBus(1) bus.write_byte(DS1624_DEV_ADDR, \ DS1624_START_CONV) while True: word= bus.read_i2c_block_data( \ DS1624_DEV_ADDR, \ DS1624_READ, 2) sample= (word[0] << 8) + word[1] temperature= (1.0 * sample)/256 print "\r%.6f °C" % (temperature), sys.stdout.flush() time.sleep(1) 8 BUS SPI, I2 C ET 1-WIRE périphérique est activée. Elle est désactivée lorsque le transfert est terminé. Une application possible du bus SPI est la commande d’un afficheur LED 7-segments via un circuit intégré spécialisé, le MAX7219. L’avantage de ce circuit est que seuls 3 signaux sont utilisés pour commander jusqu’à 8 afficheurs. Il suffit d’écrire les valeurs des chiffres de chaque afficheur dans des registres du MAX7219. Celui-ci s’occupe alors de contrôler les LEDs qui composent les afficheurs. La Figure 33 présente un exemple de circuit à base de MAX7219. Seuls 3 afficheurs y sont connectés. Le MAX7219 est connecté à la platine Raspberry Pi via le bus SPI. Le schéma de brochage du MAX7219 est fourni à la Figure 34. Note : le signal MISO n’est pas utilisé car seule des écritures vers le MAX7219 sont effectuées. 8.3 Serial Peripheral Interface (SPI) Le support du bus SPI nécessite le chargement du module spi bcm2835. Lorsque ce périphérique est chargé, de nouveaux devices apparaissent sous le nom /dev/spidev-x.y où x désigne le numéro du bus et y désigne le numéro du périphérique. Sur la plateforme Raspberry Pi, un seul bus SPI est présent (0) et deux périphériques sont supportés (0 et 1). Les périphériques 0 et 1 correspondent respectivement aux broches Chip Select SPI CE0 et SPI CE1. F IGURE 33 pi@rpi:˜$ modprobe spi\_bcm2835 pi@rpi:˜$ ls /dev/spidev* /dev/spidev-0.0 /dev/spidev-0.1 + Device tree (noyau > 3.18) : ajouter la ligne suivante au fichier /boot/config.txt et redémarrer. dtparam=spi=on L’accès au bus SPI peut être effectué en Python au travers de l’API spidev. Celle-ci n’est pas disponible sous forme de package. Il est nécessaire de la télécharger, de la compiler et de l’installer. La suite de commandes ci-dessous montre comment installer la version 3.1. pi@rpi:˜$ sudo apt-get install python-dev pi@rpi:˜$ wget https://pypi.python.org/\ packages/source/s/spidev/spidev-3.1.tar.gz pi@rpi:˜$ cd spidev-3.1 pi@rpi:˜$ sudo python setup.py install pi@rpi:˜$ cd .. Une partie de l’API spidev est résumée à la Table 11. La fonction SPIDev crée un object permettant d’interagir avec le bus SPI. La méthode open spécifie sur quel bus la communication SPI s’effectue. A cette fin, le numéro du bus et le numéro du périphérique doivent être renseignés. La méthode xfer2 envoit une séquence d’octets sur le bus (via MOSI) et lit simultanément les octets reçus (via MISO). Avant le transfert, la broche Chip Select associée au © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS F IGURE 34 – Brochage du MAX7219 (source : Maxim). Le programme ci-dessous contrôle les afficheurs 7-segments au travers de SPI et du MAX7219. L’objectif du programme est d’afficher un compteur sur les afficheurs. Ce compteur est incrémenté toutes les 100 ms. Le MAX7219 possède plusieurs registres qui servent par exemple à configurer le nombre d’afficheurs auxquels il est connecté (de 1 à 8) et la luminosité (grâce à un mécanisme PWM intégré). Les chiffress à afficher par chaque afficheur sont stockés dans 8 registres (cf. MAX7219 DIGITn ci-dessous). 1 2 3 4 5 6 import spidev import time MAX7219_DIGIT0 = 0x01 MAX7219_DECODE = 0x09 MAX7219_BRIGHTNESS = 0x0A 20 / 25 8.3 Serial Peripheral Interface (SPI) 8 BUS SPI, I2 C ET 1-WIRE Fonction Description x =spidev.SPiDev() x.open(bus, device) x.close() x.xfer2(bytes) crée une instance x nécessaire à l’interaction avec un bus SPI. initialise l’instance pour des communications sur le bus bus avec le périphérique device. déconnecte l’instance du bus. transfère la séquence d’octets bytes au sein d’une unique transaction. TABLE 11 – Résumé de l’API spidev. 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 MAX7219_SCANLIMIT = 0x0B MAX7219_SHUTDOWN = 0x0C spi = spidev.SpiDev() spi.open(0,0) spi.xfer2([MAX7219_DECODE, 0xFF]) spi.xfer2([MAX7219_BRIGHTNESS, 0x0F]) spi.xfer2([MAX7219_SCANLIMIT, 0x02]) spi.xfer2([MAX7219_SHUTDOWN, 0x01]) try: c= 0 while True: x= c for d in range(3): spi.xfer2([MAX7219_DIGIT0 + d, \ x % 10]) x= x/10 time.sleep(0.1) c= c + 1 finally: spi.close() Un grand nombre de circuit intégrés peuvent être connectés à un bus SPI. Par exemple, le TLC549 est un convertisseur analogique digital dont les échantillons peuvent être lus via SPI. Les écrans LCD graphiques des Nokia 5110 (circuit intégré PCD8544) sont contrôlés via SPI. Le MCP23S17 est un circuit intégré qui permet de contrôler via SPI jusqu’à 16 entrées/sorties. Certaines mémoires Flash et EEPROM peuvent être lues et écrites via SPI. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 21 / 25 A A ANNEXE - CONNECTEURS D’ENTRÉES/SORTIES Annexe - Connecteurs d’entrées/sorties I2C SDA I2C SCL GPCLK0 SPI MOSI SPI MISO SPI SCLK 3,3V GPIO 2 GPIO 3 GPIO 4 GND GPIO 17 GPIO 27 GPIO 22 3,3V GPIO 10 GPIO 9 GPIO 11 GND EEPROM ID SD GPIO 5 GPIO 6 GPIO 13 GPIO 19 GPIO 26 GND 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 5V 5V GND GPIO 14 GPIO 15 GPIO 18 GND GPIO 23 GPIO 24 GND GPIO 25 GPIO 8 GPIO 7 UART TxD UART RxD PCM CLK SPI CE0 SPI CE1 EEPROM ID SC GND GPIO 12 GND GPIO 16 GPIO 20 GPIO 21 TABLE 12 – Connecteur J8, modèles A+ et B+ I2C0 SDA I2C0 SCL GPCLK0 SPI0 MOSI SPI0 MISO SPI0 SCLK 3,3V GPIO 0 GPIO 1 GPIO 4 GND GPIO 17 GPIO 21 GPIO 22 3,3V GPIO 10 GPIO 9 GPIO 11 GND 1 3 5 7 9 11 13 15 17 19 21 23 25 2 4 6 8 10 12 14 16 18 20 22 24 26 5V 5V GND GPIO 14 GPIO 15 GPIO 18 GND GPIO 23 GPIO 24 GND GPIO 25 GPIO 8 GPIO 7 UART TxD UART RxD PCM CLK SPI0 CE0 SPI0 CE1 TABLE 13 – Connecteur P1, modèles A et B rev.1 © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 22 / 25 B B ANNEXE - CONTRÔLE DES GPIOS VIA LES REGISTRES Annexe - Contrôle des GPIOs via les reLa Table 16 décrit le contenu du registre GPSET0. Ce registre contrôle l’état des broches 0 à 31. Le registre GPSET1 a une strucgistres Les registres spéciaux sont accédés au travers d’opération de lecture / écriture en mémoire. On parle de memory-mapped registers. La Table 14 présente une liste partielle des registres destinés au contrôle des broches d’entrées/sorties. Chaque registre fait 32 bits de large. Pour chaque registre, les informations suivantes sont fournies : le nom du registre, une brève description, le type d’accès (lecture/écriture) et l’adresse à laquelle le registre est accessible en mémoire. Afin de contrôler une broche de sortie digitale, il est nécessaire de contrôler 3 registres. Le premier registre, nommé GPFSELn, permet de sélectionner la fonction associée à la broche : dans notre cas, sortie digitale. Ensuite, les registres nommés GPSETp et GPCLRp permettent respectivement de mettre l’état de la broche à un état haut ou bas. La Table 15 indique la signification des bits du registre GPFSEL0. Les autres registres GPFSELn sont structurés de la même manière. Le registre GPFSEL0 permet de contrôler la fonction des broches GPIO 0 à 9. Trois bits sont réservés dans ce registre par broche. Par exemple, la fonction de la broche 0 est sélectionnée avec les bits 0 à 2 de GPFSEL0 alors que la fonction de la broche 6 sera sélectionnée avec les bits 18 à 20. A ce stade, les seules fonctions qui nous intéressent sont 000 (0) pour une entrée et 001 (1) pour une sortie. Bits Field 31-30 29-27 26-24 ... 5-3 2-0 — FSEL9 FSEL8 ... FSEL1 FSEL0 ture similaire et contrôle les broches 32 à 54. Mettre à 1 le bit k dans le registre GPSET0 revient à mettre la broche k à un niveau haut (pour 0 ≤ k ≤ 31). Bits Field 31 ... 1 0 SET31 ... SET1 SET0 Description 0 = no change ; 1 = set GPIO pin 31 0 = no change ; 1 = set GPIO pin 1 0 = no change ; 1 = set GPIO pin 0 TABLE 16 – Structure du registre GPSET0. La Table 17 décrit le contenu du registre GPCLR0. Le comportement est similaire à celui du registre GPSET0 à l’exception que mettre à 1 le bit k dans le registre GPCLR0 revient à mettre la broche k à un niveau bas (pour 0 ≤ k ≤ 31). Le registre GPCLR1 a une structure similaire et contrôle les broches 32 à 53. Bits Field 31 ... 1 0 CLR31 ... CLR1 CLR0 Description Description 0 = no change ; 1 = clear GPIO pin 31 0 = no change ; 1 = clear GPIO pin 1 0 = no change ; 1 = clear GPIO pin 0 TABLE 17 – Structure du registre GPCLR0. Reserved Select function of GPIO 9 Select function of GPIO 8 La Table 18 décrit le contenu du registre GPLEV0. Ce registre permet de connaı̂tre l’état des broches 0 à 31. Le registre GPLEV1 a une structure similaire et permet de lire l’état des broches 32 à 54. L’extrait de programme en C suivant illustre comment la broche 22 peut être configurée en sortie. Pour cela, il est nécessaire de remplacer les bits 6 à 8 dans le registre GPFSEL2 par la valeur Select function of GPIO 1 Select function of GPIO 0 TABLE 15 – Structure du registre GPFSEL0. Register name Description Access Addresses GPFSEL0 GPFSEL1 ... GPFSEL5 select function select function ... select function R/W R/W 0x7E200000 0x7E200004 R/W 0x7E200014 GPSET0 GPSET1 set output state set output state W W 0x7E20001C 0x7E200020 GPCLR0 GPCLR1 clear output state clear output state W W 0x7E200028 0x7E20002C GPLEV0 GPLEV1 read input state read input state R R 0x7E200034 0x7E200038 several registers configure pin events read datasheet GPPUD, GPPUDCLOK..1 configure pull-up/down read datasheet TABLE 14 – Aperçu des registres spéciaux destinés au contrôle des entrées/sorties du SoC BCM2835. © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 23 / 25 B.1 Exemple complet en C B ANNEXE - CONTRÔLE DES GPIOS VIA LES REGISTRES F IGURE 35 – Correspondance entre adresses de bus, physiques et virtuelles. Bits Field 31 ... 1 0 LEV31 ... LEV1 LEV0 Description 0 = low ; 1 = high 0 = low ; 1 = high 0 = low ; 1 = high TABLE 18 – Structure du registre GPLEV0. 001. Assurez-vous que vous comprenez pourquoi il s’agit de ce registre et de ces bits. Le programme suivant fait l’hypothèse que l’adresse du registre GPFSEL2 se trouve préalablement dans la variable GPFSEL2. La valeur du registre est lue et les bits 6 à 8 sont masqués en effectuant un ET bit à bit (opérateur &) avec la valeur exprimée en hexadécimal 0xFFFFFE3F. Cette valeur correspond à un nombre de 32 bits dans lequel tous les bits sont à 1 sauf les bits 6 à 8 qui valent 0. Le résultat est combiné (opérateur |, OU bit à bit) avec la valeur sélectionnant une sortie (1) décalée de 6 positions vers la gauche (opérateur <<). Le résultat final est écrit dans le registre GPFSEL2. 1 *GPFSEL2= (*GPFSEL2 & 0xFFFFFE3F) | (1 << 6) Un exemple de programme complet est fourni en annexe à la Section B.1. B.1 Exemple complet en C Cette section contient un exemple complet de programme en C permettant d’accéder directement aux registres du SoC. Le programme nécessite d’accéder aux registres spéciaux du SoC. Cependant, ces registres se situent dans une zone mémoire qui n’est pas directement accessible à un programme utilisateur. Il est cependant possible d’accéder à l’entièreté de la mémoire au travers du pseudo fichier /dev/mem. L’accès à ce pseudo fichier est restreint : il faut les privilèges d’administrateur. Accéder au fichier /dev/mem avec des appels systèmes tels que read et write n’est pas très efficace. Pour cette raison, le programme demande au kernel de créer une page mémoire alignée sur la partie du fichier (lire de la mémoire) qui correspond aux registres spéciaux © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS contrôlant les GPIO. Ainsi, lire/écrire dans cette page mémoire aura pour effet de lire/écrire dans /dev/mem à la position des registres qui nous intéressent. Pour obtenir une telle page, l’appel système mmap est employé. Il est notamment nécessaire d’indiquer à mmap l’adresse mémoire de base à laquelle se situent les registres. Cette adresse vaut 0x20200000. Les étudiants attentifs auront peut être remarqué que cette adresse est fort différente des adresses renseignées à la Table 14. La raison est simplement que /dev/mem expose la mémoire au travers des adresses physiques alors que la Table 14 renseigne les adresses des registres GPIO sur un bus interne au SoC. La Figure 35 illustre la correspondance entre les divers types d’adresses. Toute cette technique est regroupée dans la fonction get mmapped gpio dans le programme ci-dessous. Le restant du programme consiste à accéder aux registres au travers de la page mémoire ainsi créée. Le programme configure la broche 27 en sortie puis change sa valeur toutes les secondes. La suite de commandes ci-dessous montre comment compiler un programme en C et comment l’exécuter. Dans notre cas, le programme est composé d’un seul fichier source nommé test-gpio.c. La commande gcc désigne le compilateur C (GNU C Compiler). Il est invoqué avec les options -Wall et -Werror qui demandent respectivement de reporter tous les avertissements générés lors de la compilation et de considérer les avertissements comme des erreurs. L’option -o suivie d’un nom de fichier spécifie le nom du fichier résultant de la compilation. Le dernier argument est le fichier source (test-gpio.c). pi@rpi:˜$ gcc -Wall -Werror -o test-gpio test-gpio.c pi@rpi:˜$ sudo ./test-gpio pi@rpi:˜$ 24 / 25 B.1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 Exemple complet en C #include #include #include #include #include #include #include #include B ANNEXE - CONTRÔLE DES GPIOS VIA LES REGISTRES <fcntl.h> <stdio.h> <stdlib.h> <stdint.h> <sys/mman.h> <sys/stat.h> <sys/types.h> <unistd.h> #define PHYS_GPIO_BASE 0x20200000 #define GPFSEL0_OFFSET 0x00000 #define GPSET0_OFFSET 0x0001C #define GPCLR0_OFFSET 0x00028 #define GPF_INPUT 0 #define GPF_OUTPUT 1 void * get_mmapped_gpio() { int page_size= getpagesize(); int fd= open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { perror("open"); return NULL; } void * addr= mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, PHYS_GPIO_BASE); if (addr == MAP_FAILED) { perror("mmap"); return NULL; } close(fd); return addr; } int main() { void * addr= get_mmapped_gpio(); if (addr == NULL) exit(EXIT_FAILURE); volatile uint32_t * GPFSEL0= (volatile uint32_t *) (addr + GPFSEL0_OFFSET); volatile uint32_t * GPSET0= (volatile uint32_t *) (addr + GPSET0_OFFSET); volatile uint32_t * GPCLR0= (volatile uint32_t *) (addr + GPCLR0_OFFSET); unsigned char pin= 27; volatile uint32_t * GPFSEL= GPFSEL0 + (pin/10); int bit_pos= 3 * (pin%10); *GPFSEL= (*GPFSEL & ˜(7 << bit_pos)) | (GPF_OUTPUT << bit_pos); while (1) { *GPSET0= 1 << pin; sleep(1); *GPCLR0= 1 << pin; sleep(1); } return 0; } © 2015, B. Q UOITIN, D. H AUWEELE et G. H UYSMANS 25 / 25