Bare-Metal STM32 : Configuration et utilisation de SPI

L’interface Serial Peripheral Interface (SPI) a été initialement normalisée par Motorola en 1979 pour la communication à courte distance dans les systèmes embarqués. Dans sa configuration à quatre fils la plus courante, le transfert de données en duplex intégral est possible sur les deux lignes de données (MOSI, MISO) avec des débits dépassant largement 10 Mb/s. Cela rend SPI adapté aux applications à large bande passante en duplex intégral telles que les cartes de stockage SD et les écrans à grande résolution et à rafraîchissement élevé.

Les appareils STM32 sont livrés avec un nombre variable de périphériques SPI, deux dans le F042 à 18 Mb/s et cinq dans le F411. Dans toutes les familles STM32, le périphérique SPI est relativement similaire, avec des différences assez mineures dans la disposition des registres. Dans cet article, nous verrons comment configurer un périphérique SPI en mode maître.

Définir le SPI

Un fait intéressant et peut-être ennuyeux avec SPI est que bien qu’il puisse prendre en charge plusieurs appareils, il n’a pas de bus d’adressage, mais nécessite plutôt qu’une broche désignée soit tirée vers le bas sur l’appareil, généralement appelée sélection d’esclave (SS) ou sélection de puce ( CS). Avec SS haut, le périphérique esclave met ses autres broches en mode haute impédance, se déconnectant efficacement des lignes SPI. Les périphériques STM32 SPI ont une disposition pour une broche SS dédiée (NSS) qui peut rationaliser ce processus si un seul appareil est connecté. Généralement, vous souhaitez utiliser des broches GPIO pour basculer ces broches SS, avec une broche GPIO par appareil.

Pour le SPI à quatre fils, les appareils maître et esclave sont ainsi connectés avec les lignes suivantes, avec la ligne SS dupliquée pour chaque esclave supplémentaire :

  • SCLK (horloge série, du maître)
  • MOSI (sortie maître, entrée esclave)
  • MISO (entrée maître, sortie esclave)
  • SS (sélection esclave)
Chronogramme SPI, montrant les différentes configurations CPHA et CPOL.
Chronogramme SPI, montrant les différentes configurations CPHA et CPOL.

La configuration du périphérique SPI est relativement simple, nécessitant la configuration de l’horloge et des paramètres tels que les transferts 8 ou 16 bits. Moins évidents sont les paramètres de polarité d’horloge SPI (CPOL) et de phase (CPHA). Ici, la valeur par défaut (mode 0) est généralement CPOL 0 et CPHA 0, ce qui se traduit par une ligne d’horloge au ralenti et de nouvelles données poussées sur la ligne de données sur le front arrière du cycle d’horloge actuel. CPOL 1 et CPHA 1 entraînent le comportement opposé. Les esclaves peuvent prendre en charge d’autres modes que le mode 0, mais la fiche technique de chaque esclave doit être consultée au cas par cas.

Avec tout cela à l’esprit, nous pouvons envisager de configurer SPI sur les microcontrôleurs F411 et F042. En raison de la similitude susmentionnée entre les périphériques SPI des familles STM32, il est relativement simple d’adapter la routine d’initialisation. Les routines de transfert de données elles-mêmes restent inchangées.

Mettre les choses en place

La configuration d’un maître SPI commence par la configuration des broches GPIO que nous utiliserons. Cela implique de définir le mode de fonction alternative (AF) et les paramètres de broche appropriés, par exemple AF5 sur les broches 4 à 6 du MCU F411 sur le port A. Les broches SPI elles-mêmes se voient attribuer les propriétés suivantes :

  • SCLK : flottant, push-pull, grande vitesse.
  • MOSI : flottant, push-pull, grande vitesse.
  • MISO : pull-up, push-pull, grande vitesse.
  • SS : pull-up, push-pull, grande vitesse.

