Principes Fondamentaux des Timers STM32
Les timers sont des périphériques essentiels des microcontrôleurs STM32. Leur fonction principale est de réaliser des opérations de temporisation, de déclencher des interruptions et de générer des formes d'onde précises grâce à un comptage basé sur une horloge. Dans les systèmes embarqués, les timers trouvent des applications extrêmement variées, notamment :
- Contrôle temporel : Changement d'état périodique de LEDs, implémentation de fonctions de délai système, référence temporelle pour les planificateurs de tâches.
- Génération de formes d'onde : Contrôle de la vitesse des moteurs, de l'angle des servomoteurs et de la luminosité des LEDs via la modulation de largeur d'impulsion (PWM).
- Mesure de signaux : Utilisation de la fonction de capture d'entrée pour mesurer la largeur d'impulsion (par exemple, signaux de télécomande infrarouge) ou la fréquence des signaux externes.
- Contrôle synchronisé : Liaison de plusieurs timers pour des logiques de séquencement complexes, ou synchronisation avec d'autres périphériques (ADC, DAC).
La série de microcontrôleurs STM32F407 intègre 14 timers, classés en trois catégories selon leurs fonctionnalités. Les différences principales résident dans les fonctions supportées et le nombre de bits de comptage :
| Type de Timer | Modèle Spécifique | Nombre de Bits | Fonctionnalités Clés |
|---|---|---|---|
| Timer de Base | TIM6, TIM7 | 16 bits | Fonction de temporisation uniquement, peut déclencher la conversion DAC. |
| Timer Générique | TIM2 à TIM5, TIM9 à TIM14 | TIM2/TIM5 : 32 bits, autres : 16 bits | Temporisation, capture d'entrée, comparaison de sortie, génération PWM, mode impulsion unique. |
| Timer Avancé | TIM1, TIM8 | 16 bits | Inclut toutes les fonctionnalités des timers génériques, avec en plus des sorties complémentaires et un contrôle de zone morte (adapté pour le pilotage de moteurs). |
Origine de l'Horloge et Calcul de Fréquence
La source d'horloge des timers provient du système d'horloge principal (RCC). Elle est ensuite divisée par les bus APB avant d'atteindre le timer. La formule de calcul de la fréquence du timer varie selon le bus auquel il est connecté :
- Bus APB1 : Fréquence maximale de 42 MHz. Les timers TIM2 à TIM7 et TIM12 à TIM14 sont connectés à ce bus. Si le diviseur APB1 est de 1 (pas de division), la fréquence du timer est égale à la fréquence APB1. Si le diviseur est supérieur à 1, la fréquence du timer est égale à la fréquence APB1 multipliée par 2 (atteignant 84 MHz).
- Bus APB2 : Fréquence maximale de 84 MHz. Les timers TIM1, TIM8 à TIM11 sont connectés à ce bus. Si le diviseur APB2 est de 1, la fréquence du timer est égale à la fréquence APB2. Si le diviseur est supérieur à 1, la fréquence du timer est égale à la fréquence APB2 multipliée par 2 (atteignant 168 MHz).
Conclusion essentielle : Sur le STM32F407, les timers connectés à l'APB1 fonctionnent réellement à 84 MHz, et ceux connectés à l'APB2 à 168 MHz.
Calcul du Temps de Temporisation
Le temps de temporisation d'un timer est déterminé conjointement par le diviseur de présélection (PSC) et le registre de rechargement automatique (ARR). La formule clé est la suivante :
Temps de Temporisation = (PSC + 1) × (ARR + 1) / Fréquence du Timer
Explication des paramètres :
- PSC (Prescaler) : Registre de 16 bits (valeur de 0 à 65535). Il divise la fréquence du timer. La fréquence résultante est
Fréquence Timer / (PSC + 1). - ARR (Auto-Reload Register) : Registre de 16 ou 32 bits (selon le timer). Le compteur, après avoir atteint la valeur ARR, déclenche un événement de mise à jour (débordement/sous-dépassement) et recommence à compter.
Exemple : Utilisation du TIM6 (connecté à APB1, fréquence 84 MHz) pour une temporisation de 500 ms. Calculons les valeurs de PSC et ARR. Temps de temporisation = 500 ms = 0.5 s. Fréquence du timer = 84 MHz = 84 × 10⁶ Hz.
En appliquant la formule : (PSC + 1) × (ARR + 1) = 0.5 × 84 × 10⁶ = 42 × 10⁶.
Une configuration possible : PSC = 8399 (fréquence divisée = 84 MHz / 8400 = 10 kHz), ARR = 4999 (compte 5000 fois). Alors, (8399 + 1) × (4999 + 1) = 8400 × 5000 = 42 × 10⁶. Cette configuration satisfait la temporisation de 500 ms.
Modes de Comptage
Différents types de timers supportent divers modes de comptage. Les principaux modes sont :
- Comptage Ascendant (par défaut) : Le compteur démarre à 0 et compte vers le haut jusqu'à la valeur ARR. Un événement de mise à jour est déclenché, et le compteur redémarre à 0.
- Comptage Descendant : Le compteur démarre à la valeur ARR et compte vers le bas jusqu'à 0. Un événement de mise à jour est déclenché, et le compteur redémarre à la valeur ARR.
- Comptage Centré (ou Alignement Central) : Le compteur compte de 0 à ARR-1 (débordement), puis de ARR à 1 (sous-dépassement), et répète ce cycle. Ce mode est idéal pour la génération de PWM symétrique. Note : Les timers TIM9 à TIM14 ne supportent que le comptage ascendant. Les timers TIM2 à TIM5 supportent les trois modes. Les timers avancés supportent tous les modes.
Configuration de Base du Timer : Interruption de Temporisation
Prenons l'exemple du TIM6 configuré pour une temporisation de 1 seconde afin de faire basculer une LED (LED0). Les étapes principales sont les suivantes : activation de l'horloge -> configuration de la base de temps -> configuration de l'interruption -> activation du timer -> écriture de la routine de service d'interruption.
Code (Bibliothèque Standard)
#include "stm32f4xx.h"
/**
* @brief Initialisation de TIM6 pour une interruption de 1s.
* @note TIM6 est sur le bus APB1 (84MHz). Pour 1s : PSC=8399, ARR=9999.
* Temps = (8399+1)*(9999+1) / 84e6 = 8400 * 10000 / 84e6 = 1s.
*/
void TIM6_Init(void) {
// 1. Activer l'horloge pour TIM6 et le bus APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
// 2. Configurer la structure de base de temps
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8399; // Valeur du diviseur de présélection
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // Mode de comptage ascendant
TIM_TimeBaseInitStruct.TIM_Period = 9999; // Valeur de rechargement automatique
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // Division d'horloge (pas de division)
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // Compteur de répétition (uniquement pour timers avancés)
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct);
// 3. Configurer le contrôleur d'interruption NVIC (autoriser l'interruption TIM6)
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM6_DAC_IRQn; // Canal d'interruption TIM6
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // Priorité de préemption 2
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // Priorité de sous-exécution 0
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // Activer le canal d'interruption
NVIC_Init(&NVIC_InitStruct);
// 4. Activer l'interruption de mise à jour TIM6
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
// 5. Activer le compteur TIM6
TIM_Cmd(TIM6, ENABLE);
}
/**
* @brief Routine de service d'interruption pour TIM6.
* @note Déclenchée lorsque le compteur TIM6 atteint ARR (9999).
* Il faut vérifier le drapeau d'interruption et le réinitialiser.
*/
void TIM6_DAC_IRQHandler(void) {
// Vérifier si c'est une interruption de mise à jour TIM6
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) {
// Basculer la LED0 (supposée connectée à PF9)
GPIO_ToggleBits(GPIOF, GPIO_Pin_9);
// Effacer le drapeau d'interruption
TIM_ClearITPendingBit(TIM6, TIM_IT_Update);
}
}
/**
* @brief Initialisation du port GPIO PF9 en sortie pour contrôler la LED0.
*/
void LED_Init(void) {
// Activer l'horloge du port GPIOF
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // Sortie push-pull
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL; // Pas de résistance de pull-up/down
GPIO_Init(GPIOF, &GPIO_InitStruct);
// Éteindre la LED initialement
GPIO_SetBits(GPIOF, GPIO_Pin_9);
}
int main(void) {
// Configurer le groupement des priorités d'interruption
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init(); // Initialiser la LED
TIM6_Init(); // Initialiser l'interruption de temporisation TIM6
while (1) {
// Le traitement est effectué dans l'interruption, la boucle principale est vide.
}
}
Fonctionnalités Avancées des Timers Génériques
Les timers génériques (comme TIM2 à TIM5, TIM9 à TIM14) offrent, en plus des fonctions des timers de base, les modes de capture d'entrée et de comparaison de sortie. L'application la plus courante est la génération de PWM.
Principe Fondamental du PWM
Le PWM (Pulse Width Modulation) est une technique numérique permettant de simuler une sortie analogique en modulant le rapport cyclique d'un signal d'impulsion périodique. Les paramètres clés sont :
- Fréquence : Mesurée en Hz, elle représente le nombre d'impulsions par seconde.
Fréquence = Fréquence Timer / [(PSC + 1) × (ARR + 1)] - Rapport Cyclique (Duty Cycle) : Le pourcentage du temps pendant lequel le signal est à l'état haut sur une période complète.
Rapport Cyclique = (CCR + 1) / (ARR + 1) × 100%(pour PWM Mode 1, niveau haut actif).
Classification des modes PWM (configuré par les bits OCxM dans le registre TIMx_CCMRx) :
- PWM Mode 1 : En comptage ascendant, le signal de sortie est actif lorsque le compteur (CNT) est inférieur au registre de comparaison (CCR). Il devient inactif lorsque CNT ≥ CCR.
- PWM Mode 2 : En comptage ascendant, le signal de sortie est inactif lorsque CNT < CCR. Il devient actif lorsque CNT ≥ CCR. Note : CCR (Capture/Compare Register) est un registre de 16 bits utilisé pour définir le rapport cyclique. Sa valeur peut aller de 0 à ARR.
Application Pratique du PWM : Contrôle de Servomoteur
Le contrôle d'un servomoteur (par exemple, SG90) nécessite un signal PWM avec une période de 20 ms (fréquence de 50 Hz) et une largeur d'impulsion (durée du niveau haut) variant de 0.5 ms à 2.5 ms, correspondant à des angles de 0° à 180°. L'exemple suivant utilise le TIM9 Channel 2 (pin PE6) pour générer ce signal PWM.
Câblage Matériel
- Fil rouge du servomoteur : Connecté à l'alimentation 5V.
- Fil marron du servomoteur : Connecté à la masse (GND).
- Fil jaune du servomoteur : Connecté au TIM9 Channel 2 (pin PE6, fonction alternative GPIO_AF_TIM9).
Code
#include "stm32f4xx.h"
/**
* @brief Délai en microsecondes.
* @param nus : temps en microsecondes.
* @note Utilise le timer SysTick cadencé à 21 MHz.
*/
void delay_us(u32 nus)
{
SysTick->CTRL = 0; // Arrête le timer
SysTick->LOAD = nus * 21 - 1; // Charge la valeur de recalcul
SysTick->VAL = 0; // Réinitialise la valeur actuelle
SysTick->CTRL = 1; // Démarre le timer
while ((SysTick->CTRL & 0x00010000) == 0); // Attend la fin du décompte
SysTick->CTRL = 0; // Arrête le timer
}
/**
* @brief Délai en millisecondes.
* @param nms : temps en millisecondes.
* @note Utilise le timer SysTick. Peut présenter une légère imprécision.
*/
void delay_ms(u32 nms)
{
while(nms--)
{
SysTick->CTRL = 0;
SysTick->LOAD = 21000 - 1; // 21 * 1000
SysTick->VAL = 0;
SysTick->CTRL = 1;
while ((SysTick->CTRL & 0x00010000) == 0);
SysTick->CTRL = 0;
}
}
/**
* @brief Configuration du canal de sortie TIM9 pour le contrôle du servomoteur.
*/
void TIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. Activer l'horloge pour TIM9 (bus APB2)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
// 2. Activer l'horloge pour le port GPIOE
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
// 3. Configurer la broche GPIO PE6
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // Mode fonction alternative
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // Vitesse de sortie 100 MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // Sortie push-pull
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; // Résistance de pull-up
GPIO_Init(GPIOE, &GPIO_InitStructure);
// 4. Mapper la broche PE6 à la fonction TIM9
GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_TIM9);
// 5. Configurer la base de temps pour une période de 20 ms
TIM_TimeBaseStructure.TIM_Prescaler = 168 - 1; // Bus APB2 divisé, fréquence résultante 1 MHz (1 tick = 1 us)
TIM_TimeBaseStructure.TIM_Period = 20000 - 1; // 20000 ticks pour 20 ms
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM9, &TIM_TimeBaseStructure);
// 6. Configurer le mode de comparaison de sortie (PWM Mode 1)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // Activer la sortie
TIM_OCInitStructure.TIM_Pulse = 500; // Largeur d'impulsion pour 0 degré (0.5 ms)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // Signal haut actif
TIM_OC2Init(TIM9, &TIM_OCInitStructure); // Initialiser le canal 2
// 7. Activer le pré-chargement pour le registre CCR du canal 2
TIM_OC2PreloadConfig(TIM9, TIM_OCPreload_Enable);
// 8. Activer le pré-chargement pour le registre ARR
TIM_ARRPreloadConfig(TIM9, ENABLE);
// 9. Activer le timer TIM9
TIM_Cmd(TIM9, ENABLE);
}
/**
* @brief Point d'entrée principal du programme.
*/
int main(void)
{
// Configurer le groupement des priorités d'interruption
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// Initialiser la configuration du timer pour le servomoteur
TIM_Config();
/* Boucle infinie */
while (1)
{
// Angle 0 degrés
TIM_SetCompare2(TIM9, 500); // 0.5 ms
delay_ms(500);
// Angle 45 degrés
TIM_SetCompare2(TIM9, 1000); // 1.0 ms
delay_ms(500);
// Angle 90 degrés
TIM_SetCompare2(TIM9, 1500); // 1.5 ms
delay_ms(500);
// Angle 135 degrés
TIM_SetCompare2(TIM9, 2000); // 2.0 ms
delay_ms(500);
// Angle 180 degrés
TIM_SetCompare2(TIM9, 2500); // 2.5 ms
delay_ms(500); // Délai supplémentaire pour une transition plus uniforme
}
}
Extension PWM : Effet de "Respiration" LED
L'effet de "respiration" d'une LED est obtenu en faisant varier progressivement le rapport cyclique du PWM, simulant un changement de luminosité. Cet exemple utilise le TIM14 Channel 1 (pin PF9) configuré pour une fréquence PWM de 1 kHz.
#include "stm32f4xx.h"
/**
* @brief Initialisation PWM TIM14_CH1 (PF9) à 1kHz.
* @note TIM14 sur APB1 (84MHz). Pour 1kHz: PSC=83, ARR=999.
* Fréquence = 84e6 / [(83+1)*(999+1)] = 84e6 / (84 * 1000) = 1000 Hz.
*/
void TIM14_PWM_Init(void) {
// 1. Activer les horloges (TIM14 et GPIOF)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
// 2. Configurer PF9 en mode fonction alternative (AF14 pour TIM14_CH1)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
// 3. Configurer la base de temps
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 83;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 999;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM14, &TIM_TimeBaseInitStruct);
// 4. Configurer le mode PWM
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_Pulse = 0; // Luminosité minimale au départ
TIM_OC1Init(TIM14, &TIM_OCInitStruct); // Initialiser le canal 1
// 5. Activer le pré-chargement
TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM14, ENABLE);
// 6. Activer le timer
TIM_Cmd(TIM14, ENABLE);
}
int main(void) {
uint16_t ccr_val = 0;
uint8_t dir = 1; // Direction du changement de luminosité : 1=croissant, 0=décroissant
TIM14_PWM_Init();
while (1) {
if (dir) {
ccr_val++;
if (ccr_val > 999) dir = 0; // Atteint le max, inverser la direction
} else {
ccr_val--;
if (ccr_val <= 0) dir = 1; // Atteint le min, inverser la direction
}
TIM_SetCompare1(TIM14, ccr_val); // Mettre à jour le rapport cyclique
delay_ms(5); // Contrôler la vitesse de variation de la luminosité
}
}
Simulation de PWM par Logiciel
Il est possible de simuler un signal PWM avec un rapport cyclique ajustable en utilisant la fonction de temporisation de base d'un timer et en contrôlant manuellement les niveaux haut et bas d'une broche GPIO ordinaire. Cette approche n'est pas limitée par le nombre de canaux matériels disponibles et offre une meilleure portabilité.
Configuraton de la Simulation PWM (PF9, 50Hz)
int main(void)
{
// Configurer le groupement des priorités d'interruption à 4 bits de préemption
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_4);
// Initialiser le module de touche (supposé ici PF9)
PF9_Init(); // Note : PF9_Init() doit être défini ailleurs
// Boucle principale
while (1)
{
GPIO_ResetBits(GPIOF, GPIO_Pin_9); // Niveau bas
delay_ms(19); // Durée du niveau bas pour une période de 20 ms (50 Hz)
GPIO_SetBits(GPIOF, GPIO_Pin_9); // Niveau haut
delay_ms(1); // Durée du niveau haut pour un rapport cyclique de 5%
}
}
Résultat de Mesure à l'Oscilloscope
(Image de l'oscilloscope montrant la forme d'onde PWM générée)
Points Clés à Considérer
- Activation de l'Horloge : L'horloge de tous les timers et des GPIOs associés doit être activée avant toute configuration.
- Fonction Alternative : Pour les sorties PWM ou les captures d'entrée, les broches GPIO doivent être configurées en mode "fonction alternative" (AF) avec la valeur AF correcte.
- Priorités d'Interruption : Lorsque plusieurs interruptions de timer sont utilisées simultanément, une configuration judicieuse des priorités de préemption et de sous-exécution est nécessaire pour éviter les conflits.
- Registres de Pré-chargement : En mode PWM, il est recommandé d'activer le pré-chargement des registres CCR et ARR pour garantir la stabilité de la forme d'onde.
- Sans pré-chargement ARR : Une modification pendant le comptage peut tronquer le cycle courant, rendant la période irrégulière.
- Sans pré-chargement CCR : Une modification pendant le comptage peut déclencher prématurément l'événement de comparaison, créant des transitions de niveau irrégulières ou des artefacts.
PWM Matériel vs Logiciel : Le PWM matériel offre une haute précision et une faible utilisation du CPU, idéal pour les applications critiques. Le PWM logiciel est plus flexible (n'importe quelle broche GPIO) mais sa précision dépend de la résolution du timer logiciel et son utilisation CPU est légèrement plus élevée.