Outils pour utilisateurs

Outils du site


tuto_avr_eclipse_librairie_de_communication

[TUTO] [AVR] [Eclipse] Librairie de Communication Série


Introduction

Dans ce tutoriel, je vous propose de commencer une librairie de communication série asynchrone. Cette librairie sera ensuite utilisée pour communiquer plutôt avec un PC ; la communication inter-Arduino sera plutôt couverte par un autre tutoriel.

Il reste toutefois possible de faire communiquer des Arduinos ensemble via un liaison série asynchrone; cependant, nos chers microcontrôleurs adorés n'ont qu'une seule USART1) ; or, si on l'emploie pour une communication inter-Arduino, elle n'est plus disponible pour la communication avec le PC. Il est a noté que l'USART est utilisée par le bootloader lors du téléversement du programme ; notre Arduino serait par conséquent moins facilement re-programmable. Dans les faits, cela dépendrait de l'interface Electrique.

Pour la communication inter-Arduino, on peut préférer l'interface SPI2) qui offre de nombreux avantages, parmi lesquels, la sélection de manière hardware du destinataire, et des vitesse de transmissions élevés (La fréquence de cadencement max est de Fosc/2, soit 8MHz dans notre cas, ce qui nous ferait du 8Mbps théorique). Par contre cela impose une architecture Maître/Esclave(s), et l'utilisation à minima de 4 ligne d'E/S (MISO/MOSI/CLK/SS) en tant qu'esclave et Plus en tant que Maître, puisqu'il faudra prévoir la logique nécessaire pour la sélection de l'esclave.

Revenons a nos moutons et commençons.

Mes sources, si vous voulez les télécharger pour les étudier ou vous y référer, elles sont là :


Creation du Projet

Pour ma part, le projet, je l'ai nommé AVR_USART_lib ; toujours la même convention de codage. Et toujours pareil aussi, j'ai commencé par la creation de mon repository, clonage, etc… Se référer aux tutoriels précédents pour plus de precisions a ce sujet3).

[ACTION] Créer un nouveau projet C/C++ de type AVR Cross Target Static Library nommé AVR_USART_lib, pour un ATmega328P cadencé à 16MHz

La seule chose a faire, est eventuellement renomer la configuration Debug en un nom, plus representatif comme ATmega328P_16MHz par exemple, et demander l'optimsation en taille.

[ACTION] Cliquer sur le menu Project > Build Configurations > > Manage…
[ACTION] Supprimer la configuration Release
[ACTION] Renommer la configuration Debug en ATmega328P_16MHz

[ACTION] Afficher les propriétés du projet : click-droit > Properties
[ACTION] Dans le panneau de droite, sélectionner C/C++ Build > Settings

[ACTION] Dans AVR Compiler > Optimizations, sélectionner Size Optimizations (-Os) pour Optimitzation Level

Répéter l'opération pour AVR C++ Compiler :

[ACTION] Dans AVR C++ Compiler > Optimizations, sélectionner Size Optimizations (-Os) pour Optimitzation Level

[ACTION] Appliquer les changements et valider

Il convient ensuite d'ajouter des fichiers sources. Nous allons en ajouter deux, conformement à notre convention de nommage, les voici :

  • AVR_USART_lib.h : Ce fichier est un fichier d'en-tete C, qui est inclu par chacun des programmes faisant appel a la librairie. Il ne contient que la définition des fonctions et choses utiles à connaitre pour utiliser la librairie. C'est la partie émergée de l'iceberg, celle qu'on voit, equivalent au socle d'une prise electrique. Peut importe ce qu'il y a dans le mur, tant qu'on peut y brancher des appareils dessus.
  • AVR_USART_lib.c : ce fichier quant à lui contient toute la mécanique interne de la librairie. Il contient l'implementation des fonctions, procédures et autres choses dont le programmeur n'a pas forcément besoin de connaitre le détail. Pour reprendre les analogies précédentes, c'est la partie immergée de l'iceberg, celle qu'on ne voit pas, équivalent a ce qui est câblé dans la prise électrique : fil noir ou marron pour la phase à droite, fil bleu pour le neutre a gauche, fil vert/jaune pour la liaison équipotentielle 4).
