Cet article explique comment utiliser le lien série asynchrone (lire USART) présent sur le Atmega328p du Arduino UNO.
Dans cet article, nous utiliserons une boucle pour la transmission et la réception des octets. Je ferai un autre article qui expliquera comment faire avec les interruptions, ce qui est recommandé afin de libérer le programme principal.
Étape 1 : Initialisation du USART
Il faut d’abord établir les paramètres du port série. Dans le cas présent, nous allons configurer le port pour une des configurations les plus communes.
Vitesse | 9600 bauds |
Longueur des données | 8 bits |
Parité | Aucune |
Nombre de bit STOP | 1 |
Les explications détaillées des registres utiles du Atmega328p sont disponibles dans la feuille de spécification suivante : ATMEGA328P
Le premier registre que nous allons configurer est en fait deux registres. C’est le UBRR qui est séparé en deux parties UBRR0L et UBRR0H. Ce registre configure la vitesse du port série. La valeur de UBRR dépend donc de l’horloge du microcontrôleur et la vitesse désirée. Dans notre cas, puisque nous utilisons le mode normal du USART. L’équation qui permet de déterminer UBRR est :
Où fosc est la fréquence d’opération du CPU et BAUD est la vitesse désirée.
Pour établir cet valeur, il est possible de calculer la valeur et d’utiliser un define ou vous pouvez utiliser une macro qui calculera automatiquement la valeur en fonction des paramètres. Dans le premier cas, avec nos paramètres le résultat serait 103 (le CPU du Arduino utilise un cristal externe de 16MHz). Cependant, je recommande plutôt la seconde manière, plus facile à maintenir. Voici le code de la macro en question :
#define BAUD_PRESCALE(fcpu,br) ((fcpu / 16 / br) - 1)
Les 8 bits les moins significatifs sont dans le registre UBBR0L et les 4 derniers bits dans UBBRoH. Voici la représentation des registres en question:
Pour UBRR0H, il faut simplement utiliser un décalage comme ceci :
UBRR0H = (BAUD_PRESCALE(F_CPU,baudRate) >> 8); UBRR0L = BAUD_PRESCALE(F_CPU,baudRate);
Un autre registre d’intérêt est le UCSR0C, qui permet de configurer le nombre de bits des données, le bit de polarité ainsi que le nombre de bits STOP.
UMSEL | Détermine le mode du port 00 est le mode asynchrone |
UPM | Détermine la parité 00 est aucune parité |
USBS | Détermine le nombre de bit STOP 0 est 1 bit |
UCSZ | Est la longueur des données 011 est 8bits. (valeur par défaut) Notez que UCSZ2 est dans un autre registre |
Voici donc le code qui permet de configurer le registre selon la configuration désirée :
UCSR0C = (1<<UCSZ00)|(1<<UCSZ01);
Le dernier registre à configurer sera le UCSR0B, qui active les éléments du port série. Il contient également le dernier bit pour la configuration de la longueur des données (UCSZ2).
RXCIE | Active l’interruption de fin de réception d’une donnée. Pas utilisé dans cet article. |
TXCIE | Active l’interruption de fin de transmission d’une donnée. Pas utilisé dans cet article. |
RXEN | Active la réception de donnée |
TXEN | Active la transmission de donnée |
Voici donc le code de la fonction uartInit au complet (F_CPU est un define utilisé par la librairie delay.h. Il vaut 16000000UL dans le cas présent) :
void usartInit(uint32_t baudRate) { UBRR0L = BAUD_PRESCALE(F_CPU,baudRate); UBRR0H = (BAUD_PRESCALE(F_CPU,baudRate) >> 8); UCSR0B = ((1<<TXEN0)|(1<<RXEN0); UCSR0C = (1<<UCSZ00)|(1<<UCSZ01); }
Étape 2 : Envoyer une donnée
Envoyer une donnée est simple, il suffit d’écrire dans le registre UDR0 et la transmission débute automatiquement si la transmission est activée dans le registre UCSR0B. Avant d’écrire, il faut cependant s’assurer que le registre est vide, avec le bit UDRE0 du registre UCSR0A. Voici à quoi peut ressembler la fonction de transmission d’une donnée.
void usartSendByte(uint8_t data) { while((UCSR0A & (1<<UDRE0)) == 0); UDR0 = data; }
Étape 3 : Réception d’une donnée
Pour lire une donnée reçue, il suffit de lire le registre UDR0. Cependant, pour s’assurer que le contenu est bien une donnée reçue, il faut vérifier que le bit RXC0 est activé dans le registre UCSR0A. Pour éviter que le programme se bloque, il est préférable d’utiliser une fonction qui valide la présence d’une donnée avant de lancer la lecture. Voici les deux fonctions recommandées pour cette étape :
bool usartDataAvailable() { return (UCSR0A & (1<<RXC0)); } uint8_t usartGetRxByte() { // Option pour attendre la réception d'une donnée... risque de blocage // while (!usartDataAvailable()); return UDR0; }
Finalement, il restera à vérifier que l’on peut recevoir et envoyer des données à l’aide des fonctions. Je vous recommande HTerm pour vérifier votre programme.
Voici un petit programme qui utilise les fonctions précédentes et valide le fonctionnement. Les fonctions sont mises dans un fichier usart.cpp et les déclarations de fonctions dans usart.h.
#include <avr/io.h> #ifndef F_CPU #define F_CPU 16000000UL #endif #include <util/delay.h> #include "usart.h" void main(void) { usartInit(19200); while(1) { if(usartDataAvailable()) data = usartGetRxByte(); usartSendByte(data); _delay_ms(1000); } }
A défaut de commentaire, cet article mérite au moins un remerciement, pour sa clarté.
Je reste un peu sur ma faim : comment et pourquoi régler la parité ? Le(s) stops ? L’article qui doit suivre, comment faire avec les interruptions, a-t-il été écrit ?
Quoiqu’il en soit, merci beaucoup Marc, ce fut un plaisir.
Bonjour. Désolé pour le temps de réponse j’ai été débordé avec une chaine youtube. Justement, j’ai une suite pour toi sur une des vidéos : https://youtu.be/ZdEVzz7TpLw et https://youtu.be/0aykJetzCpA
Tu y trouveras des exemples pratique sur AVR. Bon visionnement.