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.
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.
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
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.
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.
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.
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.
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.
SystemCoreClock
doit correspondre à la fréquence réelle du processeur. Si l'horloge change,
cette variable doit être mise à jour.
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.