CH32V003 : UART

Ce que nous allons faire - Prérequis

Nous allons voir dans ce cours :

  • ce qu'est un UART et à quoi il sert

  • la différence entre UART et USART

  • comment utiliser l'UART du CH32V003

Les prérequis de ce cours sont :

  • Avoir suivi le cours CH32V003 : GPIO et interruptions.

  • Avoir lu la page Wikipedia sur le UART afin d'avoir une idée générale du sujet. Vous n'avez pas besoin de chercher à tout comprendre, ça viendra naturellement avec les manipulations.

UART et USART

Un UART est un dispositif de communication série, c'est-à-dire dans laquelle les bits des données sont transférés un par un à la suite. Le protocole I2C, que nous avons déjà utilisé est un autre protocole de communication série, de même que le protocole SPI, que nous verrons très bientôt.

Avec un UART, l'émetteur et le destinataire doivent utiliser la même fréquence d'horloge pour la transmission, ou taux de transmissions ("baud rate" en anglais). La communication peut être bidirectionnelle en simultané ("full duplex" en anglais), c'est-à-dire que chaque extrémité de la ligne de transmission peut être simultanément émetteur et récepteur.

Un USART est capable de fonctionner de la même façon qu'un UART, mais offre un mode de fonctionnement supplémentaire dit "synchrone". Dans ce mode, le maître partage son horloge avec l'esclave, ce qui permet de garantir qu'émetteur et récepteur traitent les mêmes données en même temps et à la même vitesse. En mode synchrone, la communication est unidirectionnelle alternée ("half-duplex" en anglais), c'est-à-dire que le maître émet d'abord, puis signale à l'esclave qu'il peut répondre.

Le CH32V003 dispose d'un USART, mais nous ne l'utiliserons qu'en mode asynchrone (UART). Je n'ai pas connaissance d'exemples concrets où le mode synchrone serait utile - peut-être parce que SPI l'a supplanté depuis longtemps en étant à la fois plus simple et plus souple.

Vous trouverez une description complète de l'USART du CH32V003 au chapitre 12 du manuel de référence.

Utilisation de l'UART en mode polling

Nous avons déjà parlé du polling dans le cours CH32V003 : GPIO et interruptions et le même principe peut être appliqué à l'UART.

Nous allons pour cela écrire un petit programme qui convertir les caractères reçus en majuscules et les retourne à l'expéditeur. Le mode polling convient bien à ce cas d'utilisation car le rythme de frappe des caractères au clavier est extrêmement lent pour un micro-contrôleur cadencé à 48 MHz, donc il n'y a aucun risque de perte de données, ni d'impact sur le temps alloué au reste de l'application.

Créez un nouveau projet et modifiez system_ch32v00x.c comme vous en avez maintenant l'habitude, puis remplacez le contenu du fichier main.c par le code suivant :

#include <ch32v00x.h>

int main() {
	// == Configurer les lignes de GPIO
	GPIO_InitTypeDef gpioInit = { 0 };

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);

	// RX est sur PD6
	gpioInit.GPIO_Pin = GPIO_Pin_6;
	gpioInit.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOD, &gpioInit);

	// TX est sur PD5 et est une alternate function
	gpioInit.GPIO_Pin = GPIO_Pin_5;
	gpioInit.GPIO_Mode = GPIO_Mode_AF_PP;
	gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOD, &gpioInit);

	// == Configurer l'USART
	USART_InitTypeDef usartInit = { 0 };

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

	// C'est ici qu'on définit les paramètres de la liaison série :
	// 115200 Baud, 8 bits de données, 1 bit de stop, pas de parité,
	// pas de contrôle de flux de communication.
	usartInit.USART_BaudRate = 115200;
	usartInit.USART_WordLength = USART_WordLength_8b;
	usartInit.USART_StopBits = USART_StopBits_1;
	usartInit.USART_Parity = USART_Parity_No;
	usartInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	usartInit.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;
	USART_Init(USART1, &usartInit);

	USART_Cmd(USART1, ENABLE);

	while (1) {
		// Est-ce qu'un caractère est disponible en entrée ?
		if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET) {
			// Lire le caractère reçu.
			uint8_t c = USART_ReceiveData(USART1);

			// Si c'est une minuscule, le convertir en majuscule.
			if (c >= 'a' && c <= 'z') {
				c -= 'a' - 'A';
			}

			// Envoyer le caractère en écho sur la sortie.
			USART_SendData(USART1, c);

			// Attendre la fin de l'envoi.
			while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
		}
	}
}

