AVR USART avec un Arduino UNO

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 :

Screenshot_7

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:

Screenshot_6

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.

Screenshot_8

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).

Screenshot_9

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);
  }
}