«

»

Jan 01

Ada et UART

Hola,

Aujourd’hui au programme, le début de la communication avec une stm32 ! Pour se faire, nous introduirons un module très utilisé : l’UART.

Mais avant tout, qu’est ce que l’UART ? UART (Universal Asynchronous Receiver Transmitter : Emetteur Récepteur Asynchrone Universel) est un composant permettant de transformer des données sous forme d’octets en une série de bits (0 ou 1). Pourquoi cela ? Dans la mémoire de notre carte, les données sont stockées en « parallèle » : lorsque l’on demande la valeur dans une case mémoire, on obtient un paquet de 8, 16, 32bits,… dépendant de la structure. Toutefois, lorsque l’on communique, nous utilisons souvent un fil unique : il faut donc sérialiser les données pour les transmettre les uns à la suite des autres. UART effectue cette conversion.

Il existe deux types d’UARTs : les synchrones (USART) et les asynchrones (UART).

Les communications synchrones impliquent que l’émetteur et le récepteur partagent une même horloge, appelée clock (clk). Une horloge est un composant émettant à intervalle régulier une impulsion. Grâce à cela, l’émetteur et le récepteur se mettent d’accord sur quand lire et écrire sur le fil. Toutefois, un fil supplémentaire est nécessaire pour communiquer. Par la suite, nous n’utiliserons pas ce mode de communication.

Les communications asynchrones n’ont pas besoin d’horloge commune : les deux entités se mettent d’accord sur une vitesse de transmission (Baudrate) avant la communication. L’émetteur commence la communication avec un bit spécial appelé Start Bit. Il permet au récepteur de commencer à écouter la conversation. Les données sont ensuite envoyées en commançant par les bits de poids faible. S’en suit un bit de parité qui permet de vérifier de manière rapide si une erreur a eu lieu. Enfin, un bit de fin de transmission (Stop Bit) est envoyé pour conclure la communication.

Passons maintenant à l’implémentation pratique en Ada. Le package à importer est Stm32.UART.

Pour l’UART comme pour les GPIOS, il faut définir les paramètres qui vont permettre de configurer l’UART. Pour cela nous allons utiliser le type UART_Params. Il est composé de nombreux arguments :

  • Baud_Rate : le baudrate est la vitesse à laquelle les composants se sont entendus pour communiquer. Il s’agit d’un Unsigned_32 présent dans le package Interfaces. Pour rappel, on peut caster un entier comme suit : Unsigned_32(entier)
  • Word_Length : Il s’agit de la longueur des mots qui vont être transmis, c’est à dire le nombre de bits lus ou reçus d’un coup. Nous avons le choix entre Length_8 et Length_9 suivant que l’on choisi de transmettre 8 ou 9 bits.
  • Stop_Bit : il s’agit de la longueur du bit signalant la fin de la transmission. Nous avons le choix entre Stop_1, Stop_0_5, Stop_2 et Stop_1_5 selon qu’il dure 1, 0.5, 2 et 1,5 période(s).
  • Parity : indique s’il y a un bit de parité et si oui, il signale un nombre pair ou impair. Le bit de parité indique si le nombre de 1 est pair ou impair ce qui permet rapidement de détecter certaines erreurs. Nous avons le choix entre : No_Parity, Odd_Parity et Even_Parity selon qu’il n’y a pas de bit de parité, que l’on indique que le nombre de impaire de 1 ou un nombre pair de 1.
  • Mode : indique le mode de fonctionnement de l’UART. Nous pouvons choisir entre Mode_None, Mode_Rx, Mode_Tx et Mode_Both selon qu’on ne fasse rien, que l’on reçoive selon, que l’on transmette uniquement ou que l’on fasse les deux.
  • Flow_Control : il s’agit d’un option permettant le contrôle des flux de transmission. Si par exemple le récepteur est plus lent que l’émetteur, le système adaptera sa vitesse tout seul. Il existe plusieurs choix. None que l’on ne souhaite pas activer cette option. Dans le mode RTS (Ready To Send : prêt à envoyer), l’émetteur indique au récepteur qu’il est prêt à envoyer alors que dans CTS, c’est le récepteur qui indique quand il est prêt à recevoir. RTS_CTS est la combinaison des deux options précédentes.

 

L’étape suivant consiste à paramétrer les pins de notre stm32 pour les rendre aptes à recevoir l’UART. Pour cela, nous disposons de la fonction préconçue suivante :

;procedure Configure_Pins (UART : UART_Number; Tx : Pin_Type; Rx : Pin_Type);;

