
Au sein de la gamme des cœurs de processeurs proposés par ARM, le Cortex-M3, opérant sur des registres de 32 bits, fournit un compromis entre une puissance de calcul appréciable et une consommation réduite qui, sans atteindre les performances du MSP430 (16 bits), propose néanmoins des modes de veille en vue de réduire la consommation moyenne d’une application. Bien que les nombreux périphériques disponibles ainsi que l’énorme quantité de mémoire associée à ce processeur puissent justifier l’utilisation de bibliothèques dont une implémentation libre est disponible sous le nom de libopencm3, nous verrons qu’il est possible d’appréhender un certain nombre de ces périphériques pour en faire un usage optimal en accédant directement aux registres qui en contrôlent l’accès. Le cœur M3 est décliné par de nombreux fondeurs : nous nous focaliserons ici sur l’implémentation de ST Microelectronics sous le nom de STM32F1 (dans la suite, le microcontrôleur sera nommé simplement STM32 car la plupart des applications sont portables sur les autres modèles).
1. Introduction
La famille des microprocesseurs STM32 de ST Microelectronics fournit une vaste gamme de périphériques autour d’un cœur d’ARM Cortex-M3 [CortexM3], allant du simple GPIO (port d’entrée-sortie généraliste) et interface de communication série synchrone (SPI) ou asynchrone (RS232) aux interfaces aussi complexes que l’USB, Ethernet ou HDMI. Un point remarquable est qu’un certain nombre de ces processeurs possèdent deux convertisseurs analogiques-numériques, permettant un échantillonnage simultané de deux grandeurs analogiques. Cadencé sur un résonateur interne ou sur un quartz externe haute fréquence 8 MHz (multiplié en interne au maximum à 72 MHz), ce processeur est compatible pour des applications faible consommation (section 9) avec un mode veille dont le réveil s’obtient par une condition sur une horloge interne ou une interruption externe. La multiplicité des horloges et leur utilisation pour cadencer les divers périphériques est d’ailleurs un des aspects les plus déroutants dans la prise en main du STM32.
Notre choix d’investissement de temps sur ce microcontrôleur en particulier est dirigé par quelques contraintes techniques :
- avant tout, un double convertisseur analogique-numérique rapide (1 M échantillons/s) censé garantir la simultanéité des conversions sur deux voies, un point clé si du traitement numérique additionnel est effectué sur une combinaison des deux voies,
- une architecture ARM Cortex-M3 exploitée par d’autres constructeurs : nous ne nous enfermons pas sur une architecture supportée par un unique fondeur, les principes de base concernant le cœur du processeur et la toolchain peuvent être réutilisés ultérieurement sur un autre processeur basé sur la même architecture (par exemple Atmel SAM3),
- un mode veille proposant une consommation raisonnable pour les applications embarquées autonomes qui nous intéressent.
2. Architecture du processeur – implémentation d’un circuit
Le lecteur désireux de simplement exploiter un circuit commercialement disponible devrait pouvoir travailler sur le circuit STM32H103 de Olimex1.
Pour notre part, nous nous proposons d’exploiter un circuit dédié, spécifiquement développé en vue d’émuler le port parallèle d’un PC, fournissant ainsi accès à la majorité des signaux utiles mais surtout illustrant le peu de composants annexes nécessaires au fonctionnement de ce microcontrôleur : un quartz 32,768 kHz, éventuellement un quartz 8 MHz, et des condensateurs de découplage aux 4 coins pour filtrer l’alimentation (Figs. 1 et 2). Ce circuit comporte par ailleurs un convertisseur USB-RS232 pour fournir une interface communément disponible sur tout PC récent. Nous verrons deux applications de cette carte, dans un cas pour émuler le comportement du port parallèle d’un PC pour le contrôle d’un instrument (section 5), et, plus ambitieux, la réalisation d’une station de mesure météorologique (section 8).
3. Chaîne de compilation et bibliothèques
La chaîne de compilation est basée sur l’habituelle génération des binaires issus de binutils et gcc sur architecture x86 à destination du processeur ARM, et en particulier Cortex M3. Un script à peu près parfait est disponible sous la nomenclature summon-arm-toolchain – accessible par git au moyen de git clone git://github.com/esden/summon-arm-toolchain.git – pour aller rechercher l’ensemble des archives sur les sites appropriés et compiler les outils nécessaires à générer un binaire à destination du STM32. On notera que cette même toolchain est fonctionnelle pour d’autres architectures ARM, notamment l’ARM7 fourni dans l’ADuC7026 de Analog Devices déjà présenté auparavant [LM117].
La compilation se fait classiquement à l’aide de la commande suivante :
Celle-ci permet d’installer la toolchain avec la version Vanilla de GCC au lieu de linaro GCC (USE_LINARO=0), sans la compilation et l’installation de OpenOCD (OOCD_EN=0), disponible sous forme de paquet binaire dans toutes les distributions. Par défaut, seule la bibliothèque libre libopencm3 est installée. Pour installer une bibliothèque propriétaire mais gratuite développée par ST, libstm32, l’option LIBSTM32_EN=1 devra être passée à la ligne de commande. Nous exploitons en particulier cette fonction pour compiler les nombreux exemples disponibles sur le web, en décortiquer le fonctionnement et ainsi accéder aux mêmes fonctionnalités en enrichissant libopencm3. Par défaut, les outils seront installés dans $HOME/sat.
Obtenir un compilateur fonctionnel ne constitue que le début de l’aventure du développement sur processeur ARM Cortex-M3. Ce cœur de processeur 32 bits est en effet supporté par divers fondeurs de circuits intégrés, et une tentative d’unification du support logiciel en vue de la portabilité du code d’un fondeur à l’autre est proposée sous la nomenclature CMSIS. Comme souvent dans cette thématique, l’intention est sûrement noble, mais le résultat consiste en une bibliothèque à la licence peu rassurante (ST) exploitant abusivement des structures de données lourdes dont l’utilisation sur un système embarqué est un peu surprenante. D’un autre côté, une architecture 32 bits documentée sur plus de 1000 pages [RM0008] est difficile à appréhender par la lecture d’une datasheet décrivant la fonction de chaque registre2 : un compromis appréciable en termes de licence d’utilisation, complexité et proximité au matériel semble être le projet libopencm3 (libopencm3.org). Accompagnée de nombreux exemples concrets – et en particulier sur le point épineux de l’exploitation du périphérique USB – cette bibliothèque est facile à appréhender malgré un manque de maturité certain et quelques périphériques absents qui seront sans doute comblés rapidement.
Une fois la compilation de toolchain finie, nous disposons dans $HOME/sat des outils nécessaires à la compilation d’applications : nous ajoutons le répertoire $HOME/sat/bin dans le PATH,
La ligne relative au répertoire include et lib servira lors de la compilation d’applications telle que présentée plus loin pour accéder aux bons répertoires lors de la génération du binaire.
4. Outils de programmation
Le second prérequis, après l’obtention d’une chaîne de compilation fonctionnelle, concerne l’outil pour programmer le microcontrôleur. Deux solutions sont possibles :
- la programmation par RS232 avec un outil tel que stm32flash (code.google.com/p/stm32flash/).
- la programmation par JTAG grâce à OpenOCD et à une sonde.
4.1 stm32flash
La récupération des sources de stm32flash se fait par :
Un simple make && sudo make install dans le répertoire compilera et installera l’outil.
stm32flash prend en argument le nom du fichier contenant l’image binaire à placer en mémoire du microcontrôleur (fichier .bin ou .hex), la commande à effectuer (lecture, écriture, vérification) et l’interface de communication. Accessoirement, le débit de communication peut être ajouté
Afin de passer le STM32 en mode programmation il faut, comme pour bon nombre d’autres microcontrôleurs, manipuler deux broches. La première est BOOT0 qui doit être mise à la tension d’alimentation VCC. Cette commande est validée par une mise à GND de la broche reset du STM32. À ce moment le microcontrôleur est prêt à être programmé avec la commande :
Le -w signifie que le fichier main.bin doit être écrit en flash, le -g 0x0 spécifie l’adresse où commencera l’exécution (0x0 correspond au début de la flash). Nous proposons une modification du module ftdi_sio.ko permettant de manipuler deux broches du composant servant de convertisseur USB-RS232 (FT232RL) et de stm32flash pour manipuler ces deux signaux lors de la programmation3. Ces manipulations nécessitent néanmoins un transistor monté en circuit inverseur pour palier à l’état par défaut (incorrect) des broches du FT232RL, et d’un circuit de gestion du reset, Analog Devices ADM809, pour retarder l’impulsion d’initialisation lors de la mise sous tension et ainsi permettre au FT232RL de stabiliser l’état de ses sorties avant que le microcontrôleur ne tente d’exécuter son application (Fig.3). Une présentation plus détaillée des possibilités de “détournement” des FTDIs fera l’objet d’un futur article.
4.2 OpenOCD
L’utilisation du JTAG pour la programmation d’un microcontrôleur présente de nombreux intérêts. Sans rentrer dans les détails, il permet de charger le binaire bien plus rapidement qu’avec une liaison série et de débugger le code (à l’aide de GDB connecté à OpenOCD) .
L’incantation pour programmer un STM32 à l’aide d’une sonde JTAG et de OpenOCD est
L’interface (la sonde JTAG) dépend bien entendu du matériel disponible. Bien que notre carte ne soit pas une Olimex, l’utilisation de ce fichier de configuration permet de fixer certaines informations plus confortablement. Bien entendu là encore la configuration devra être adaptée à la version du STM32 utilisée4.
4.3 Compilation de programme
La compilation des fichiers .c ainsi que la génération du fichier au format ELF se fait à l’aide des commandes suivantes :
Nous y activons quelques warnings pour la compilation, ajoutons les en-têtes et la libopencm3_stm32f1.a. Nous en profitons pour ajouter également la libc qui va permettre d’utiliser des fonctions telles que sprintf().
Selon que openOCD ou stm32flash soit utilisé, il faudra générer un fichier au format hexadécimal ou binaire.
5. Premier exemple : faire clignoter une LED
Pour ne pas faillir à la règle, la découverte du STM32 va se faire en pilotant une broche.
Quelle que soit l’application, le STM32 par défaut est cadencé par son oscillateur interne, peu précis en fréquence : nous allons donc passer sur le quartz 8 MHz, nous autorisant ainsi à cadencer le microcontrôleur à la vitesse de 72 MHz (au maximum) par multiplication interne. Cadencer le STM32 à 72MHz n’est pas une obligation. La réduction de la vitesse des horloges (utilisation d’un plus faible multiplieur pour la PLL) est une solution pour baisser légèrement la consommation globale du STM32.
Cette fonction, d’apparence simple, cache de nombreuses opérations telles que la configuration de la PLL, des horloges APB1 et APB2 utilisées pour les périphériques du STM32, le basculement sur quartz HSE (quartz externe haute-fréquence) et l’attente de la fin du calibrage de ce dernier.
Les ports d’entrée-sortie (General Purpose Input Output, GPIO) du STM32 sont synchrones : nous avons besoin d’activer l’horloge pour le(s) port(s) que nous allons utiliser. Dans notre cas, la broche est PC7, ainsi il faut en premier lieu activer ce port :
puis configurer la GPIO en sortie et la mettre à l’état haut :
Pour finir, nous entrons dans une boucle infinie dans laquelle nous mettons la GPIO à l’état haut, puis après une attente (itération sur la mnémonique assembleur nop en boucle), nous la passons à l’état bas :
En quelques lignes de code nous avons ainsi pu configurer la vitesse du STM32, la nature de la source d’horloge, et avons pu manipuler une GPIO. Une application concrète de ces concepts très simples consiste à émuler le port parallèle d’un PC en vue de contrôler un instrument exploitant une telle interface. Le RADAR de sol (Ground Penetrating RADAR, GPR) de la société suédoise Malå est un instrument permettant de sonder les discontinuités de permittivité ou de conductivité dans le sous-sol à des profondeurs allant de quelques centimètres (état des routes ou des armatures dans les murs en béton armé par exemple) à quelques centaines de mètres (glaciers et banquise). Cet instrument, relativement ancien pour ses versions CU (Fig. 4) et CUII, se connecte à un ordinateur de contrôle de type compatible IBM au moyen du port parallèle (Centronics). Notre objectif est d’automatiser la mise sous tension de cet instrument, l’acquisition de trames contenant les mesures, stockage des informations sur support de stockage non-volatil, et mise en veille, le tout pour une consommation réduite en vue d’un fonctionnement autonome pendant plusieurs mois. Ayant identifié la fonction des diverses broches du port parallèle dans ce protocole de communication (en partie grâce à une documentation fournie par le constructeur, et en partie par nos propres écoutes sur le bus de communication lors de l’échange d’informations sous le contrôle du logiciel propriétaire commercialisé par le constructeur de l’instrument), ce protocole est émulé par GPIO. La puissance du Cortex M3 ne se justifie évidemment pas pour cette application triviale, mais nous verrons plus loin comment les informations acquises sont stockées sur carte SD (section 10) et comment le microcontrôleur est périodiquement réveillé de son mode de veille profonde par une horloge temps-réel fournie comme périphérique matériel indépendant (section 9). La puissance de calcul du Cortex M3 ne se justifie que s’il y a traitement embarqué des informations en vue d’en extraire les informations pertinentes permettant de réduire la quantité d’information stockée (compression, intercorrélation) ou en transmission par liaison sans fil.
L’extrait de programme ci-dessous propose une implémentation de la fonction d’écriture entre le STM32 et l’unité de contrôle du RADAR RAMAC (la fonction de lecture est trop longue et sans nouveauté pour être présentée ici, mais est disponible dans l’archive de programmes associée au manuscrit de cet article sur les sites http://jmfriedt.free.fr et www.trabucayre.com5. Connaissant le protocole de communication qui a été sondé par un analyseur logique Logic Sniffer6, les diverses séquences de manipulation de l’état des broches sont implémentées de façon logicielle. La séquence des commandes, dont seules les premières étapes d’initialisation sont décrites ici (Fig. 4, haut), consiste à placer l’unité de contrôle en liaison de données bidirectionnelles sur 8 bits, lecture de la fréquence de fonctionnement (R_FREQ), définition du nombre d’échantillons acquis (S_SAMP), position du début d’acquisition des échos par rapport à l’impulsion d’excitation (S_SIGPOS), et relance d’une acquisition temporairement interrompue (CONT).
6. Deuxième exemple : plus loin avec le clignotement d’une LED
Dans le premier exemple, les attentes entre deux transitions d’état du GPIO s’obtiennent par des boucles basées sur une instruction ne faisant rien. Non seulement cette solution n’est pas satisfaisante car la durée d’attente est intimement liée à la vitesse du cœur, mais en plus selon le niveau d’optimisation cette boucle peu potentiellement être supprimée par le compilateur. Nous allons présenter une solution plus fiable nous permettant d’avoir une base de temps plus stable.
Pour cela et généralement après avoir appris à manipuler une broche, le but suivant est d’apprendre à communiquer. Nous allons appliquer cette règle mais d’une manière plus “ancienne”, à savoir transmettre un classique hello world en morse [morse] au lieu du RS232.
“hello world” va donc donner ceci :
Le . correspond à 1/4 de temps, le - et le temps entre deux caractères à 3/4. L’espace entre deux mots à 7/4 de période. Une attente de 1/4 de temps est placée entre chaque impulsion. Le code étant clair, l’implémentation sera également facile sans compliquer inutilement l’exemple.
Pour obtenir des durées précises et non dépendantes du compilateur nous allons utiliser un périphérique du STM32 dont le travail consiste à déclencher une interruption lors du débordement d’un compteur interne, le systick.
Dans la suite, nous n’allons pas reprendre l’initialisation de l’horloge, pas plus que la configuration de la broche, nous allons juste nous focaliser sur la partie configuration du systick et son utilisation.
La première étape consiste en l’initialisation :
Le systick est configuré avec une fréquence de 9 MHz (l.2) et un débordement (génération d’une interruption) toutes les 1 ms (l.4). Les interruptions sont activées et le compteur est démarré. Bien entendu dans le code ci-dessus la durée avant interruption est codée en dur mais il est tout à fait possible de faire la même chose d’une manière plus élégante et capable de s’adapter automatiquement à la fréquence de l’horloge.
La seconde étape consiste en l’ajout du gestionnaire d’interruptions pour le systick et en la création d’une fonction d’attente.
La fonction sys_tick_handler est, par convention de libopencm3, le gestionnaire pour l’interruption du systick. Cette fonction n’aura comme seul rôle que d’incrémenter une variable à chaque débordement. Quand la variable atteindra 0xffffffff, son incrément fera retomber la valeur à 0;
La seconde fonction (Delay()) va attendre que la différence entre le contenu de la variable lors de l’entrée dans la fonction et la valeur courante ait atteint la valeur désirée.
Finalement, une dernière fonction qui va gérer le comportement d’attente selon le caractère fourni :
La fonction est relativement simple à comprendre. Pour le caractère . ou - une durée va être renseignée, la LED est allumée pendant un temps correspondant, puis éteinte, et une seconde attente va être réalisée. Dans les autres cas, la fonction est mise en attente puis ressort sans toucher à la LED. SHORT_TIME correspond à 250 ms, LONG_TIME à 750 ms, et SPACE_TIME à 1750 ms.
Enfin il ne reste plus qu’à assembler le tout pour avoir un programme prêt pour la compilation.
Hormis le tableau contenant le message, la fonction principale main() est très simple puisqu’après configuration du microcontrôleur, le programme rentre dans une boucle infinie qui va envoyer le message en permanence.
Nous pouvons voir clignoter une LED au rythme de l’envoi du message. Mais il faut reconnaître que ce n’est pas une manière spécialement évidente de transmettre des informations ni de débugger une application.
7. Communication RS232
Le STM32 dispose selon les modèles de 4 à 6 USARTs. Sur notre carte, l’USART1 est connecté à un convertisseur USB-série.
La configuration de ce périphérique se fait de la façon suivante : comme tous les périphériques du STM32, il est nécessaire d’activer l’horloge pour l’USART ainsi que pour les broches de communication :
Chaque broche du microcontrôleur dispose de plusieurs fonctions selon les besoins, il est donc nécessaire de configurer les broches PA9 (USART1_TX) et PA10 (USART1_RX) en alternate function push-pull (GPIO_CNF_OUTPUT_ALTFN_PUSHPULL) pour TX et en input floating (GPIO_CNF_INPUT_FLOAT) ou input pull-up (GPIO_CNF_INPUT_PULL_UPDOWN) pour RX ([RM0008, pp.161-162]).
Nous devons ensuite configurer le port et l’activer.
Une fois encore, la libopencm3 fournit des fonctions qui simplifient notre code de tous les détails spécifiques au matériel visé.
La dernière étape consiste à faire appel à cette fonction dans un main avant d’envoyer des messages sur le port série :
La fonction usart_send_blocking(..) va s’assurer en premier lieu que l’USART est disponible (pas de caractère en cours d’envoi) en attendant que le bit TXE du registre USART_SR (status register) soit à 1. Dès que le périphérique est disponible, l’octet est chargé dans le registre USART_DR (data register) et la fonction rend la main.
8. Application pratique
Les premiers exemples nous ont permis de découvrir le STM32, de comprendre comment générer un binaire et de programmer le microcontrôleur. Cependant, cette compréhension est en somme relativement basique : nous savons certes comment manipuler des GPIOs, nous avons un mécanisme d’attente précis et une solution pour communiquer avec l’extérieur, mais par rapport aux périphériques disponibles sur ce microcontrôleur, cette première découverte n’est en somme qu’un “amuse-bouche”, tout reste à faire en pratique.
Nous nous sommes donc fixés comme objectif la réalisation d’une station météorologique (Fig. 5) équipée d’un capteur de pression, d’une sonde de température, d’un hygromètre et d’un capteur de lumière (Fig. 6). Au-delà de l’aspect ludique de pouvoir enregistrer les données sur une carte SD ou de les transmettre à un ordinateur, cette application va nous permettre d’aller plus avant dans la découverte de ce microcontrôleur. Les composants choisis étant hétérogènes du point de vue du protocole, il est donc nécessaire de comprendre et maîtriser des périphériques tels que le SPI, l’I2C, l’ADC et les timers.
Un second objectif de la compréhension de la structure de ce microcontrôleur est le portage de TinyOS sur celui-ci : cette partie sera présentée dans un prochain article. Afin de pouvoir coller au plus près à la structure interne du STM32 (registre, configuration, ...), nous avons décidé dans la suite de ne plus faire usage de la libopencm3 qui, bien que pratique car permettant de développer rapidement sans avoir à connaître la structure de chaque registre pour un périphérique, empêche d’avoir une idée précise de comment faire en sorte de rendre les modules les plus génériques possibles et donc éviter la duplication de code.
8.1 MS5534A : SPI
8.1.1 Présentation du capteur
Le MS5534A [MS5534] est un capteur de pression compensé en température qui communique avec un microcontrôleur au moyen d’un bus synchrone bidirectionnel (deux signaux de données, l’un en provenance et l’autre vers le périphérique, et une horloge). Les commandes à envoyer sont sur 10 bits et les données reçues sont sur 16 bits.
Ce capteur fournit non seulement une information permettant de calculer la pression mais également la température. Afin d’obtenir ces deux informations, il est nécessaire de récupérer un jeu de constantes de calibrage qui permettront ensuite de réaliser les calculs nécessaires pour obtenir des valeurs en °C et en hPa.
La communication peut s’apparenter à du SPI à quelques exceptions près :
- Il n’y a pas de Chip-Select, le capteur risque donc de parler alors qu’il n’est pas la cible ;
- La broche DOUT (MISO) ne passe jamais en haute impédance quand le composant est au repos, imposant donc son potentiel à la ligne. Ceci n’est pas compatible avec la communication multi-esclave ;
- Le capteur lit les données sur le front montant de l’horloge et le microcontrôleur doit lire les données issues du capteur sur le front descendant. Il n’existe pas de mode SPI adapté à cette situation ;
- Les commandes à lui envoyer sont codées sur 10 bits. La plupart des microcontrôleurs ne peuvent communiquer qu’en 8 ou 16 bits.
Une application note [AN510] explique comment utiliser ce composant avec ce protocole. Ainsi, pour palier au manque de CS, un buffer trois états (74HC1G125 par exemple) doit être installé : ce composant dispose d’un enable actif à l’état bas. Grâce à ce composant, il est possible à la fois de couper la ligne DOUT et de laisser MISO en haute-impédance, ceci afin de ne pas parasiter la communication dans le cas de l’utilisation de plusieurs esclaves SPI. Le problème du mode de communication non compatible est réglé par une reconfiguration de CPHA entre les phases de lecture et d’écriture. Finalement, ce document fournit les commandes en 16 bits (par adjonction de 0).
8.1.2 Configuration du SPI
En vue de communiquer avec le capteur, il est logiquement nécessaire de mettre en place un certain nombre de fonctions pour la configuration et la communication en SPI. Nous allons utiliser arbitrairement SPI2 mais le même code sera applicable à tous les autres SPI disponibles. Attention, la configuration des registres est identique, modulo le passage de l’adresse adaptée, mais par contre les broches ne sont (bien évidemment) pas les mêmes et le SPI1 est cadencé par APB2 et non APB1 (donc fréquence maximale de fonctionnement différente).
Comme nous l’avons vu dans les premiers exemples, il est nécessaire d’activer les périphériques ainsi que de configurer les broches :
Vient maintenant la configuration du SPI (baudrate, mode master, CPHA, CPOL, ...). Dans le cas de l’utilisation du SPI sans interruption ni DMA, la configuration se fait uniquement au niveau du registre SPI_CR1 :
Par acquis de conscience, ne sachant pas l’état courant du SPI et comme certains paramètres ne peuvent être modifiés si le périphérique est actif, nous mettons le bit 6 (SPE : SPI enable) à 0 pour désactiver le SPI (l.4).
Les périphériques SPI peuvent également être configurés en I2S (Inter-IC Sound, Integrated Interchip Sound)7, il faut donc choisir le mode SPI en mettant à 0 le bit 11 du registre SPI_I2SCFGR(l.6).
Nous ne nous soucions pas du contenu antérieur du registre SPI_CR1 que nous écrasons avec notre configuration. Le SPI est maintenant configuré en tant que maître (l.11), full duplex (MISO et MOSI)(l.6) pour des paquets de 8bits par transfert (l.7) le bit de poids fort en premier (l.9) , SCK à l’état bas lors de l’absence de transmission (l.12), avec une vitesse de transfert de 4,5 MHz (36 MHz pour APB1, divisé par 8)(l.10) et avec un Chip-Select géré manuellement (l.8) .
Il ne reste plus qu’à activer le périphérique (l.16).
Nous allons ajouter quelques fonctions utilitaires pour la communication avec le capteur : comme présenté précédemment ce capteur reçoit toutes les données lues sur le front montant de l’horloge et le microcontrôleur doit lire sur le front descendant :
Le STM32 ne supporte pas un changement de mode alors que le SPI est actif, il faut donc désactiver celui-ci, changer le CPHA et le réactiver.
Ensuite, nous créons une fonction qui va envoyer et recevoir les données.
La donnée est chargée dans le Data Register (SPI_DR), ensuite une attente est faite sur la transmission de celle-ci, puis sur la réception d’un octet depuis le capteur. Le même registre SPI_DR est utilisé pour récupérer l’information.
Et pour finir, la fonction qui va gérer la totalité de la communication (envoi d’un ordre, attente, puis récupération de l’information fournie par le capteur)...
8.1.3 Acquisition et traitement des données
La partie purement liée au SPI du STM32 (configuration et communication) est maintenant finie. Il nous faut finalement ajouter quelques fonctions pour envoyer et recevoir des commandes et les données, transmettre un reset au capteur, obtenir l’ensemble des paramètres nécessaires à l’exploitation de celui-ci, et traiter les informations de température et de pression obtenues.
Cette fonction sert à transmettre une commande et à recevoir la réponse du capteur. Les commandes, sur 16 bits, sont découpées en deux octets. Le périphérique est basculé dans le mode d’envoi, puis la commande est envoyée (l.8-10). Le ms5534a nécessite un temps de conversion de 33 ms, la donnée doit être récupérée dans un délai de maximum 100 ms après la fin de la conversion, ainsi nous utilisons la fonction Delay() présentée plus tôt pour garantir une attente correcte. Le SPI est ensuite basculé en mode lecture (l.16), les deux octets sont reçus (l.17-18) et la donnée sur 16 bits est reconstruite (l.20).
La seconde fonction concerne la transmission de la commande de reset :
Cette fonction ne présente pas de difficultés particulières, comme elle ne fait qu’envoyer 4 octets sans faire de lecture, nous configurons le STM32 puis nous envoyons séquentiellement les octets.
Il nous faut ensuite être capable d’obtenir les données de calibrage du capteur :
L’obtention des paramètres de calibrage du capteur n’a rien de particulièrement difficile non plus. Après avoir envoyé la commande de reset, telle que présentée dans la datasheet, nous récupérons les 4 mots (l.5-8). Ensuite, nous calculons l’ensemble des informations nécessaires pour l’obtention de la pression et de la température que nous stockons dans des variables globales pour leur réutilisation ultérieure. Les CAL_READ_Wx sont des #define fournis dans [AN510].
Et pour finir, nous créons la fonction de récupération et de calcul des deux grandeurs qui nous intéressent.
Après transmission à un ordinateur, nous pouvons générer des courbes d’évolution des informations fournies par le capteur telles que présentées Fig.7.
8.2 HH10D : I2C et input capture
8.2.1 Présentation
Le HH10D [HH10D] est un capteur d’humidité qui fournit un signal périodique proportionnel au pourcentage d’humidité dans l’air. Chaque composant est étalonné individuellement, ces informations sont stockées dans une EEPROM accessible en I2C.
L’obtention de la durée de la période d’un signal se fait, sur STM32, à l’aide d’un des périphériques timer. Il existe deux modes de capture :
- Le mode input capture qui permet d’obtenir uniquement la durée de la période du signal ;
- Le mode pwm input capture qui fournit en plus la durée de l’état haut ou bas (selon le front choisi pour la période) de ce même signal.
Bien que le HH10D fournisse un signal périodique (rapport cyclique de 50%), nous allons utiliser le pwm input capture pour comprendre ce mode qui est sans doute le plus complexe, sans pour autant nécessiter de grosses modifications par rapport au mode input capture de base.
8.2.2 Interrogation de l’EEPROM
Pour pouvoir exploiter l’information de période du signal, il est nécessaire de récupérer les données issues du calibrage du capteur, stockées dans l’EEPROM. Cette mémoire est accessible en I2C à une vitesse de communication de 400 kHz maximum.
Nous n’allons pas copier à nouveau l’activation de l’horloge pour ce périphérique. Il faut juste savoir que I2C1 est cadencé sur APB1 et utilise les broches PB6 et PB7 qui doivent être configurées en mode GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN.
La configuration du périphérique est en somme relativement simple.
Classiquement, le périphérique est désactivé (l.2). Pour l’heure, la configuration du registre I2C_CR1 se résume à forcer le mode i2c et non le SMBUS (l.5). Le registre I2C_CR2 ne pose pas plus de difficultés, nous ne voulons pas d’interruptions, il n’est donc nécessaire que de fournir la fréquence d’utilisation du périphérique (soit 36 MHz qui est le maximum pour APB1).
Le registre I2C_CCR est dédié à la fréquence de transfert, à la forme du signal SCL, ainsi qu’au mode de fonctionnement. Nous le configurons en fast mode afin de pouvoir atteindre les 400 kHz (l.2), le signal SCL aura un rapport cyclique de 1/3 (l.3) et finalement nous fournissons la durée de la période de SCL (l.4). Cette valeur correspond à la durée de l’état haut du signal SCL divisée par la période de l’horloge (I2C_CR2) [RM0008, p.755]. Donc il nous faut en premier lieu connaître cette durée en divisant la période de SCL (400 kHz : 2500 ns) par 3. Ce qui nous donne 833,33 ns, et finalement diviser ce résultat par 27,778 ns (36 MHz) ce qui nous donne 30 ou 0x1e.
La dernière partie va consister à configurer I2C_TRISE. Ce registre correspond au nombre maximum de périodes de l’horloge APB1 incrémenté de 1 pour la durée de transition entre l’état bas et l’état haut des signaux SCL et SDA. Cette information n’a pas de relation avec la vitesse de communication. Elle dépend du mode de communication et se trouve dans les spécifications du protocole I2C. Elle est de 1000 ns en standard mode et 300 ns en fast mode. La donnée à charger dans le registre correspond donc, puisque nous sommes en fast mode, à (300 ns/27,778 ns) ce qui donne 10,8. Cette valeur n’est pas entière, il faut donc la tronquer, soit 10. Le résultat étant donc 11.
Finalement, nous activons le périphérique (l.4).
La configuration du périphérique étant finie, nous allons ajouter les fonctions de communications sur bus i2c.
En se basant sur la documentation de l’EEPROM, la lecture des 4 octets nécessaires va se faire de la façon suivante :
- génération d’un start bit, suivi de l’envoi de l’adresse de l’EEPROM en mode écriture, le composant va produire un ACK, suite à quoi le microcontrôleur envoie l’adresse de la position en mémoire du premier octet à lire, cette seconde transmission va être également acquittée par l’EEPROM ;
- le microcontrôleur va re-générer un start bit et envoyer l’adresse de l’EEPROM mais en mode lecture (bit de poids faible à 1), comme précédemment l’EEPROM va acquitter la commande. Ensuite la mémoire va fournir les octets séquentiellement. À la fin de chacun d’eux, le microcontrôleur devra produire un ACK sauf pour le dernier octet où ce sera un NACK pour avertir le composant que la lecture est finie. Et finalement un stop bit est envoyé.
Par commodité, nous allons créer une fonction dont le seul but sera de générer le start bit :
Le bit START du registre I2C_CR1 est mis à 1, ensuite le STM32 se met en attente du passage à 1 du bit SB de I2C_SR1 (start bit envoyé).
Une seconde fonction va être utilisée pour l’envoi d’un octet au composant, en mode écriture :
L’adresse du composant est mise dans le registre de données, puis le STM32 se met en attente de la fin de la transmission (indiquée par le bit ADDR de I2C_SR1). Celui-ci passe à 1 lorsque l’adresse a été envoyée et que le composant esclave a transmis le ACK. La documentation du microcontrôleur précise qu’il est également nécessaire de lire I2C_SR2 (l.8). Finalement, il ne reste plus qu’à envoyer notre donnée, dans le cas présent, l’offset du contenu de l’EEPROM. Cette transmission se fait de la même manière que pour l’adresse, mais avec une attente sur l’information “buffer d’envoi vide” (I2C_SR1_TxE) et transfert fini (I2C_SR1_BTF).
La partie réception est un peu plus complexe à mettre en œuvre. En fait, il est nécessaire de gérer deux cas de figure. L’obtention d’un octet différent du dernier et le dernier.
La documentation du STM32 précise qu’il faut spécifier quelle sera la réponse ( (N)ACK) faite pour la réception du prochain octet avant de se mettre en attente de la fin de la transmission de l’adresse. Il n’est donc pas possible d’utiliser la fonction créée précédemment.
La différence avec le code précédent réside aux lignes 7 et 8. I2C_CR1_POS précise que le (N)ACK sera émis lors de la réception du prochain octet, le I2C_CR1_ACK précise que ce sera un ACK.
Ensuite, il faut boucler tant qu’il y a des octets à recevoir en dissociant deux cas :
- le cas où il reste plus qu’un octet à recevoir. L’attente se fait sur le simple fait d’avoir le registre de données non vide (I2C_SR1_RxNE) ;
- le cas où le prochain octet est le dernier. Avant de se mettre en attente de la réception, il est nécessaire de configurer le STM32 pour qu’un NACK (l.8) soit envoyé à la fin de la réception de l’octet en cours de transfert (l.7), suivi par le stop bit (l.9).
Et finalement, nous créons une fonction qui va faire appel à toutes les fonctions présentées pour obtenir les informations dont nous avons besoin.
Ainsi nous en avons fini avec l’obtention des paramètres de calibrage du capteur et nous pouvons passer à l’acquisition de données.
8.2.3 Récupération de la période du signal et du rapport cyclique
/// fig9.pdf ///
Chaque timer du STM32 dispose de 4 canaux pouvant être reliés aux broches du boîtier. Chacun de ces canaux est capable de capturer la période d’un signal (durée entre deux fronts montants ou descendants successifs), c’est le mode input capture (Fig. 8). Tel que représenté sur cette figure, le registre correspondant au canal 1 (TIM2_CCR1) est mis à jour avec la valeur courante du compteur global.
Il existe une variante de ce mode dans laquelle deux canaux sont liés (Fig. 9) et observent le même signal. Un des canaux (le canal 2 sur la figure) est utilisé pour l’obtention de la période de la même manière qu’en input capture. Le second canal (canal 1) va être sensible au front opposé du signal, ainsi sur la figure, ce canal fournira la durée de l’état haut. Afin de synchroniser les deux canaux, lorsque la condition de déclenchement du canal maître (canal 2) se produit, le compteur global du timer est remis à 0. Dans ce cas, un timer ne pourra plus être utilisé que pour une seule capture.
Comme pour tous les périphériques du STM32, il est nécessaire d’activer l’horloge pour TIM2 (sur APB1) et pour le port contenant la broche PA1 (APB2), ainsi que de configurer cette dernière en INPUT_FLOAT).
La configuration du timer2 en pwm input capture commence par le réglage du compteur global de ce périphérique (valeur maximale du compteur, fréquence de comptage, etc.). Nous savons que le HH10D génère une fréquence entre 5 kHz et 10 kHz. Donc nous devons pouvoir compter, au moins, jusqu’à 200 µs. La fréquence de l’horloge utilisée pour tous les timers (sauf TIM1) dépend de APB1 et de son prescaler8 :
- s’il n’est pas utilisé, la fréquence de TIMxCLK est identique à APB1 ;
- s’il est utilisé, alors la fréquence de TIMxCLK est le double de celle de APB1.
Comme la PLL qui fournit la fréquence aux APBx est de 72 MHz, le prescaler de APB1 est utilisé (division par 2 pour ne pas dépasser les 36 MHz). Ainsi TIM2CLK vaut 36 MHz x 2 (= 72 MHz), soit une période de 14 ns. À cette fréquence, il est possible de compter jusqu’à 910215 ns, soit 900 µs, ce qui est plus que suffisant pour nos besoins. Il serait possible de réduire la vitesse mais nous aurions une perte de précision.
Nous configurons TIM_CR1 pour que le compteur soit cadencé à la fréquence de l’horloge d’entrée (l.1) et s’incrémente (l.2). Nous configurons TIM2_PSC pour que le compteur soit incrémenté à chaque période de l’horloge d’entrée donc à 72 MHz. Le compteur repassera à 0 lorsqu’il atteindra 0xffff.
Une fois la base de temps définie, il faut configurer les deux canaux du timer. Les canaux 1 et 2 peuvent être utilisés avec comme source les broches TIM2_CH1 ou TIM2_CH2.
Du point de vue de la configuration, les 4 canaux partagent le registre TIM2_CCER, TIM2_CH1 et TIM2_CH2 partagent TIM2_CMR1 et TIM2_CH3 et TIM2_CH4 partagent TIM2_CMR2
Voyons donc d’abord la configuration du canal 2 :
Le canal 2 est configuré pour être déclenché sur le front montant du signal (l.6).
Ensuite nous configurons le registre TIM_CCMR1 pour que le canal fonctionne à la fréquence de l’horloge sans prescaler et nous spécifions que le canal 2 est connecté en direct (sur TIM2_CH2).
Il ne nous reste plus finalement qu’à activer le canal (l.15).
Si nous ne souhaitons faire que du input capture, la configuration est quasiment finie à ce stade. Mais comme nous allons utiliser le mode PWM, il est encore nécessaire de configurer le canal 1. Pour éviter d’ajouter trop de code et comme les deux configurations sont pratiquement identiques seul l’offset diffère (il faut enlever 8 à chaque décalage sur TIM_CCMR1 et 4 sur TIM_CCER). Les seules lignes réellement importantes concernent le front de déclenchement et le passage en mode indirect, c’est-à-dire la connexion du canal 1 sur la broche TIM2_CH2. Pour le canal 2 en mode direct nous avions :
et :
dans le cas du canal 1, nous aurons :
et :
Pour finir avec la configuration du périphérique nous avons besoin de synchroniser les deux canaux.
Nous passons donc le timer en mode maître esclave (l.2), avec une synchronisation des canaux sur TI2FP2 (broche TIM2_CH2) et avec remise à 0 du compteur lors d’un front montant détecté sur cette broche.
Il ne reste plus qu’à activer le périphérique :
Pour finir, nous ajoutons une fonction pour obtenir les informations concernant la forme du signal et pour calculer le taux d’humidité :
Le STM32 est mis en attente d’un front montant (et donc de la fin de la capture) (l.5), les valeurs mesurées sont disponibles dans le registre relatif au canal 2 pour la période (l.7) et au canal 1 pour la durée de l’état haut du signal (l.9). Nous convertissons la valeur de la période en une durée (l.11) puis en une fréquence en kHz (l.12) et nous réalisons le calcul pour obtenir le taux d’humidité.
8.3 TEMT6000 : ADC
8.3.1 Présentation
Le dernier composant que nous allons mettre en œuvre est le TEMT6000 [TEMT6000]. C’est un capteur de lumière ambiante. Ce composant se présente sous la forme d’un transistor NPN (monté en collecteur commun avec une résistance de 10 kΩ) qui fournit une tension proportionnelle à l’intensité lumineuse.
Pour cela, nous allons utiliser l’ADC du STM32, le composant étant connecté sur la broche PA0.
8.3.2 Configuration et acquisition
Contrairement aux précédents exemples, l’ADC dispose d’un prescaler au niveau du RCC. Ce composant ne peut pas être cadencé à plus de 14 MHz, la fréquence d’entrée du prescaler étant donnée par APB2 (72 MHz dans notre cas). Il n’est possible que de faire des divisions par 2,4,6,8. Ainsi pour obtenir la fréquence la plus rapide sans dépasser la limite, il faut donc diviser par 6.
Le coefficient de division étant donné [RM0008, p.125] (2 correspond à une division par 6).
L’étape, classique, et suivante consiste en l’activation du périphérique et du port contenant la GPIO qui nous intéresse. Il faut donc activer ADC1 et le port A sur APB2. La broche sur laquelle est connectée étant PA0 (ADC1_0), elle doit être configurée en mode entrée et de type INPUT_ANALOG.
Finalement, nous allons pouvoir configurer l’ADC :
Après la désactivation du périphérique, nous configurons les deux registres de contrôle. Pour ADC_CR1 nous ne souhaitons pas l’utilisation du watchdog, ni des modes tels que scan mode ou dual mode, et nous n’allons pas utiliser d’interruptions, sa configuration se résume à y mettre 0. Pour ADC_CR2 le résultat est le même car nous n’allons pas utiliser la température du STM32, nous n’exploiterons pas plus le déclenchement de la conversion sur événement, ni le DMA et nous souhaitons que les 12 bits issus de la conversion soient alignés à droite (bit de poids faible).
Nous configurons ensuite le temps de conversion du canal que nous allons exploiter :
Pour cela, plusieurs registres sont disponibles. Pour le cas qui nous intéresse, le canal 0 de l’ADC se trouve (ainsi que les canaux < 10) dans ADC_SMPR2. Les trois bits destinés à ce canal sont remis à 0 puis nous le configurons pour avoir un temps de conversion de 28,5 cycles (valeur 3 [RM0008, p.235]). Avec cette configuration le temps de conversion sera de 3,41 µs (tconv = (sample time + 12,5 cycles)*période de ADCCLK) [RM0008, section 11.6, p.216].
Maintenant il nous faut activer et calibrer l’ADC :
Cette étape se fait en deux temps, nous commençons par réinitialiser le registre de calibrage (l.4) et attendons que le bit soit remis à 0 (l.5) signifiant que l’opération s’est effectuée avec succès. Ensuite nous lançons le calibrage (l.7) et attendons la fin du traitement (l.8).
Pour finir avec la configuration, il faut spécifier quel sera le canal qui sera utilisé (dans cet exemple nous n’utilisons qu’un seul canal, mais nous pourrions aller jusqu’à 16).
Le registre ADC_SQR1 contient à la fois 4 bits (bits [23:20]) pour spécifier le nombre de canaux à exploiter à chaque fois qu’une conversion est lancée, 0 signifiant une conversion. Le reste de ce registre contient l’ordre des canaux lorsque plus de 12 conversions sont à faire. Le registre ADC_SQR2 comporte les canaux pour les conversions 12 à 7, et finalement ADC_SQR3 pour les conversions 6 à 1. Comme nous ne voulons qu’une seule conversion sur le canal 0 nous laissons ce dernier registre à 0.
Nous en avons donc fini avec la configuration. Une demande de conversion se fait de la façon suivante :
Une conversion est déclenchée quand le bit ADON est remis à 1 (l.3). Nous attendons ensuite que le bit SR_EOC du registre ADC_SR passe à 1 (l.6) pour récupérer le résultat de la conversion (l.8). Comme l’ADC est sur 12 bits et que la valeur maximale correspond à 3,3 V nous divisons le résultat par 4095 puis nous le multiplions par 3300 pour retourner le résultat en millivolts (Fig. 11).
8.4 Assemblage et mise en service
Nous avons à disposition l’ensemble des pilotes nécessaires à la fois pour l’exploitation du STM32 mais également pour la communication et le traitement des données acquises des capteurs de la station.
Il faut donc associer tout ça dans un main(). Pas grand chose de complexe, après la configuration du STM32 comme présentée au début, nous configurons l’USART3 sur lequel est connecté le convertisseur bluetooth-série. L’ensemble des composants est ensuite configuré ainsi que le systick pour appliquer une pause entre chaque envoi.
Dans notre boucle infinie, les données sont acquises, formatées et envoyées, toutes les 6 secondes.
Bien entendu, une attente active faite par notre fonction Delay n’est pas idéale. Il serait plus judicieux d’exploiter l’un des modes de mise en veille du microcontrôleur afin de réduire la consommation globale de la station.
9. Passage en mode faible consommation et réveil périodique
Le STM32 (et plus largement l’ensemble des cortex-m3 semble-t-il) dispose de trois modes faible consommation :
- Le sleep mode qui ne fait qu’arrêter l’horloge du CPU, mais laisse actif les quartz ainsi que les périphériques. Le microcontrôleur peut être réveillé à l’aide d’une interruption ;
- Le stop mode qui arrête les quartz haute fréquence et la plupart des périphériques. La seule possibilité pour sortir le microcontrôleur de ce mode est qu’il reçoive une interruption externe (broches dédiées) ;
- Le standby mode qui a les mêmes caractéristiques que le précédent, mais qui autorise le réveil à l’aide de la RTC(Real Time Clock). Ce mode est le plus économique mais présente le défaut de remettre le STM32 dans son état initial (perte des configurations des registres et remise à zéro de la mémoire), comme après une mise à GND de la broche RESET.
Nous allons nous intéresser plus spécialement au standby mode en reposant le réveil sur l’usage de l’horloge temps-réel (RTC). Pour simplifier cette présentation et comme ce n’est pas réellement un point critique à comprendre en termes de programmation nous allons faire usage de la libopencm3, seules les parties non disponibles dans celle-ci seront faites à la main.
L’exemple ci-dessous initialise l’horloge temps-réel pour s’incrémenter toutes les secondes (compteur à 0x7fff sur le quartz à 32768 Hz) et place le microcontrôleur en mode de veille profonde tel que décrit dans la section 5.3 de [RM0008, p.70]. Lors de chaque interruption, l’alarme est incrémentée de 10 s.
Afin d’être en mesure d’utiliser la RTC, il est nécessaire d’activer à la fois le backup domain et le power control. Ensuite la RTC elle-même est configurée pour être cadencée par le quartz basse fréquence externe et pour incrémenter le compteur toutes les secondes. Il ne reste plus ensuite qu’à activer globalement les interruptions pour la RTC et spécifier que l’événement “alarme” ( valeur configurée du compteur atteinte) doit déclencher une interruption.
La fonction rtc_isr est le gestionnaire d’interruptions pour la RTC. Le seul traitement réalisé étant d’acquitter l’interruption puis de réamorcer l’alarme pour un déclenchement “temps_mesure” après la date actuelle. Dans notre cas, ce traitement n’a pas réellement d’intérêt car le STM32 sera réinitialisé dès le déclenchement de cette interruption.
Cette fonction est la plus importante pour le passage en standby mode. Le registre SCB_SCR n’est pas documenté dans [RM0008] car il est relatif au cœur ARM CM3. le bit SCB_CR_SLEEPDEEP sert à déterminer si le processeur doit passer en sleep mode ou dans l’un des deux autres modes.
Le bit PWR_CR_PDDS spécifie que le STM32 va entrer en standby mode (bit à 1) ou en stop mode (bit à 0) lors du passage en mode deep sleep.
Le passage en mode basse consommation est ensuite validé par la mnémonique WFI.
Après configuration de la RTC, nous réalisons nos traitements. Quand ceux-ci sont finis, nous fixons le moment où la RTC va produire son interruption et nous rentrons dans la fonction qui passe le STM32 en mode basse consommation. La boucle infinie ne sera finalement jamais atteinte car à la sortie de WFI le STM32 sera réinitialisé. Les mesures de consommation (table 1) se font dans un premier temps avec un convertisseur série-USB FT232RL en mode veille (RESET# en position basse), avant que les résultats excessivement élevés nous obligent à effectuer une mesure finale avec le STM32 seul, en mode veille, sans aucun composant périphérique autre que les condensateurs de découplage et les quartz. Lors de la présence du FTDI, la liaison asynchrone entre le FTDI (TX et RX) et le STM32 se fait au travers de résistances de 36 kΩ.
Dans cet exemple nous ne faisons rien de particulier, mais il peut sembler parfois laborieux voire consommateur en ressources de devoir, pour chaque réveil, faire toute une série d’initialisation du fait de la réinitialisation de la mémoire volatile. Pour palier à ce problème, le STM32 dispose de 16 registres de 16 bits (BKP_DRxx) qui ne seront pas réinitialisés lors de la mise en veille, ils peuvent donc permettre d’éviter certains traitements pouvant être faits une fois et réutilisés ensuite.
À titre indicatif, la fonction RTC_GetTime() propose une lecture du contenu du registre de l’horloge temps-réel en vue d’une datation des trames acquises.
Le STM32 n’a pas vocation à être exploité dans les applications à très basse consommation. Néanmoins, mentionnons que la série de processeurs Tiny Gecko de Energy Micro9 annonce des consommations aussi basses que 900 nA en mode veille et 20 nA lorsque désactivés. Un port de la bibliothèque libopencm3 pour ces processeurs semble être amorcé.
10. Stockage sur carte SD au format FAT : exploitation de EFSL
Nous avions déjà mentionné auparavant les perspectives spectaculaires offertes, pour les systèmes embarqués par la capacité à stocker des informations sur support non-volatil de type Secure Digital (SD), et en particulier la capacité à organiser les données selon des fichiers exploitables par tout utilisateur grâce au format FAT [LM117]. Nous avions proposé la bibliothèque EFSL qui, de par son excellente structuration, ne nécessite que le portage de quelques fonctions bas niveau pour être utilisable sur une nouvelle architecture. Nous avons donc modifié EFSL pour une utilisation sur STM32 avec bibliothèque libopencm3. Nous n’exploitons cependant pas les fonctionnalités avancées de cette architecture telles que la capacité à transférer les données par DMA, optimisation qui mériterait d'être implémentée pour réduire l’impact du stockage sur l’efficacité du programme principal.
Dans l’arborescence d’EFSL, l’unique fichier contenant la description des accès bas-niveau entre le microcontrôleur et la carte SD communiquant au travers du protocole SPI se trouve dans efsl/source/interface/efsl_spi.c. Dans ce fichier, nous redéfinissons, tel que vu auparavant, la direction et la fonction des broches associées à SPI1 (port A) au niveau de SPI_Config(), ainsi que les macros définissant le niveau du signal d’activation de la carte (GPIOA 4 dans notre exemple).
Les fonctions de communication sur bus SPI sont déjà encapsulées dans libopencm3 sous forme de spi_send(SPI1, outgoing); et incoming = spi_read(SPI1);. Rapidement, nous avons ainsi la satisfaction d’accéder à un fichier dans un répertoire et d’y stocker des informations dans un format accessible sur tout ordinateur personnel.
Nous testons dans cet exemple si le fichier de sauvegarde existe déjà en tentant d’y ajouter les informations (mode ’a’). En cas d’échec, le fichier est créé (mode w). Si cette création échoue, il y a eu disparition de la carte depuis son initialisation (ouvre_sd()) et nous la marquons comme absente, sinon le contenu du tableau de caractères b est transféré sur la carte. Notez que les informations ne sont physiquement écrites que lors du démontage du système de fichiers fs_umount().
Ainsi, une application naturelle qui découle de l’utilisation de l’horloge temps-réel (vue dans la section précédente) et du stockage au format FAT sur carte SD est l’obtention d’un enregistreur autonome capable de stocker des informations d’un instrument réveillé de façon périodique, et ce en l’absence d’intervention des utilisateurs pendant un intervalle de temps défini par l’autonomie des batteries alimentant le circuit. En ce sens, nous avons constaté que toutes les cartes SD ne se valent pas, et tandis que certains modèles passent automatiquement en mode veille une fois la phase d’écriture achevée, les modèles les moins chers continuent à drainer un courant important même en l’absence de toute opération. La façon la plus sûre de réduire la consommation est donc d’alimenter la carte SD au travers d’un régulateur DC-DC avec désactivation (shutdown) ou d’un interrupteur analogique.
Conclusion
Cet ensemble de mises en œuvre nous a permis de nous faire une bonne idée de la manière d’exploiter le STM32 et de configurer les périphériques. Il est ainsi possible de constater que pour un périphérique donné une partie est totalement liée à celui-ci (activation de l’horloge pour le périphérique et pour le port, configuration des broches, etc...). Une seconde partie de la configuration/utilisation se base sur des registres indexés par l’adresse du périphérique. Cette partie pourra donc être facilement incluse dans un module utilisable par tous les périphériques d’un même type. Nous n’avons pas détaillés dans le présent article l’usage du DMA, ceci est toutefois fait dans l’article "Traitement du signal sur système embarqué -- application au RADAR à onde continue".
La compréhension de la structure du STM32 est l’étape inévitable pour être en mesure de réaliser le portage d’un exécutif tel que TinyOS sur ce microcontrôleur, ainsi que nous le verrons dans un prochain article.
Il est à noter que les STM32 disposent selon le modèle d’un USB device, voire OTG sur les hauts de gamme. La libopencm3 ne propose à l’heure actuelle que l’implémentation pour le mode device, la partie OTG étant à faire. Des exemples de port-séries virtuels (entre autres) sont fournis avec la libopencm3.
Références
[CortexM3] J. Yiu, The Definitive Guide to the ARM Cortex-M3, 2nd Ed., Newnes (2009)
[RM0008] RM0008, Reference manual rev. 13, Mai 2011, disponible à http://www.st.com/internet/com/TECHNICAL_RESOURCES/TECHNICAL_LITERATURE/REFERENCE_MANUAL/CD00171190.pdf
[morse] Page wikipedia concernant l’alphabet morse http://fr.wikipedia.org/wiki/Alphabet_morse
[MS5534] Datasheet du MS5534C (remplaçant du MS5534A) http://www.meas-spec.com/WorkArea/linkit.aspx?LinkIdentifier=id&ItemID=6673
[AN510] Utilisation du MS5534A en SPI http://www.meas-spec.com/downloads/Using_SPI_Protocol_with_Pressure_Sensor_Modules.pdf
[HH10D] Datasheet du capteur d’humidité HH10D http://www.hoperf.com/upload/sensor/HH10D.pdf
[TEMT6000] Datasheet du capteur de lumière TEMT6000 http://www.vishay.com/docs/81579/temt6000.pdf
[MSP430] J.-M. Friedt, A. Masse, F. Bassignot, Les microcontrôleurs MSP430 pour les applications faibles consommations – asservissement d’un oscillateur sur le GPS, GNU/Linux Magazine France 98, Octobre 2007, disponible à http://jmfriedt.free.fr
[LM117] J.-M. Friedt & É. Carry, Développement sur processeur à base de cœur ARM7 sous GNU/Linux GNU/LINUX Magazine France 117, juin 2009, pp.32-51, disponible à http://jmfriedt.free.fr
1 http://www.olimex.com/dev/stm32-h103.html
2 à la fin, on revient toujours aux fondamentaux, mais l’approche est un peu rude en premier abord
3 patchs disponibles sur la page http://www.trabucayre.com/page-ftdi.html
4 dans /usr/share/openocd/scripts et ses sous répertoires (interface pour les sondes, board pour les cartes).
5 http://www.trabucayre.com/lm/lm\_stm32\_examples.tgz
6 http://dangerousprototypes.com/open-logic-sniffer/
7 http://wikipedia.org/wiki/I2S
8 cette information est seulement donnée sur la [RM0008, fig.8 p.90]
9 http://www.energymicro.com/news-archive/energy-micro-launches-efm32-tiny-gecko-microcontrollers