Vous devrez connecter votre WCH-LinkE de la manière indiquée dans le cours CH32V003 : premiers pas, puis ouvrir une vue Terminal dans laquelle vous pourrez taper du texte et le voir s'afficher en majuscules.

Utilisation de l'UART en mode interruptions

Dès que les données deviennent plus nombreuses et/ou circulent plus vite, le mode polling devient inadapté et on doit donc gérer la communication via des interruptions et des tampons ("buffer" en anglais) d'émission et de réception, c'est-à-dire des files d'attente de données reçues ou à émettre. Ces files d'attente sont encore appelées FIFO pour First In First Out, donc premier élément entré dans la file, premier sorti.

Un autre aspect déterminant de l'utilisation des interruptions est de permettre de découpler le code de gestion de la communication de celui du traitement des données. Même avec de faibles volumes et/ou vitesses, cet aspect est éminemment appréciable.

Extrayez maintenant cette archive dans le répertoire User de votre projet et rafraîchissez votre projet. Votre fichier main.c sera remplacé, faites en une copie si besoin.

Vous verrez les fichiers supplémentaires suivants :

  • project-defs.h est un fichier d'entête que j'inclue dans tous les modules du projet. son rôle est de faciliter la réutilisation de code en centralisant ce qui dépend du micro-contrôleur utilisé, et de regrouper les définitions relatives à l'ensemble du projet.

  • fifo-buffer.h et fifo-buffer.c permettent de créer facilement des tampons de type FIFO.

  • uart-api.h et uart-api.c donnent un exemple d'abstraction du fonctionnement de l'UART. Ses fonctionnalités sont limitées à ce qui est utile pour notre application, mais ça illustre bien le principe et l'intérêt d'une couche d'abstration du matériel (en anglais : HAL, Hardware Abstration Layer). Jetez un oeil à main.c pour en voir le bénéfice.

Documenter votre code

Les fichiers supplémentaires dont nous venons de parler illustrent également la manière de documenter votre code. Au moment où vous écrivez votre code, tout vous semble évident, naturel, normal. Replongez-vous dedans 6 mois plus tard et vous comprendrez que ce n'était pas si évident que ça et qu'il va vous falloir de gros efforts pour vous remettre dans le bain. Maintenant, imaginez ce qui va se passer si quelqu'un d'autre doit intervenir sur votre code, ou si vous devez intervenir sur le code d'un tiers.

Il est donc indispensable de disposer d'un minimum de documentation sur votre code et la meilleure façon de la créer est d'utiliser des commentaires, non seulement parce qu'ils sont là où vous en aurez le plus besoin, mais aussi parce que vous penserez à les mettre à jour en cas de modification des sources, ce qui n'est pas le cas avec une documentation séparée.

On trouve 2 types de commentaires complémentaires dans un source, les commentaires qui documentent des API et les commentaires "au fil de l'eau" qui clarifient ce qui n'est pas évident à comprendre ou ne peut être deviné autrement.

Les commentaires qui documentent des API doivent respecter une convention particulière qui permet de les extraire pour les présenter sous forme de pages HTML et/ou de les voir en "tooltips" dans votre IDE préféré. Il existe plusieurs conventions pour ces commentaires, celle que j'ai utilisé ici est la convention JavaDoc, qui ne se limite pas à Java.

L'outil d'extraction et de mise en forme HTML de la documentation utilisé ici s'appelle doxygen et est à installer avant de continuer. Sous Linux, c'est aussi simple qu'un sudo apt install doxygen ou équivalent.