Tout ça pour introduire un concept en programmation qui est l'encapsulation.

Afin que ce tutoriel ne soit pas trop indigeste, nous allons faire un paragraphe pour chaque fichier, OK ?


Le fichier d'en-tête AVR_USART_lib.h

Le fichier d'en-tête AVR_USART_lib.h ne contient pas spécialement de difficultés. Vous noterez le formatage particulier des commentaires : il s'agit de d'un formatage à la doxygen. Doxygen est un outil de génération de documentation assez utilisé, et assez pratique. Ce que j'apprécie dans doxygen, c'est le fait qu'il impose de structurer les commentaires et met tout le monde d'accord sur le contenu et sur la forme.

Nous ne revenons pas sur la directive #include <avr/io.h>. La directive #include <stdio.h> permet la définition du type FILE utilisé dans la déclaration de la fonction USART_putchar_printf(…).

Les fonctions suivantes sont ensuite definies :

  • USART_Init(…) permet l'initialisation de l'USART. Elle prend en parametres la vitesse de transmission souhaitée. Il est à noter que la vitesse maximale est de 57600; le paramètre étant un entier non-signé sur 16-bit (uint16_t), la valeur maximale est de 65535. Les vitesses de 115200 ou 230400 restent peut-etre possibles à condition de faire évoluer la librairie en ce sens. Je ne l'ai pas fait, car pour le moment, je n'en avais pas besoin. On verra lorsque l'on regardera le détail de cette fonction qu'il y a un petit calcul a faire afin de trouver le bon baudrate, j'espere pouvoir montrer que les types employés ont leur importance.
  • USART_Transmit(…) permet la transmission d'un caractère sur la liaison série.
  • USART_putchar_printf(…) permet la redirection de la sortie standard vers la liaison série. Cela nous sera utilie pour utiliser la fonction printf qui peut s'averer tres pratique pour “afficher” des nombres.
AVR_USART_lib.h
/*
 * AVR_USART_lib.h
 *
 *  Created on: Oct 11, 2013
 *      Author: lhotse
 */
 
 
 
#ifndef AVR_USART_LIB_H
#define AVR_USART_LIB_H
 
 
 
#include <avr/io.h>
 
#include <stdio.h>
 
 
/*! \brief USART Initialization
 *  \details This function will initialize the USART using selected baudrate.
 *  		 User must pay attention to select one of the usually used Baud Rate (9600, 19200, 38400, 57600)
 *  		 Note that since an uint16 is used as argument, Baud rate cannot be more than 57600
 *  \param [in] uint16_t ui16BaudRate : Desired Baud Rate (16 bit) - up to 57600
 *  \return Nothing.
 */
void USART_Init( uint16_t ui16BaudRate ) ;
 
 
 
/*! \brief USART Transmit data
 *  \details Nothing Special. It just wait for the transmit buffer is empty before writing it again.
 *  \param [in] uint8_t ui8Data : Desired data char to transmit
 *  \param None.
 *  \return Nothing.
 */
void USART_Transmit( uint8_t ui8Data ) ;
 
 
 
/*! \brief USART printf handler
 *  \details Just write data to the Serial link
 *  \param [in] char var : char being printed
 *  \param [in] FILE *stream : pointer to the stream being printed ?
 *  \param None.
 *  \return Always 0.
 */
int USART_putchar_printf( char var, FILE *stream ) ;
 
 
 
#endif /* AVR_USART_LIB_H */

Le fichier source AVR_USART_lib.c

Le fichier AVR_USART_lib.c quant à lui, est un poil plus compliqué. Mais ça va encore. Comme dit précédemment, il a pour rôle de fournir l'implémentation des fonction déclarées dans le fichier d'en-tête5).

Alors, un truc marrrant, c'est qu'un fichier tel que celui là peut tout a fait définir et implémenter des fonctions qui ne figurent pas dans le fichier d'en-tête ; la seule chose, c'est qu'elles ne seront pas utilisables a l’extérieur de la librairie ; on peut parler de fonctions privées par opposition a celle définies dans le fichier d'en-tête qui elles seront plutôt qualifiées de publiques. Le seul impératif est que toutes les fonctions du fichier d'en-tête DOIVENT avoir une implémentation ; mais on s’égare…

