Utilisation du compteur DWT sur STM32F4

Le DWT (Data Watchpoint and Trace) est un périphérique intégré au cœur ARM Cortex-M4 des microcontrôleurs STM32F4. Il possède notamment un compteur appelé CYCCNT, qui s'incrémente à chaque cycle d'horloge du processeur.

Ce compteur permet de mesurer très précisément le temps d'exécution d'une fonction ou de générer de courts délais, sans utiliser les timers matériels TIMx du microcontrôleur.

Les principaux avantages du DWT sont :

Cette méthode est particulièrement intéressante pour les applications embarquées qui nécessitent des mesures de temps très courtes.

Principe de fonctionnement

Le DWT dispose d'un registre compteur nommé CYCCNT. Celui-ci s'incrémente à chaque cycle d'horloge CPU. Par exemple, avec un STM32F407 fonctionnant à 168 MHz, le compteur progresse de 168 millions de cycles par seconde.

Pour utiliser ce compteur, il faut activer le bloc de trace du cœur Cortex-M4, puis autoriser le comptage des cycles. Ces opérations sont réalisées à l'aide des registres CoreDebug->DEMCR et DWT->CTRL.

Les définitions de ces registres sont fournies par les fichiers CMSIS du microcontrôleur, généralement inclus avec STM32CubeIDE ou STM32CubeMX.

Le fichier dwt.h

Ce fichier contient les déclarations des fonctions qui permettront d'initialiser le compteur DWT, de générer un délai et de mesurer le temps d'exécution d'un code.

#ifndef DWT_H
#define DWT_H

#include "stm32f4xx.h"
#include <stdint.h>

void DWT_Init(void);
void DWT_Delay_us(uint32_t us);
uint32_t DWT_GetCycles(void);

#endif

Le fichier dwt.c

La fonction DWT_Init() active le comptage des cycles et remet le compteur à zéro. La fonction DWT_Delay_us() utilise ensuite ce compteur pour attendre le nombre de cycles correspondant au délai demandé.

#include "dwt.h"

void DWT_Init(void)
{
    /* Activation du bloc de trace */
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;

    /* Remise à zéro du compteur */
    DWT->CYCCNT = 0;

    /* Activation du compteur de cycles */
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}

uint32_t DWT_GetCycles(void)
{
    return DWT->CYCCNT;
}

void DWT_Delay_us(uint32_t us)
{
    uint32_t start;
    uint32_t cycles;

    /* Nombre de cycles correspondant au délai demandé */
    cycles = (SystemCoreClock / 1000000U) * us;

    /* Mémorisation de la valeur initiale */
    start = DWT->CYCCNT;

    /* Attente du nombre de cycles demandé */
    while ((uint32_t)(DWT->CYCCNT - start) < cycles)
    {
    }
}

La soustraction entre le compteur courant et sa valeur initiale permet de mesurer le nombre de cycles écoulés, même lorsque le compteur déborde et revient à zéro.

Attention : le compteur doit avoir été initialisé avant l'utilisation de DWT_Delay_us(). Cette fonction bloque le processeur pendant toute la durée du délai. Elle ne convient donc pas aux attentes longues ni aux fonctions qui doivent rester non bloquantes.

Initialisation dans main.c

Après l'initialisation du système et de l'horloge du microcontrôleur, il suffit d'appeler DWT_Init() une seule fois.

#include "dwt.h"

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

    DWT_Init();

    while (1)
    {
        /* Code de l'application */
    }
}

Dans cet exemple, SystemClock_Config() représente la fonction habituelle de configuration de l'horloge générée par STM32CubeMX. Le compteur utilise la fréquence CPU configurée par le système, disponible dans la variable SystemCoreClock.

Exemple 1 : générer un délai de 100 µs

Une fois le DWT initialisé, il suffit d'appeler DWT_Delay_us(100) pour demander un délai de 100 microsecondes.

DWT_Delay_us(100);  // Délai demandé : 100 µs

Prenons un STM32F407 dont la fréquence CPU est de 168 MHz :

Le programme attend donc environ 16 800 cycles avant de poursuivre son exécution. La durée réellement observée peut être légèrement supérieure au délai demandé, notamment en raison des instructions exécutées dans la boucle.

Exemple 2 : mesurer le temps d'exécution d'une fonction

Le DWT permet également de déterminer le nombre de cycles nécessaires à l'exécution d'une fonction. Pour cela, on mémorise la valeur du compteur avant l'appel, puis on lit sa nouvelle valeur après l'exécution.

uint32_t debut;
uint32_t fin;
uint32_t cycles;

debut = DWT_GetCycles();

MaFonction();

fin = DWT_GetCycles();

cycles = fin - debut;

La variable cycles contient le nombre de cycles écoulés pendant l'appel de MaFonction(), en tenant compte également du faible surcoût lié aux instructions de mesure.

Pour convertir ce résultat en microsecondes, on peut effectuer le calcul suivant :

uint32_t temps_us;

temps_us = cycles / (SystemCoreClock / 1000000U);

Cette conversion donne une durée exprimée en microsecondes entières. La division supprime les décimales : pour conserver davantage de précision, il est préférable de conserver le résultat en cycles ou d'utiliser une conversion en virgule fixe.

Exemple 3 : mesurer une durée très courte

Le compteur DWT est particulièrement utile pour mesurer des durées très courtes, pour lesquelles la résolution d'un timer configuré en millisecondes serait insuffisante.

Supposons que la mesure donne cycles = 840 sur un STM32F407 fonctionnant à 168 MHz. La durée correspondante est :

840 / 168 = 5 µs

La durée mesurée est donc de 5 microsecondes, soit 840 cycles CPU. Il faut toutefois tenir compte du surcoût de la mesure si l'on souhaite connaître précisément la durée propre de la fonction.

Remarques importantes

Pour un STM32F407, STM32F429 ou STM32F446, cette méthode constitue une solution pratique pour mesurer des temps d'exécution et générer de courts délais sans mobiliser un timer matériel.

Lexique

Cortex-M4
Cœur de processeur ARM utilisé notamment dans les STM32F4.
DWT (Data Watchpoint and Trace)
Bloc matériel du cœur ARM qui fournit notamment des fonctions de comptage de cycles et de surveillance.
CYCCNT (Cycle Count Register)
Registre de 32 bits qui compte les cycles d'horloge du processeur lorsqu'il est activé.
CoreDebug
Bloc de registres associé aux fonctions de débogage du cœur ARM.
DEMCR
Registre de contrôle permettant notamment d'activer le bloc de trace nécessaire au fonctionnement du DWT.
TRCENA
Bit du registre DEMCR qui active le fonctionnement des composants de trace.
TIMx
Désignation générique des timers matériels des microcontrôleurs STM32.
CMSIS
Ensemble de définitions et d'interfaces standardisées pour les cœurs ARM Cortex-M, notamment les registres matériels.
SystemCoreClock
Variable fournie par l'environnement logiciel STM32 qui indique la fréquence du cœur du processeur en hertz.
Cycle CPU
Unité de temps correspondant à un cycle de l'horloge du processeur. À 168 MHz, un cycle dure environ 5,95 nanosecondes.