Utiliser SEGGER RTT Viewer

Avec une sonde de débogage compatible, comme la J-Link, SEGGER RTT Viewer permet d'afficher directement sur l'ordinateur les informations transmises par un microcontrôleur. Il devient ainsi plus facile de suivre l'exécution d'un programme, d'afficher des messages de diagnostic et de repérer d'éventuelles erreurs.

Son principal avantage est de pouvoir surveiller une variable en temps réel, sans devoir arrêter le programme à chaque vérification. On peut ainsi observer l'évolution de ses valeurs pendant le fonctionnement du système et mieux comprendre le comportement de son application embarquée.

Nous allons découvrir comment configurer et utiliser RTT Viewer pour suivre le fonctionnement d'un programme et observer les variables qui nous intéressent.

J-Link + Segger

Introduction

SEGGER RTT (Real Time Transfer) est un mécanisme de communication qui permet d'échanger des informations entre un microcontrôleur et un ordinateur, par l'intermédiaire d'un débogueur compatible, comme la sonde J-Link.

Contrairement à une communication série classique utilisant l'UART, SEGGER RTT présente plusieurs avantages :

Le logiciel SEGGER RTT Viewer permet notamment d'afficher en temps réel les messages envoyés par l'application embarquée. C'est un moyen simple de suivre son fonctionnement, de contrôler certaines valeurs et de faciliter la recherche d'erreurs pendant le développement.

Principe de fonctionnement

Le fonctionnement de SEGGER RTT repose sur une zone mémoire réservée aux échanges de données, appelée RTT Buffer. L'application exécutée sur le microcontrôleur STM32 y écrit les informations qu'elle souhaite transmettre.

Le débogueur J-Link accède ensuite à cette mémoire par l'interface de débogage et récupère les données disponibles. Celles-ci sont transmises au logiciel RTT Viewer, qui les affiche sur l'ordinateur.

Le cheminement des données peut être résumé de la manière suivante :

STM32 → RTT Buffer → J-Link → RTT Viewer

Ce mécanisme permet de consulter les informations transmises par le programme sans utiliser de liaison série UART dédiée. Il est ainsi possible de suivre son exécution et d'analyser les valeurs affichées pendant le fonctionnement du système.

Installation

Pour commencer, il faut télécharger et installer le package SEGGER J-Link, disponible sur le site du fabricant. Il regroupe les outils nécessaires au débogage et à la programmation des microcontrôleurs compatibles.

Après l'installation, plusieurs utilitaires sont disponibles, notamment :

Configuration du projet

Pour utiliser RTT dans un projet STM32, il faut ajouter les fichiers sources et les fichiers d'en-tête correspondants. Ils se trouvent dans le sous-répertoire SEGGER/RTT/ du package SEGGER.

Les fichiers à intégrer au projet sont les suivants :

Les fichiers .c doivent être ajoutés à la compilation du projet. Les fichiers .h doivent être accessibles au compilateur grâce aux chemins d'inclusion configurés dans l'environnement de développement.

Initialisation

Dans une configuration classique, SEGGER RTT ne nécessite pas d'initialisation explicite. Le mécanisme est initialisé lors de son premier appel, à condition que les fichiers nécessaires soient correctement intégrés au projet.

Pour commencer à utiliser RTT dans le fichier main.c, il suffit d'inclure le fichier d'en-tête :

#include "SEGGER_RTT.h"

Le programme pourra ensuite utiliser les fonctions RTT, par exemple pour afficher un message ou transmettre la valeur d'une variable vers l'ordinateur.

Premier message RTT

Pour vérifier que RTT fonctionne correctement, il suffit d'envoyer un premier message à l'aide de la fonction SEGGER_RTT_WriteString().

SEGGER_RTT_WriteString(0, "Application started\r\n");

Résultat dans RTT Viewer :

Application started

Le paramètre 0 désigne le canal RTT utilisé. Le canal 0 correspond au canal de sortie par défaut.

Affichage d'une variable entière

RTT permet également d'afficher la valeur d'une variable. Dans l'exemple suivant, la fonction SEGGER_RTT_printf() affiche la valeur du compteur counter à l'aide du format %lu, adapté à un entier non signé de type unsigned long.