Bref, ça commence par l'inclusion du fichier d'en-tête #include “AVR_USART_lib.h”. Ici, c'est pour bénéficier de l'inclusion e avr/io.h et stdio.h principalement.

Sautons directement a la fonction USART_Init(…). Cette fonction va initialiser notre USART avec les paretres suivants :

  • Vitesse de transmission/Baud rate6) paramétrable,
  • 8 bits de données,
  • 2 bits de stop.

Pour ne rien vous cacher ce morceau de code provient de la datasheet ($20.5). Le calcul du Baud Rate est lui aussi donné dans la Datasheet (§20.3 - Table 20.1). L'important dans l'affaire est de calculer la valeur à écrire dans le registre UBRR. Il s'agit d'un registre 16-bit. Cependant si l'on code la formule “bêtement”, on s'expose a quelques dépassement de capacité (Overflow) pouvant donner un résultat inexact. Le compilateur devrait cependant s'en apercevoir et renvoyer erreurs et warnings adéquats.

Afin de prévenir ces dépassements, il convient de travailler dans des registres plus grand. Il faut garder à l'esprit que notre microcontrôleur est construit autour d'un microprocesseur 8-bit. Cela implique qu'il va être très fort a faire des opérations sur des registres 8-bits, et beaucoup moins fort a faire des opérations sur des types plus longs. Et sans parler des flottants qui deviennent très gourmands en temps processeur lorsqu'il n'y a pas de FPU7) (mais pour ça, on a une autre technique : le Fixed-point Arthmetic8), j’espère qu'on y reviendra un de ces quatre, c'est des problèmes super intéressants !). Trêves de bavardages…

Le calcul faisant intervenir F_CPU (“valant” 16000000) cela ne tient pas dans une variable 8-bit (Max = 28-1=255), 16-bit non plus (Max = 232-1 = 65535); mais dans une variable 32-bit (max = 232 - 1 = 4 294 967 295). Ça rentre largement ! D'autant plus qu'on va le diviser par 16. Pour gagner en temps de cycles, il a été choisit de décaler a droite de 4 bit ; un décalage à droite ou a gauche est plus efficace qu'une division. Notre processeur travaille en base 2. aussi, toute division par des puissances de 2 peut être faite en décalant a droite du nombre de bit de la puissance. la réciproque est vrai avec la multiplication ou l'on décalera a gauche du nombre de bit de la puissance. Ça ramène la valeur à 1 000 000. ce qui est encore trop grand pour un entier sur 8-bit ou même 16-bit. Il est ensuite divisé par le Baud Rate souhaité (dans le pire des cas : 9600). ça nous donne un résultat maximal de 104. est ensuite retranché 1.

uint32_t ui32UBRR = F_CPU ;
 
ui32UBRR >>= 4 ; // (=) to ui32UBRR /= 16 but more efficient
ui32UBRR /= ui16BaudRate ;
ui32UBRR -= 1 ;

L’étape d’après est l'affectation du registre UBRR. On notera le décalage a droite de 8 bit de la variable ui32UBRR avant masquage des bits de point fort de manière a conserver uniquement les 8 bits de poids les plus faibles. On notera aussi la syntaxe particulière du cast explicite en uint8_t, ce pour pour faire “taire” le compilateur sur un éventuelle perte de données : c'est voulu !

UBRR0H = (uint8_t) ( (ui32UBRR>>8) & (0x000000FF) ) ;
UBRR0L = (uint8_t) ( ui32UBRR & (0x000000FF) ) ;

La suite ne comporte pas de points particuliers. Il s'agit de l'affectation des différents registres de contrôle de l'USART. Il est a noté que la redirection de la sortie standard est faite de facto. ça pourrait être paramétrable. On pourrait considérer que l'on a un afficheur LCD qui puisse aussi jouer le rôle de la sortie standard ; c'est a voir…

/* Enable receiver and transmitter */
UCSR0B = (1<<RXEN0) | (1<<TXEN0) ;
 
/* Set frame format: 8data, 2stop bit */
UCSR0C = (1<<USBS0) | (3<<UCSZ00) ;
 