Étant donné que SPI repose sur une configuration push-pull plutôt que sur le drain ouvert d’I2C, nous devons régler toutes les broches pour qu’elles correspondent à cela, ainsi que l’option de vitesse GPIO rapide pour suivre la signalisation SPI. La décision de laisser une goupille flottante plutôt que d’activer son pull-up est déterminée principalement par la fonction de ces goupilles. Dans le cas d’une broche de sélection, il est essentiel de la maintenir dans un état haut afin d’éviter l’activation accidentelle d’un dispositif avant que le système ait fini de s’initialiser.

L’activation du pull-up de la broche MISO est effectuée pour maintenir cette ligne dans un état connu lorsqu’aucun périphérique n’est sélectionné et qu’aucun d’entre eux ne pilote donc la ligne MISO. Même si le maître ne lit pas le registre des données entrantes, les tensions intermédiaires peuvent potentiellement causer des problèmes comme une consommation électrique excessive.

Avec les broches GPIO ainsi configurées, le périphérique SPI cible est activé dans le registre d’activation RCC (Reset and Clock Control) correspondant. Par exemple, le périphérique SPI 1 est activé dans le RCC_APB2ENR alors que SPI 2 et SPI 3 se trouvent généralement sur le bus APB1 et donc activés dans le registre correspondant en RCC. La prochaine étape consiste à configurer le périphérique SPI lui-même.

Le premier élément à configurer ici est le diviseur d’horloge SPI (débit en bauds, BR) dans le SPI_CR1 S’inscrire. Cela utilise la fréquence APB (la fréquence du bus périphérique, ou fPCLK) comme entrée pour l’horloge SPI, qui peut être réglée entre fPCLK/2 et fPCLK/256 en utilisant trois bits de résolution. Le diviseur doit être choisi pour obtenir une horloge raisonnable et donc une vitesse de transfert pour l’application.

Alors que sur les familles F0 et F4, la taille de transfert par défaut est de 8 bits, le périphérique de ce dernier permet uniquement de définir le format de trame de données sur 8-16 bits dans le SPI_CR1 DFF (Format de trame de données). Avec le périphérique SPI du F0, la gamme d’options est beaucoup plus large lors de la configuration de sa valeur DS (taille des données) dans le SPI_CR2 S’inscrire. Il s’agit d’une valeur de 4 bits qui permet de configurer la taille des données entre 4 et 16 bits, avec par exemple 8 bits correspondant à b0111.

Sauf en cas d’exigences particulières, la taille de données 8 bits par défaut, la configuration Mode 0 par défaut et le paramètre MSB-first par défaut sont de bons choix par défaut qui devraient fonctionner avec la plupart des appareils SPI. Cela signifie alors que seul le diviseur d’horloge doit être configuré dans tous les cas, après quoi le mode maître peut être activé dans SPI_CR1 (MSTR). La broche SS peut ensuite être activée et définie comme sortie en définissant SSOE dans SPI_CR2.

Enfin, le périphérique SPI peut être activé en réglant SPE (Activation périphérique SPI) dans SPI_CR1.

Transferts de données

Bus SPI typique : maître et trois esclaves indépendants.  (Crédit : Cburnett)
Bus SPI typique : maître et trois esclaves indépendants. (Crédit : Cburnett)

Comme mentionné précédemment, SPI permet des transferts en duplex intégral. La complication que cela ajoute vient de la nature entièrement synchrone de SPI : pour chaque octet mis sur la ligne MOSI par le maître, l’esclave mettra un octet sur la ligne MISO, et vice versa. Étant donné que la ligne d’horloge est pilotée par des octets envoyés par le maître, le résultat est que pour recevoir des données d’un esclave, le maître doit mettre des données (par exemple des octets nuls) sur MOSI pour chaque octet sur MISO.