Ensuite, vous devrez créer un fichier de configuration pour votre projet. Pour cela, ouvrer une ligne de commande dans le répertoire User de votre projet et exécutez doxygen -g, qui aura pour effet de créer un fichier Doxyfile. Rafraîchissez votre projet et ouvrez ce fichier, il y a quelques modifications à faire :

  • Dans tous les cas, vous devez donner un nom à votre projet. Localisez la ligne commençant par PROJECT_NAME et indiquez-le. Par exemple :
    PROJECT_NAME = "CH32V003 UART"

  • Si vous utilisez des numéros de version, vous pouvez indiquer la version courante avec la propriété PROJECT_NUMBER.

  • Vous pouvez fournir une brève description du projet avec la propriété PROJECT_BRIEF.

  • Si vous écrivez vos commentaires en français, vous devez l'indiquer en modifiant la propriété OUTPUT_LANGUAGE :
    OUTPUT_LANGUAGE = French

  • Pour que la première phrase d'un commentaire soit utilisée comme description brève de l'élément commenté, vous devez le préciser en modifiant la propriété JAVADOC_AUTOBRIEF :
    JAVADOC_AUTOBRIEF = YES

  • Puisque tous nos sources sont en langage C, vous pouvez demander à optimiser la présentation des pages en conséquence en modifiant la propriété OPTIMIZE_OUTPUT_FOR_C :
    OPTIMIZE_OUTPUT_FOR_C = YES

  • Enfin, doxygen produit par défaut des fichiers LaTeX dont nous n'avons pas l'utilité, donc vous devez modifier la propriété GENERATE_LATEX pour les supprimer :
    GENERATE_LATEX = NO

Vous pouvez ensuite générer la documentation HTML en ouvrant une ligne de commande dans le répertoire User de votre projet et en exécutant simplement la commande doxygen. Rafraîchissez votre projet et vous verrez apparaître un répertoire "html" sous "User". La page d'accueil de la documentation est html/index.html.

Il ne nous reste plus qu'à voir en quoi consiste la convention JavaDoc pour que vous maîtrisiez complètement le sujet.

Commentaires de fichier

Pour commencer, il faut savoir qu'on peut mettre les commentaires soit dans les fichiers .h, soit dans les fichiers .c. J'ai l'habitude de les mettre dans les .h parce que quand on diffuse une bibliothèque sans ses sources, on a au moins les .h. D'autres développeurs préfèrent mettre les commentaires de documentation dans les .c pour avoir tous les types de commentaires au même endroit. Du point de vue de doxygen, c'est indifférent.

Ensuite, les commentaires JavaDoc commencent par /** et se terminent par */. Ce sont donc des commentaires de bloc et non de ligne. Les commentaires JavaDoc contiennent au moins une phrase et peuvent contenir des directives, introduites par le caractère @.

Pour qu'un fichier soit pris en compte par doxygen, il doit contenir au minimum un commentaire pour l'ensemble du fichier. Sa structure est la suivante :

/**
 * @file mon-fichier.h
 *
 * Description brève du module.
 *
 * Description libre et aussi complète que nécessaire du module, 
 * utilisant autant de phrases que vous voulez.
 *
 * Notez au passage qu'une phrase est une suite de caractères qui 
 * commence par une majuscule et se termine par un point.
 */

Avec la configuration de doxygen que nous utilisons, la première phrase d'un commentaire est toujours la description brève de l'élément commenté.

Commentaires de fonction

La documentation d'une fonction précède son prototype et obéit aux mêmes règles que le commentaire de fichier mais avec des directives différentes :

/**
 * Description brève de la fonction.
 *
 * Description libre et aussi complète que nécessaire de la fonction, 
 * utilisant autant de phrases que vous voulez.
 *
 * @param arg1 Description de l'argument 1.
 * @param arg2 Description de l'argument 2.
 * @return Description de la valeur renvoyée si ce n'est pas void.
 */
int maFonction(const char *arg1, int arg2);

Commentaires de type ou de macro

La documentation d'un type ou d'une macro précède sa définition et ressemble à celle des fonctions, mais on utilisera avec profit un autre type de commentaire pour les champs des structures, comme illustré ci-dessous :

/**
 * Description brève du type.
 *
 * Description libre et aussi complète que nécessaire du type, 
 * utilisant autant de phrases que vous voulez.
 */
struct MaStructure {
	int champ1; /**< Documentation du champ 1. */
	char champ2; /**< Documentation du champ 2. */
};

/**
 * Description brève de la macro.
 *
 * Description libre et aussi complète que nécessaire de la macro, 
 * utilisant autant de phrases que vous voulez.
 */
#define MA_MACRO sa_definition

Le commentaire délimité par /**< */ est un commentaire de bloc. Il peut s'utiliser partout où on veut commenter l'information située à sa gauche, donc pas uniquement les champs des structures. Son seul inconvénient est qu'il est peu lisible lorsque les commentaires de ce type sont nombreux et longs.