stdout = &m_stdout ;

Vient ensuite la fonction USART_Transmit(…). Cette fonction ne s'occupe que d'attendre que le caractère précédent ait été transmit, puis écrit un nouveau caractère a transmettre.

/* Wait for empty transmit buffer */
while ( !( UCSR0A & (1<<UDRE0)) ) ;
 
/* Put data into buffer, sends the data */
UDR0 = ui8Data ;

La fonction USART_putchar_printf(…) quant à elle est appelé par la fonction printf lors de la sortie de cratères vers la sortie standard. Elle ne s'occupe que de transmettre les caractères qu'on lui envoie vers l'USART. Petit détail, elle rajoute le caractere '\r'9) devant le caratere '\n'10) à chaque fois qu'elle rencontre un '\n'. Ceci a des fin d'affichage dans nos terminaux serie, sur nos PCs.

if ( var == '\n' )
{
	USART_Transmit('\r') ;
}
 
 
USART_Transmit( (uint8_t) var ) ;
 
return 0 ;

Au final, notre fichier source n'est pas très long ni si compliqué. On notera qu'il n'y a pas de fonction pour le traitement de la réception de caractères. Je n'en ai pas encore eu besoin, mais ça ne saurait tarder. Ça ne sera pas dans le prochain tutoriel, car celui ci s'occupera de jouer avec les ADCs. Il nous servira aussi comme projet utilisant cette librairie pour envoyer le résultat de la lecture vers un terminal.

AVR_USART_lib.c
/*
 * AVR_USART_lib.cpp
 *
 *  Created on: Oct 11, 2013
 *      Author: lhotse
 */
 
 
 
#include "AVR_USART_lib.h"
 
 
 
static FILE m_stdout = FDEV_SETUP_STREAM( USART_putchar_printf, NULL, _FDEV_SETUP_WRITE ) ;
 
 
 
void USART_Init( uint16_t ui16BaudRate )
{
	uint32_t ui32UBRR = F_CPU ;
 
	ui32UBRR >>= 4 ; // (=) to ui32UBRR /= 16 but more efficient
	ui32UBRR /= ui16BaudRate ;
	ui32UBRR -= 1 ;
 
	/* Set baud rate */
	UBRR0H = (uint8_t) ( (ui32UBRR>>8) & (0x000000FF) ) ;
	UBRR0L = (uint8_t) ( ui32UBRR & (0x000000FF) ) ;
 
	/* Enable receiver and transmitter */
	UCSR0B = (1<<RXEN0) | (1<<TXEN0) ;
 
	/* Set frame format: 8data, 2stop bit */
	UCSR0C = (1<<USBS0) | (3<<UCSZ00) ;
 
	stdout = &m_stdout ;
}
 
 
 
void USART_Transmit( uint8_t ui8Data )
{
	/* Wait for empty transmit buffer */
	while ( !( UCSR0A & (1<<UDRE0)) ) ;
 
	/* Put data into buffer, sends the data */
	UDR0 = ui8Data ;
}
 
 
 
int USART_putchar_printf( char var, FILE *stream )
{
	if ( var == '\n' )
	{
		USART_Transmit('\r') ;
	}
 
 
	USART_Transmit( (uint8_t) var ) ;
 
	return 0 ;
}

Remarques, Propositions d'améliorations, Questions

Vous pouvez poster vos remarques, propositions d’amélioration, et questions sur le forum, dans la discussion prévue a cet effet : http://fablab-robert-houdin.org/fablab/phpBB-3.0.11-fr/phpBB3/viewtopic.php?f=3&t=16


1) Universal Synchronous and Asynchronous serial Receiver and Transmitter
2) Serial Peripheral Interface
4) la terre
5) Excusez moi d'insister, mais il y a la quelques notions à intégrer
6) Attention, ce n'est pas la meme chose. Pour le moment on fera l'amalgame, mais c'est un abus de langage
7) Floating-Point Unit/Floating-point Processing Unit
9) Cariage Return
10) (Line Feed
tuto_avr_eclipse_librairie_de_communication.txt · Dernière modification: 2013/11/19 14:14 par spelle