uint32_t counter = 123;

SEGGER_RTT_printf(0,
                  "Counter = %lu\r\n",
                  (unsigned long)counter);

Affichage dans RTT Viewer :

Counter = 123

Affichage de plusieurs variables

Il est possible d'afficher plusieurs variables dans un même message. Chaque spécificateur de format, comme %lu ou %.1f, indique comment la valeur correspondante doit être présentée.

uint32_t adc_value = 2048;
float temperature = 25.7f;

SEGGER_RTT_printf(0,
                  "ADC=%lu Temp=%.1f C\r\n",
                  (unsigned long)adc_value,
                  (double)temperature);

Affichage dans RTT Viewer :

ADC=2048 Temp=25.7 C

Affichage périodique

Pour suivre l'évolution d'une valeur au cours du temps, on peut afficher régulièrement le compteur de temps du STM32, fourni par la fonction HAL_GetTick(). Dans cet exemple, un message est envoyé chaque seconde.

while (1)
{
    SEGGER_RTT_printf(0,
                      "Tick=%lu\r\n",
                      (unsigned long)HAL_GetTick());

    HAL_Delay(1000);
}

Exemple d'affichage dans RTT Viewer :

Tick=1000
Tick=2000
Tick=3000
...

Remarque : ces valeurs sont données à titre d'exemple. HAL_GetTick() renvoie le nombre de millisecondes écoulées depuis le démarrage du compteur système. La première valeur affichée dépend donc du moment où le programme commence à envoyer les messages.

Afficher le contenu d'une structure

RTT peut aussi faciliter le débogage d'une structure contenant plusieurs variables. Il suffit de transmettre ses différents champs à la fonction d'affichage.

typedef struct
{
    uint32_t speed;
    uint32_t rpm;
    float current;
} Motor_t;

Motor_t motor =
{
    .speed = 1500,
    .rpm = 3200,
    .current = 2.35f
};

SEGGER_RTT_printf(0,
                  "Speed=%lu RPM=%lu Current=%.2f\r\n",
                  (unsigned long)motor.speed,
                  (unsigned long)motor.rpm,
                  (double)motor.current);

Affichage dans RTT Viewer :

Speed=1500 RPM=3200 Current=2.35

Cette méthode est particulièrement pratique pour observer simultanément plusieurs paramètres d'une application embarquée. Elle permet de suivre leur évolution pendant l'exécution du programme et de repérer plus facilement une valeur inattendue, sans devoir arrêter systématiquement le microcontrôleur.

Exemple de macro de débogage

Pour simplifier l'affichage des messages de débogage, il est possible de définir une macro appelée DEBUG_PRINT. Elle permet d'utiliser une syntaxe plus courte tout en conservant les possibilités de la fonction SEGGER_RTT_printf().

#define DEBUG_PRINT(...) \
        SEGGER_RTT_printf(0, __VA_ARGS__)

Grâce à cette macro, il suffit ensuite d'appeler DEBUG_PRINT() pour afficher un message ou la valeur d'une variable.

Exemple d'utilisation :

DEBUG_PRINT("Value=%lu\r\n", (unsigned long)value);

L'intérêt est de rendre le code plus lisible et de faciliter l'ajout de messages de diagnostic tout au long du programme.

Exemple complet

Voici un exemple qui illustre l'utilisation de SEGGER RTT dans une application STM32. Après l'initialisation du microcontrôleur, un premier message confirme le démarrage. Le programme affiche ensuite la valeur d'un compteur toutes les secondes.

#include "SEGGER_RTT.h"

int main(void)
{
    HAL_Init();
    SystemClock_Config();

    SEGGER_RTT_WriteString(0,
                           "STM32 Started\r\n");

    uint32_t counter = 0;

    while (1)
    {
        SEGGER_RTT_printf(0,
                          "Counter=%lu\r\n",
                          (unsigned long)counter++);

        HAL_Delay(1000);
    }
}

Sortie dans RTT Viewer :