Commentaires "au fil de l'eau"

Les commentaires ne respectant pas les conventions précédentes sont ignorés par doxygen, mais pas par le développeur qui lit les sources pour apporter une correction ou une évolution. Vous voudrez donc utiliser /* */ et // pour documenter :

  • La justification de tout choix qui pourrait surprendre un développeur ne connaissant pas bien l'application ou le micro-contrôleur utilisé.

  • Les étapes d'un calcul ou traitement complexe, afin de faciliter le lien avec les spécifications de l'application.

  • La signification de condition dans un test un peu obscur.

  • De manière générale, tout ce qui ne coule pas de source ou qui vous a posé problème lors de l'écriture ou de la mise au point du code.

N'oubliez jamais que vous êtes le premier bénéficiaire de la documentation que vous écrivez. Ce n'est pas parce que vous avez écrit une application que vous comprendrez encore comment elle fonctionne quelques mois plus tard. Or, aucun code n'est statique, on a toujours besoin : de corriger des bugs, d'apporter des modifications ou évolutions, de réutiliser une partie du code pour un autre projet...

Contrôle de flux

Le code que nous avons produit jusqu'à maintenant fonctionne bien parce que les 2 côtés du lien de communication réagissent suffisamment vite pour éviter des pertes de données. Si un des 2 côtés était nettement plus lent que l'autre, il faudrait trouver une solution pour ralentir le côté le plus rapide. Ce peut être une solution logicielle, auquel cas on parle de software flow control (contrôle de flux logiciel), ou matérielle, au moyen de signaux électriques dédiés.

L'avantage du contrôle de flux matériel est qu'il ne nécessite aucun développement et fonctionne aussi bien avec des flux de données binaires que du texte, contrairement au contrôle de flux logiciel. Pour utiliser le contrôle de flux matériel avec l'UART du CH32V003, il faut :

  • Lors de la configuration de l'UART, utiliser USART_HardwareFlowControl_RTS_CTS à la place de USART_HardwareFlowControl_None.

  • Que l'autre système utilise également le contrôle de flux matériel.

  • Connecter les 2 systèmes de la manière suivante :

Si vous avez la garantie qu'un des 2 systèmes sera toujours le plus lent, il est possible d'économiser un fil. Si le CH32V003 envoie les données plus vite que l'autre système ne peut les traiter, vous avez besoin de ralentir le CH32V003 et c'est le rôle de sa broche CTS. Vous devez donc utiliser USART_HardwareFlowControl_CTS et connecter les 2 systèmes de la manière suivante :

Si par contre le CH32V003 traite les données reçues moins vite que l'autre système ne les envoie, vous avez besoin de ralentir l'autre système et c'est le rôle de la broche RTS du CH32V003. Vous devez donc utiliser USART_HardwareFlowControl_RTS et connecter les 2 systèmes de la manière suivante :

Autres protocoles supportés

L'UART du CH32V003 peut être utilisé dans différents modes de communication série en plus du protocole UART déjà abordé :

  • LIN (Local Interconnect Network), un protocole de communication principalement utilisé dans le secteur de l'automobile sans lui être spécifique.
  • Communication série multi-processeurs, avec des mots de 9 bits, le bit de poids le plus fort indiquant s'il s'agit d'une adresse ou d'une donnée. Ce protocole est supporté par de nombreux micro-contrôleurs, depuis les humbles 8051 jusqu'aux célèbres STM32. En cas de besoin, le SDK du CH32V003 contient un exemple d'utilisation de ce mode.
  • IrDA, un protocole de communication par infrarouge. C'est ce même protocole qui est utilisé par les smartphones capables de fonctionner comme télécommande universelle.
  • ISO 7816 / smart card, le protocole de communication utilisé par les cartes à puce. En cas de besoin, le SDK du CH32V203 contient un exemple d'utilisation de ce mode.

Ce que nous avons appris

Vous savez maintenant :

  • Ce qu'est un UART et à quoi il sert.

  • Comment utiliser l'UART en mode polling et interruptions.

  • Comment documenter son code et utiliser doxygen.

  • Quelles sont les possibilités de l'UART du CH32V003.


Copyright (c) 2024 Vincent DEFERT - Tous droits réservés