Un moyen de contourner ce problème consiste à changer le bus SPI d’une configuration à quatre fils à une configuration à trois fils (semi-duplex) à l’aide de BIDIMODE dans SPI_CR1, ce qui nécessite une reconfiguration fastidieuse du périphérique entre les transferts. Généralement, vous voudriez simplement mettre des octets nuls sur MOSI pour vous éviter ce problème.

Pour envoyer des octets à un esclave, nous suivons donc cette séquence après avoir tiré la ligne SS de la cible vers le bas :

  1. Attendre SPI_SR_TXE (registre d’état : registre de transmission vide) pour devenir vrai.
  2. Ecrire des données (8-16 bits) dans SPI_DR. Répétez à partir de (1) si plus de données doivent être écrites.
  3. Attendre SPI_SR_TXE redevenir vrai.
  4. Attendre SPI_SR_BSY (registre d’état : bus occupé) pour devenir faux.

La séquence se termine en tirant à nouveau SS vers le haut, tout en notant que certains esclaves SPI prennent en charge plusieurs écritures dans une seule séquence. Un piège dans cette séquence est lorsque nous écrivons des données dans SPI_DR c’est-à-dire <16 bits : même si nous écrivons un 8 bits uint8_t variable ou similaire à ce registre, il finira toujours par écrire 16 bits dans le registre, avec nos données plus ce rembourrage mis sur MOSI et ruinant le transfert de données. Pour contourner ce problème, nous devons convertir le registre SPI_DR à la taille souhaitée, par exemple pour un tableau de données 8 bits :

*((volatile uint8_t*) &(SPI1->DR)) = data[i];

Afin de recevoir d’un esclave, soit nous tirons SS bas, soit nous le laissons bas après une séquence de transmission précédente et suivons cette séquence :

  1. Attendre SPI_SR_BSY devenir faux.
  2. Ecrire des données factices (par exemple 0x00) dans SPI_DR pour générer un signal d’horloge.
  3. Attendre SPI_SR_RXNE (registre d’état : registre de données de réception non vide) pour devenir vrai.
  4. Lire les données de SPI_DR dans le tampon local. Revenez à (1) pour recevoir des données supplémentaires.
  5. Attendre SPI_SR_BSY devenir faux.

Ici aussi, la séquence se termine en tirant à nouveau SS vers le haut. Notez que l’écriture des données factices fait face au même piège que l’envoi de données. Assurez-vous que le SPI_DR register est converti de manière appropriée avant d’écrire les données. Quant à savoir pourquoi nous lisons et écrivons à la fois SPI_DR est parce qu’il s’agit d’un registre partagé, connecté aux FIFO TX et RX du périphérique SPI.

Enfin, afin d’effectuer une opération d’émission-réception en duplex intégral, nous pouvons combiner ces deux séquences, en soumettant des données au lieu d’octets factices tout en recevant simultanément des données d’un esclave. Il s’agit bien entendu d’une opération qui doit être supportée par le dispositif esclave en question. Pour de nombreux appareils et capteurs SPI courants, la plupart des opérations seront probablement effectuées de manière semi-duplex.

Emballer

Il y a encore beaucoup plus à SPI comme indiqué précédemment, bien que de nombreuses options de configuration soient plutôt obscures et rarement utilisées, comme LSB-first ainsi que les transferts 16 bits, le mode TI et les différents paramètres de phase et de polarité d’horloge. Un aspect plus couramment utilisé des périphériques SPI que nous aborderons dans un prochain article est le mode I2S que l’on trouve sur la plupart des microcontrôleurs STM32. Il s’agit d’une interface de connexion pour les codecs audio externes, souvent trouvés en mode secondaire sur les périphériques SPI.

SPI lui-même voit une utilisation significative avec des écrans et un stockage de données à plus grande résolution, mais de nombreux capteurs comme le BME280 de Bosch et les capteurs MEMS associés implémentent également une interface SPI en plus de celle I2C. Selon le système, placer certains de ces appareils sur SPI plutôt que sur I2C peut avoir beaucoup de sens en raison du routage ou d’autres contraintes.