STM32 Started
Counter=0
Counter=1
Counter=2
Counter=3
...

Cet exemple montre comment suivre simplement l'exécution d'un programme sans utiliser de liaison UART dédiée. En remplaçant le compteur par une variable issue d'un capteur, d'un convertisseur analogique-numérique (ADC) ou d'un calcul, il devient possible d'observer son évolution en temps réel et de faciliter le débogage.

Configuration avancée du buffer RTT

Fichier SEGGER_RTT_Conf.h

SEGGER RTT permet d'adapter la taille des buffers aux besoins de l'application. Cette configuration s'effectue généralement dans le fichier SEGGER_RTT_Conf.h, lorsqu'il est fourni avec la version utilisée. Selon le package, il peut se trouver dans le dossier RTT/ ou dans un répertoire de configuration spécifique.

Deux paramètres importants permettent de définir la taille des buffers utilisés pour les échanges entre le microcontrôleur et l'ordinateur :

Dans une configuration courante, le buffer montant possède une taille par défaut de 1024 octets, tandis que le buffer descendant est souvent limité à 16 octets. Ces valeurs peuvent varier selon la version et la configuration du package SEGGER.

Augmenter la taille du buffer

Lorsque l'application génère beaucoup de messages de débogage, il peut être utile d'augmenter la taille du buffer montant. Cela permet de stocker davantage de données en attente de lecture par le débogueur.

Par exemple, dans le fichier de configuration :

#define BUFFER_SIZE_UP   (4096)
#define BUFFER_SIZE_DOWN (16)

Une taille de 4096 octets peut convenir à certaines applications. Si la mémoire RAM disponible le permet, une taille supérieure peut également être envisagée.

Attention : augmenter la taille du buffer ne garantit pas à elle seule l'absence de perte de données. Si l'application produit des informations plus rapidement que RTT Viewer ne peut les récupérer, le buffer finira par se remplir. Le mode de fonctionnement choisi détermine alors le comportement du programme.

Modes de fonctionnement du buffer

SEGGER RTT propose trois modes d'écriture. Ils définissent le comportement du programme lorsque le buffer ne dispose plus d'assez de place pour recevoir les données.

Mode bloquant

En mode bloquant, l'application attend que suffisamment de place soit disponible pour écrire les données dans le buffer.

Avantage :

Inconvénient :

Ce mode est à réserver aux situations où la conservation des données est prioritaire sur le respect des contraintes temporelles.

Mode non bloquant avec suppression

Dans ce mode, appelé SEGGER_RTT_MODE_NO_BLOCK_SKIP, l'application n'attend pas que le buffer se libère. Si l'espace disponible est insuffisant pour accueillir l'ensemble du message, celui-ci est ignoré.

Avantage :

Inconvénient :

Mode non bloquant avec troncature

Le mode SEGGER_RTT_MODE_NO_BLOCK_TRIM permet d'écrire autant de données que la place disponible le permet. Si le buffer est presque plein, seule une partie du message est enregistrée ; le reste est abandonné.

Avantage :

Inconvénient :

Ce mode peut être utile pour des traces de débogage lorsque la continuité de l'exécution est importante.

Modifier le mode du canal RTT

Pour le canal de sortie par défaut, le mode peut généralement être sélectionné dans le fichier SEGGER_RTT_Conf.h à l'aide du paramètre SEGGER_RTT_MODE_DEFAULT.

Par exemple, pour utiliser le mode non bloquant avec suppression :

#define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_NO_BLOCK_SKIP

Pour privilégier la conservation des données, on peut choisir le mode bloquant :

#define SEGGER_RTT_MODE_DEFAULT SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL

Le mode par défaut est généralement non bloquant avec suppression. Il convient de vérifier la configuration de la version utilisée. Le mode choisi doit aussi être adapté à l'application : le mode bloquant peut perturber les tâches qui doivent respecter des délais stricts.

Cas des gros transferts de données

Imaginons une application qui doit transmettre 1000 échantillons acquis par un convertisseur analogique-numérique (ADC). Si les données sont envoyées en continu, plusieurs facteurs peuvent entraîner une perte d'informations :

