Intégration de modèles UART pour une utilisation flexible
Dans le développement pour microcontrôleurs, la gestion efficace des communications série (UART) est fondamentale. Deux approches courantes existent : un modèle léger et un modèle plus complet. L'objectif de cette intégration est de permettre une sélection dynamique selon les contraintes de ressources du système cible.
Le modèle basique consomme peu de mémoire mais se limite à l'affichage de chaînes de caractères et de valeurs numériques (bases 10 ou 16). À l'inverse, le modèle avancé offre des fonctionnalités d'affichage étendues au prix d'une plus grande empreinte mémoire. La solution présentée permet de basculer entre les deux via des préprocesseurs.
Fichier de configuration des macros de débogage
Le mécanisme de sélection repose sur deux indicateurs : EN_DEBUG pour activer le débogage, et EN_SYS_STR_LIB pour choisir entre les implémentations avancée ou basique. Lorsque le débogage est désactivé, toutes les macros d'affichage sont compilées comme des opérations nulles.
#ifndef DEBUG_CONFIG_H
#define DEBUG_CONFIG_H
#ifndef ENABLE_DEBUG_LOG
#define ENABLE_DEBUG_LOG 0
#endif
#ifndef USE_STANDARD_STRING_LIB
#define USE_STANDARD_STRING_LIB 1
#endif
#if ENABLE_DEBUG_LOG
#if USE_STANDARD_STRING_LIB
#define DEBUG_OUTPUT SerialFormattedPrint
#else
#define DEBUG_OUTPUT SerialBasicPrint
#endif
#define LOG_MSG DEBUG_OUTPUT
#define LOG_VALUE_MSG SerialPrintValue
#else
#define LOG_MSG(...) ((void)0)
#define LOG_VALUE_MSG(...) ((void)0)
#endif
#endif
Interface du module UART
Le fichier d'en-tête déclare les fonctions communes aux deux modèles : initialisation, envoi unitaire, envoi de blocs et réception. Les fonctions spécialisées sont condiitonnées par l'activation de la bibliothèque standadr. Un jeu de macros redirige les appels de débogage vers les fonctions UART appropriées.
#ifndef UART_INTERFACE_H
#define UART_INTERFACE_H
#if USE_STANDARD_STRING_LIB
#include <stdarg.h>
void SerialFormattedPrint(const char *format, ...);
#endif
void UART_Initialize(unsigned int peripheral_clock, unsigned int baud_rate);
void UART_TransmitByte(unsigned char data);
void UART_TransmitBuffer(const unsigned char *buffer, unsigned int length);
void SerialBasicPrint(const char *message);
void SerialPrintValue(const char *prefix, unsigned int number, unsigned char base);
unsigned char UART_ReceiveByte(void);
#ifdef ENABLE_UART_DEBUG
#define UART_LOG LOG_MSG
#define UART_LOG_VALUE LOG_VALUE_MSG
#else
#define UART_LOG(...) ((void)0)
#define UART_LOG_VALUE(...)
#endif
#endif
Implémentation UART complète
L'implémentation regroupe les fonctions d'initialisation pour configurer les registres du périphérique, les routines de transmission basiques, et la fonction avancée utilisant vsnprintf pour formater les chaînes. La fonction d'affichage de valeurs gère la conversion en base 10 ou 16 avec gestion des zéros non significatifs.
#include "uart_registers.h"
#include "uart_interface.h"
static const char hex_digits[] = "0123456789ABCDEF";
void UART_Initialize(unsigned int peripheral_clock, unsigned int baud_rate)
{
unsigned short divisor;
// Configuration des broches
PINSEL0 = (PINSEL0 & ~0x0F) | 0x05;
// Activation DLAB
U0LCR = 0x83;
// Calcul du diviseur de bauds
divisor = (peripheral_clock / 16) / baud_rate;
U0DLM = divisor >> 8;
U0DLL = divisor & 0xFF;
// Désactivation DLAB
U0LCR = 0x03;
}
void UART_TransmitByte(unsigned char data)
{
while (!(U0LSR & 0x20));
U0THR = data;
}
void UART_TransmitBuffer(const unsigned char *buffer, unsigned int length)
{
while (length--) {
UART_TransmitByte(*buffer++);
}
}
void SerialBasicPrint(const char *message)
{
while (*message) {
UART_TransmitByte(*message++);
}
}
#if USE_STANDARD_STRING_LIB
void SerialFormattedPrint(const char *format, ...)
{
char output_buffer[256];
va_list args;
va_start(args, format);
vsnprintf(output_buffer, sizeof(output_buffer), format, args);
va_end(args);
SerialBasicPrint(output_buffer);
}
#endif
unsigned char UART_ReceiveByte(void)
{
while (!(U0LSR & 0x01));
return U0RBR;
}
void SerialPrintValue(const char *prefix, unsigned int number, unsigned char base)
{
char conversion_buffer[11];
int index = 0;
SerialBasicPrint(prefix);
if (base == 10) {
// Conversion décimale
do {
conversion_buffer[index++] = '0' + (number % 10);
number /= 10;
} while (number && index < 10);
while (index > 0) {
UART_TransmitByte(conversion_buffer[--index]);
}
} else if (base == 16) {
// Conversion hexadécimale
SerialBasicPrint("0x");
int bit_shift = 28;
while (bit_shift > 0 && ((number >> bit_shift) & 0xF) == 0) {
bit_shift -= 4;
}
while (bit_shift >= 0) {
UART_TransmitByte(hex_digits[(number >> bit_shift) & 0xF]);
bit_shift -= 4;
}
}
UART_TransmitByte(' ');
}
Utilisation dans un projet
Pour activer le débogage avec la bibliothèque standard, définissez ENABLE_DEBUG_LOG et USE_STANDARD_STRING_LIB à 1 dans votre fichier de configuraton. Pour les systèmes à ressources limitées, passez USE_STANDARD_STRING_LIB à 0 pour utiliser la version basique plus compacte. Les macros UART_LOG et UART_LOG_VALUE permettent un appel simplifié depuis l'application.