UART_Number est un nombre compris entre 1 et 6 et représente le numéro du module UART considéré. Dans cette fonction se cache des procédures que nous avons utilisé dans le cours sur les GPIOs. Les pins sont notamment en mode Alternate pour pouvoir utilisé l’UART.

Maintenant que nous disposons des paramètres et que nos pins sont réglées, nous allons pouvoir configurer le module UART. Pour cela, nous allons utiliser la fonction suivante, qui permet d’initialiser l’UART :

procedure UART_Init (UART : UART_Number;
Params : UART_Params);
;

 

Rien de plus compliqué 🙂

Une fois l’UART initialisé, il est possible d’aller observer ses flags et d’en déduire son état. Les flags sont des étiquettes sur lesquelles le module UART vient écrire des informations le concernant et qui peuvent intéresser l’utilisateur. Nous allons faire cela via :

;function Get_Flag_Status (UART : UART_Number;
Flag : UART_Flag_Type) return Boolean;
;

Nous obtenons en sortie l’état du flag passé en paramètre. Pour ceux là, nous avons le choix dans la liste suivante :

  • CTS: Un flag indiquant un changement du CTS.
  • LBD: LIN Break detection flag.
  • TXE: Un flag indiquant de le registre contenant les données à envoyer est vide.
  • TC: Un flag que la transmission est finie.
  • RXNE: Un  flag indiquant que des valeurs ont été reçues et sont dans les registres.
  • IDLE: Idle Line detection flag.
  • ORE: OverRun Error flag.
  • NE: Noise Error flag.
  • FE: Framing Error flag.
  • PE: Parity Error flag.

Comme nous pouvons aller lire ces valeurs, nous pouvons aussi les effacer grâce à la fonction :

procedure Clear_Flag (UART : UART_Number;
Flag : UART_Flag_Type);

Il faut savoir que PE (Parity error), FE (Framing error), NE (Noise error), ORE (OverRun error) et IDLE sont effacées automatiquement si l’on fait un Get_Flag_Status, suivi de Receive_Data (voir plus loin). Pour RXNE, il suffit de faire Receive_Data. Pour TC, il faut faire Get_Flag_Status suivi de Send_Data (voir plus bas). Enfin pour TXE, il faut faire un Send_Data.

Maintenant que nous savons tout cela, il est temps d’apprendre à envoyer et recevoir des données. Nous aurons besoin de deux fonctions :

procedure Send_Data (UART : UART_Number;
Data : Unsigned_16);

function Receive_Data (UART : UART_Number) return Unsigned_16;;

A noter que les mots à transmettre doivent se trouver sur 16 bits. On peut facilement les convertir en caractère en utilisant : Character’Val(numéro).

Un problème se pose alors : comment peut-on être en permanence en train d’écouter les conversations ? Nous pourrions faire une boucle autour de Receive_Data et mettre le tout dans une tâche histoire de ne pas bloquer l’exécution. Seulement, Receive_Data renvoie la dernière valeur reçue. Comment alors éviter les répétitions? Il faudrait lire en permanance RXNE : cela demande BEAUCOUP trop de calculs. Nous nous retrouvons dans une impasse qui va nous obliger à découvrir un nouvel outil appelé les interruptions.

UART et les interruptions

Avant de poursuivre, il est conseillé de lire le cours sur les interruptions pour apprendre à configurer le traitement des interruptions. Vous le trouverez Ici.
Pour configurer une interruption UART, rien de plus simple. Il faut utiliser la fonction :

procedure Configure_Interrupt (UART : UART_Number;
Flag : UART_Flag_Type;
State : FunctionalState);

UART_Number correspond au numéro de l’UART utilisé (c.f. cours sur l’UART). Flag correspond à l’étiquette sur laquelle l’interruption va se fier pour se déclencher. Par exemple, si l’on met RXNE, une interruption va se déclencher à chaque réception de données. State indique si l’on veut activer (Enable) ou désactiver (Disable) l’interruption.

Nous disposons d’une fonction qui est le pendant de Get_Flag_Status permettant de vérifier si une interruption a été lancée ou pas. Cela peut notamment servir lorsque la même fonction est appelée pour plusieurs interruptions différents.

function Get_Interrupt_Flag_Status (UART : UART_Number;
Flag : UART_Flag_Type) return Boolean;

Bien entendu, il existe aussi le pendant de Clear_Flag :

procedure Clear_Interrupt_Flag (UART : UART_Number;
Flag : UART_Flag_Type);

La suite au prochain numéro !

Julien