Pour limiter ces risques, plusieurs solutions sont possibles :

  1. Augmenter la valeur de BUFFER_SIZE_UP, en tenant compte de la RAM disponible.
  2. Utiliser le mode bloquant si la conservation des données est prioritaire et si les contraintes temporelles le permettent.
  3. Privilégier l'envoi de blocs de données plutôt qu'une multitude de petits appels à SEGGER_RTT_printf().

Pour des acquisitions rapides, il est également préférable de séparer la collecte des échantillons de leur transmission vers le PC. L'application peut ainsi enregistrer les mesures dans une mémoire tampon dédiée, puis les envoyer par blocs lorsque cela est possible.

Utilisation de SEGGER_RTT_Write()

Pour transférer de grandes quantités de données, il est souvent plus efficace d'utiliser SEGGER_RTT_Write() ou SEGGER_RTT_WriteNoLock(), plutôt que de multiplier les appels à SEGGER_RTT_printf().

En effet, la fonction SEGGER_RTT_printf() doit convertir les valeurs en texte avant de les transmettre. Pour des données déjà organisées dans un buffer, l'écriture directe permet de limiter ce travail supplémentaire.

Par exemple, pour transmettre un bloc de données :

SEGGER_RTT_Write(0,
                 (const char *)buffer,
                 buffer_size);

Le canal 0 correspond au canal de sortie par défaut. Le paramètre buffer désigne la zone mémoire contenant les données, tandis que buffer_size indique leur longueur en octets.

La fonction SEGGER_RTT_WriteNoLock() permet également d'écrire des données sans utiliser le mécanisme de verrouillage interne. Elle doit toutefois être réservée aux situations où l'application garantit elle-même que les accès concurrents ne provoqueront pas de conflit.

Vérification du taux de transfert

RTT Viewer permet de suivre les échanges de données avec le microcontrôleur. Selon la version du logiciel, les informations affichées peuvent notamment comprendre :

Le débit réellement obtenu dépend de plusieurs facteurs : le modèle de la sonde J-Link, la fréquence de l'interface SWD, le microcontrôleur utilisé, la taille des buffers et la quantité de données produites par l'application.

Avec une sonde adaptée et une liaison SWD suffisamment rapide, des débits de plusieurs centaines de kilo-octets par seconde peuvent être atteints dans certaines configurations. Il est toutefois préférable de mesurer le débit sur le matériel utilisé plutôt que de considérer cette valeur comme garantie.

Recommandations pratiques

Pour exporter de grandes quantités de données, par exemple des acquisitions ADC, des valeurs issues d'un régulateur PID, des mesures provenant d'un capteur IMU ou des données destinées à être enregistrées au format CSV, quelques précautions sont utiles.

Si la mémoire RAM disponible le permet, il est conseillé d'augmenter la taille du buffer RTT. Par exemple :

#define BUFFER_SIZE_UP (16384)

Une taille de 16 ko constitue un point de départ possible. Elle peut être augmentée à 32 ko ou davantage si l'application produit beaucoup de données et si la mémoire disponible le permet. Il faut également tenir compte de la configuration effective du buffer dans la version de SEGGER RTT utilisée.

Quelques bonnes pratiques permettent d'améliorer les transferts :

Sur un STM32F4 disposant de plusieurs centaines de kilo-octets de RAM, un buffer RTT de 16 à 32 ko peut représenter un bon compromis pour certaines applications. Il convient cependant de vérifier que cette réservation mémoire ne pénalise pas les autres fonctions du programme.

À retenir : le principal intérêt de SEGGER RTT est de pouvoir observer les messages et les valeurs transmises par le microcontrôleur pendant l'exécution du programme. En choisissant correctement la taille du buffer et le mode d'écriture, cet outil peut également servir à récupérer efficacement des données de mesure pour les analyser sur PC.

Lexique

Voici quelques définitions des termes et abréviations utilisés dans cette présentation de SEGGER RTT Viewer.

ADC (Analog-to-Digital Converter)
Convertisseur analogique-numérique. Il transforme une tension électrique en une valeur numérique que le microcontrôleur peut exploiter.
Application embarquée
Programme exécuté par un microcontrôleur ou un système électronique intégré dans un appareil.
Buffer (tampon mémoire)
Zone de mémoire utilisée pour stocker temporairement des données avant leur traitement ou leur transmission.
Canal RTT
Voie de communication utilisée par RTT pour échanger des données. Le canal 0 est généralement utilisé pour les messages de sortie vers l'ordinateur.
CSV (Comma-Separated Values)
Format de fichier texte permettant de stocker des données sous forme de tableau, avec des valeurs séparées par un délimiteur. Il peut notamment être utilisé pour analyser des mesures dans un tableur.
Débogage (debug)
Recherche et identification des erreurs d'un programme afin de comprendre son comportement et de corriger les problèmes.
Firmware
Logiciel intégré dans un appareil électronique et exécuté par son microcontrôleur.
FIFO (First In, First Out)
Mode de gestion d'une mémoire tampon dans lequel les données sont normalement récupérées dans le même ordre que celui dans lequel elles ont été enregistrées.
HAL (Hardware Abstraction Layer)
Bibliothèque logicielle qui simplifie l'utilisation des périphériques du microcontrôleur. STM32Cube HAL est fournie par STMicroelectronics pour les microcontrôleurs STM32.
IMU (Inertial Measurement Unit)
Centrale inertielle qui regroupe généralement des capteurs mesurant les accélérations et les vitesses de rotation.
J-Link
Famille de sondes de débogage développées par SEGGER. Elles permettent notamment de communiquer avec un microcontrôleur compatible au moyen d'une interface de débogage.
JTAG (Joint Test Action Group)
Interface normalisée utilisée notamment pour le test des circuits électroniques et le débogage des microcontrôleurs.
Macro
Instruction du préprocesseur C qui permet notamment de définir un raccourci pour une expression ou une suite d'instructions. La macro DEBUG_PRINT simplifie ici l'écriture des messages de débogage.
Mémoire Flash
Mémoire non volatile qui conserve les données sans alimentation électrique. Elle contient généralement le programme du microcontrôleur.
Microcontrôleur (MCU)
Circuit intégré qui regroupe notamment un processeur, de la mémoire et des périphériques. Il exécute le programme qui commande le fonctionnement d'un appareil électronique.
Non bloquant
Mode de fonctionnement dans lequel le programme ne reste pas en attente lorsque le buffer ne dispose plus de suffisamment de place. Selon le mode choisi, des données peuvent alors être ignorées ou tronquées.
PID (Proportionnel, Intégral, Dérivé)
Régulateur utilisé pour commander un système en fonction de l'écart entre une valeur souhaitée et une valeur mesurée.
RAM (Random Access Memory)
Mémoire vive utilisée par le programme pendant son exécution. Elle contient notamment les variables et les buffers. Son contenu est généralement perdu lorsque l'alimentation est coupée.
RTT (Real Time Transfer)
Technologie de SEGGER permettant d'échanger des données entre un microcontrôleur et un ordinateur par l'intermédiaire d'une sonde de débogage compatible, sans liaison série UART dédiée.
RTT Viewer
Logiciel de SEGGER qui permet notamment d'afficher les messages transmis par une application utilisant RTT.
SWD (Serial Wire Debug)
Interface de débogage utilisant généralement deux signaux principaux, SWDIO et SWCLK, pour communiquer avec un microcontrôleur compatible.
UART (Universal Asynchronous Receiver-Transmitter)
Interface de communication série asynchrone couramment utilisée pour échanger des données entre un microcontrôleur et un ordinateur ou un autre équipement.
Troncature
Suppression de la partie d'un message qui ne peut pas être enregistrée faute de place dans le buffer. Le message transmis peut alors être incomplet.
HAL_GetTick()
Fonction de la bibliothèque STM32 HAL qui renvoie le compteur de temps système, généralement exprimé en millisecondes depuis son initialisation.
SEGGER_RTT_printf()
Fonction qui permet de transmettre un message formaté, par exemple une phrase contenant la valeur d'une ou plusieurs variables.