CH32V003 : mesure de durées

Ce que nous allons faire - Prérequis

Nous allons voir dans ce cours comment utiliser le timer pour mesurer des intervalles de temps, notamment le temps séparant 2 impulsions successives et la largeur d'une impulsion. Nous utiliserons pour cela le mode "input capture" du timer.

Les prérequis de ce cours sont :

Principe de la mesure d'intervalle

Nous allons dans un premier temps mesurer l'intervalle de temps entre 2 appuis successifs sur le bouton poussoir, en millisecondes. Pour ce faire, vous allez réaliser le même câblage que dans le cours "CH32V003 : GPIO et interruptions", mais en reliant cette fois le poussoir à PD4 au lieu de PD2.

La raison de ce changement est que la data sheet nous indique que T2CH1 (le canal 1 du timer 2) est disponible sur PD4. Ceci nous permet de continuer à utiliser le timer 2 et au passage de pouvoir copier-coller une partie du code du cours précédent. :)

Nous utiliserons cependant le timer 1 plus loin pour bien voir les différences entre les timers 1 et 2, car elles ne sont pas clairement expliquées dans la documentation.

Par contre, le choix du canal 1 n'est pas dû au hasard, c'est parce qu'on va utiliser aussi le canal 2 dans la partie de ce cours traitant de la mesure de largeur d'impulsion. S'il n'y avait pas eu cette motivation, on aurait pu utiliser n'importe lequel des 4 canaux du timer.

Le timer est une "alternate function" du GPIO et le manuel de référence nous fournit le diagramme suivant pour nous en expliquer le fonctionnement :

Ce diagramme est incorrect. Lorsqu'on utilise une alternate function digitale (donc tout sauf l'ADC, que nous verrons plus tard), les possibilités de configuration de la ligne de GPIO restent les mêmes. On peut donc utiliser les résistances pull-up ou pull-down en entrée, ou le mode push-pull ou open-drain en sortie (avec une petite subtilité que nous verrons dans le cours sur le PWM), même si ça ne figure pas sur le diagramme. Et on ne va pas s'en priver. :)

Le moment est venu pour vous de créer un nouveau projet, modifier system_ch32v00x.c comme vous en avez maintenant l'habitude, puis de remplacer le contenu du fichier main.c par le code suivant :

#include <ch32v00x.h>
#include <debug.h>

#define SW_GPIO_PIN GPIO_Pin_4
#define SW_GPIO_PORT GPIOD
#define SW_GPIO_RCC RCC_APB2Periph_GPIOD

// Pour calculer un intervalle de temps, il faut 2 mesures.
volatile uint16_t t1;
volatile uint16_t t2;
// On doit compter les mesures restant à faire pour savoir quand calculer
// l'intervalle. Au démarrage du système, il faudra 2 mesures. Par la suite,
// on calculera l'intervalle entre la dernière valeur et la précédente.
volatile int n = 2;

__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_IRQHandler() {
	// Si l'interruption vient du canal 1 de TIM2
	if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
		// La dernière valeur devient la précédente.
		t1 = t2;
		// Mémoriser la nouvelle valeur du compteur.
		t2 = TIM_GetCapture1(TIM2);
		// Décrémenter le compteur de captures restant à faire.
		n--;
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
	}
}

int main() {
	// On utilisera printf() pour afficher les résultats.
	USART_Printf_Init(115200);

	// -- Configurer l'entrée du GPIO associée au bouton poussoir.
	RCC_APB2PeriphClockCmd(SW_GPIO_RCC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };
	gpioInit.GPIO_Pin = SW_GPIO_PIN;
	// Activer la résistance pull-up
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(SW_GPIO_PORT, &gpioInit);

	// -- Activer le timer 2
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	// -- Configurer le compteur principal du timer.
	TIM_TimeBaseInitTypeDef timeBaseInit = { 0 };
	// On veut utiliser toute la capacité du timer pour la mesure, donc on
	// doit fixer la valeur de l'auto-reload register à son maximum, soit
	// 2^16 - 1 = 65535
	timeBaseInit.TIM_Period = 65535;
	// Avec une horloge principale à 48MHz, si on veut mesurer des temps en
	// millisecondes, il faut incrémenter le compteur du timer toutes les 1ms,
	// donc le prescaler doit diviser l'horloge principale par 48000. On doit
	// donc fixer la valeur du prescaler à 48000 - 1 = 47999.
	timeBaseInit.TIM_Prescaler = 47999;
	timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV1;
	timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &timeBaseInit);

	// -- Configurer le canal de capture.
	TIM_ICInitTypeDef icInit = { 0 };
	icInit.TIM_Channel = TIM_Channel_1;
	// On veut mesurer le signal d'entrée tel quel, donc on ne veut
	// ni prescaler, ni filtre.
	icInit.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	icInit.TIM_ICFilter = 0;
	// Quand on enfonce le poussoir, l'entrée passe à 0, donc on veut
	// mesurer le temps écoulé entre 2 fronts descendants successifs,
	// qui correspondront à 2 appuis successifs sur le poussoir.
	icInit.TIM_ICPolarity = TIM_ICPolarity_Falling;
	// On veut traiter le signal présent sur l'entrée du canal 1.
	// On verra l'utilité de ce paramètre dans la 2ème partie du
	// cours pour mesurer la largeur d'une impulsion.
	icInit.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM2, &icInit);

	// -- Activer l'interruption globale du timer 2.
	NVIC_InitTypeDef nvicInit = { 0 };
	nvicInit.NVIC_IRQChannel = TIM2_IRQn;
	nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
	nvicInit.NVIC_IRQChannelSubPriority = 1;
	nvicInit.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvicInit);

	// -- Autoriser les interruptions du canal 1.
	TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);

	// -- Démarrer le compteur du timer.
	TIM_Cmd(TIM2, ENABLE);

	while (1) {
		if (n == 0) {
			// On veut un seul nouvel échantillon pour  l'intervalle suivant.
			n = 1;
			// On affiche l'intervalle actuel.
			int t = t2 - t1;
			printf("Intervalle : %d ms.\r\n", t);
		}
	}
}

Comme d'habitude, prenez le temps de lire les commentaires et de bien suivre le fonctionnement du code au debugger ou avec des printf() et de lire les passages correspondants du manuel de référence.

Filtrage de l'entrée

Il est probable que les rebonds de votre poussoir parasitent vos mesures et vous avez probablement pensé à ajouter un condensateur pour les éliminer. Lorsqu'on utilise un timer, on a cependant une possibilité supplémentaire pour s'en débarrasser, le filtrage de l'entrée.

Le filtre est un dispositif qui s'assure que l'entrée reste stable pendant un certain nombre de cycles d'horloge avant de transmettre son nouvel état à la suite de la chaîne de traitement.

La fréquence de l'horloge de tous les filtres d'un timer, notée fDTS sur les diagrammes, est définie par la valeur de TIM_ClockDivision dans la structure TIM_TimeBaseInitTypeDef. Les valeurs autorisées sont TIM_CKD_DIV1, TIM_CKD_DIV2 et TIM_CKD_DIV4, qui correspondent respectivement à une division par 1, 2 et 4 de la fréquence d'horloge du micro-contrôleur (notée CK_INT sur les diagrammes), soit 48, 24 et 12MHz dans notre cas. CKD est le nom d'une zone du registre TIMn_CTLR1, décrit dans le manuel de référence.

Le nombre de cycles du filtre est défini par la valeur du membre TIM_ICFilter de la structure TIM_ICInitTypeDef. Les valeurs autorisées vont de 0 à 15.

Ainsi, si on définit TIM_ClockDivision = TIM_CKD_DIV4 et TIM_ICFilter = 12, la durée de filtrage sera de (12 cycles) / (12 MHz) = 1 microseconde, ce qui devrait suffire dans le cas de notre poussoir. Le code devient donc :

#include <ch32v00x.h>
#include <debug.h>

#define SW_GPIO_PIN GPIO_Pin_4
#define SW_GPIO_PORT GPIOD
#define SW_GPIO_RCC RCC_APB2Periph_GPIOD

volatile uint16_t t1;
volatile uint16_t t2;
volatile int n = 2;

__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_IRQHandler() {
	if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
		t1 = t2;
		t2 = TIM_GetCapture1(TIM2);
		n--;
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
	}
}

int main() {
	USART_Printf_Init(115200);

	RCC_APB2PeriphClockCmd(SW_GPIO_RCC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };
	gpioInit.GPIO_Pin = SW_GPIO_PIN;
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(SW_GPIO_PORT, &gpioInit);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	TIM_TimeBaseInitTypeDef timeBaseInit = { 0 };
	timeBaseInit.TIM_Period = 65535;
	timeBaseInit.TIM_Prescaler = 47999;
	// On fixe la fréquence de filtrage à 48 / 4 = 12 MHz.
	timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV4;
	timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &timeBaseInit);

	TIM_ICInitTypeDef icInit = { 0 };
	icInit.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	// On veut filtrer sur 12 cycles d'horloge.
	icInit.TIM_ICFilter = 12;
	icInit.TIM_ICPolarity = TIM_ICPolarity_Falling;
	icInit.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM2, &icInit);

	NVIC_InitTypeDef nvicInit = { 0 };
	nvicInit.NVIC_IRQChannel = TIM2_IRQn;
	nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
	nvicInit.NVIC_IRQChannelSubPriority = 1;
	nvicInit.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvicInit);

	TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);

	TIM_Cmd(TIM2, ENABLE);

	while (1) {
		if (n == 0) {
			n = 1;
			int t = t2 - t1;
			printf("Intervalle : %d ms.\r\n", t);
		}
	}
}

Mesure d'intervalles longs

Le code précédent fonctionne très bien, sauf que vous obtenez des valeurs très bizarres lorsque l'intervalle de temps dépasse 65535ms. Ceci est dû au fait que le compteur a une résolution de 16 bits, ce qui contraint ses valeurs dans l'intervalle [0 ; 65535].

Pour mesurer des temps plus longs, il faudrait augmenter la résolution du compteur. Comme on ne peut pas modifier le micro-contrôleur, on va se débrouiller en comptant les débordements du compteur dans une variable de 16 bits également. Utilisée avec la valeur du compteur, on obtient donc une valeur de 32 bits dont le nombre de débordements est le demi-mot de poids fort. Au total, on pourra donc mesurer des intervalles de temps de 2^32 = 4294967296 ms, soit environ 49 jours et 17 heures. Ça devrait le faire. :)

Notre code sera donc modifié de la façon suivante :

#include <ch32v00x.h>
#include <debug.h>

#define SW_GPIO_PIN GPIO_Pin_4
#define SW_GPIO_PORT GPIOD
#define SW_GPIO_RCC RCC_APB2Periph_GPIOD

// Pour avoir des valeurs d'intervalles justes sur de longues périodes
// (> 1mn), il faut calculer les différences en prenant en compte les
// débordements du compteur principal.
volatile uint16_t overflows = 0;

volatile uint16_t t1overflow;
volatile uint16_t t1capture;
volatile uint16_t t2overflow;
volatile uint16_t t2capture;
volatile int n = 2;

__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_IRQHandler() {
	// Si l'interruption concerne le débordement du compteur
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
		// Incrémenter le nombre de débordements
		overflows++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}

	if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
		t1overflow = t2overflow;
		t1capture = t2capture;
		t2overflow = overflows;
		t2capture = TIM_GetCapture1(TIM2);
		n--;
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
	}
}

int main() {
	USART_Printf_Init(115200);

	RCC_APB2PeriphClockCmd(SW_GPIO_RCC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };
	gpioInit.GPIO_Pin = SW_GPIO_PIN;
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(SW_GPIO_PORT, &gpioInit);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	TIM_TimeBaseInitTypeDef timeBaseInit = { 0 };
	timeBaseInit.TIM_Period = 65535;
	timeBaseInit.TIM_Prescaler = 47999;
	timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV4;
	timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &timeBaseInit);

	TIM_ICInitTypeDef icInit = { 0 };
	icInit.TIM_Channel = TIM_Channel_1;
	icInit.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	icInit.TIM_ICFilter = 12;
	icInit.TIM_ICPolarity = TIM_ICPolarity_Falling;
	icInit.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM2, &icInit);

	NVIC_InitTypeDef nvicInit = { 0 };
	nvicInit.NVIC_IRQChannel = TIM2_IRQn;
	nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
	nvicInit.NVIC_IRQChannelSubPriority = 1;
	nvicInit.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvicInit);

	TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
	
	// -- Autoriser les interruptions des événements de mise à jour.
	TIM_ITConfig(TIM2, TIM_IT_Update , ENABLE);

	TIM_Cmd(TIM2, ENABLE);

	while (1) {
		if (n == 0) {
			n = 1;

			// On affiche l'intervalle actuel sour forme d'entier long non signé.
			uint32_t t = ((((uint32_t) t2overflow) << 16) | t2capture)
						- ((((uint32_t) t1overflow) << 16) | t1capture);
			printf("Intervalle : %lu ms.\r\n", t);
		}
	}
}

Différences entre TIM1 et TIM2

Nous avons vu dans le cours CH32V003 : introduction aux timers qu'il y a quelques différences d'utilisation entre les timers 1 et 2 et celles-ci vont bien entendu nous affecter. Le code ci-dessous remplit la même fonction que le précédent mais en utilisant TIM1 au lieu de TIM2. Évidemment, le poussoir devra dans ce cas être connecté à PD2 au lieu de PD4. Les différences sont mises en évidence en rouge.

#include <ch32v00x.h>
#include <debug.h>

#define SW_GPIO_PIN GPIO_Pin_2
#define SW_GPIO_PORT GPIOD
#define SW_GPIO_RCC RCC_APB2Periph_GPIOD

volatile uint16_t overflows = 0;

volatile uint16_t t1overflow;
volatile uint16_t t1capture;
volatile uint16_t t2overflow;
volatile uint16_t t2capture;
volatile int n = 2;

__attribute__((interrupt("WCH-Interrupt-fast")))
// TIM1_UP_IRQHandler est la routine d'interruption associée
// aux événements de mise à jour du compteur principal du timer.
void TIM1_UP_IRQHandler() {
	if (TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
		overflows++;
		TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
	}
}

__attribute__((interrupt("WCH-Interrupt-fast")))
// TIM1_CC_IRQHandler est la routine d'interruption associée
// aux canaux de capture du timer.
void TIM1_CC_IRQHandler() {
	if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET) {
		t1overflow = t2overflow;
		t1capture = t2capture;
		t2overflow = overflows;
		t2capture = TIM_GetCapture1(TIM1);
		n--;
		TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
	}
}

int main() {
	USART_Printf_Init(115200);

	RCC_APB2PeriphClockCmd(SW_GPIO_RCC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };
	gpioInit.GPIO_Pin = SW_GPIO_PIN;
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(SW_GPIO_PORT, &gpioInit);

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

	TIM_TimeBaseInitTypeDef timeBaseInit = { 0 };
	timeBaseInit.TIM_Period = 65535;
	timeBaseInit.TIM_Prescaler = 47999;
	timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV4;
	timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM1, &timeBaseInit);

	TIM_ICInitTypeDef icInit = { 0 };
	icInit.TIM_Channel = TIM_Channel_1;
	icInit.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	icInit.TIM_ICFilter = 12;
	icInit.TIM_ICPolarity = TIM_ICPolarity_Falling;
	icInit.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM1, &icInit);

	// -- Activer l'interruption des canaux de capture du timer.
	NVIC_InitTypeDef nvicInit = { 0 };
	nvicInit.NVIC_IRQChannel = TIM1_CC_IRQn;
	nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
	nvicInit.NVIC_IRQChannelSubPriority = 1;
	nvicInit.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvicInit);

	// -- Autoriser les interruptions du canal 1.
	TIM_ITConfig(TIM1, TIM_IT_CC1, ENABLE);

	// -- Activer l'interruption des événements de mise à jour.
	nvicInit.NVIC_IRQChannel = TIM1_UP_IRQn;
	// La valeur des autres champs de la structure est déjà correcte.
	NVIC_Init(&nvicInit);

	// -- Autoriser les interruptions des événements de mise à jour.
	TIM_ITConfig(TIM1, TIM_IT_Update , ENABLE);

	TIM_Cmd(TIM1, ENABLE);

	while (1) {
		if (n == 0) {
			n = 1;

			uint32_t t = ((((uint32_t) t2overflow) << 16) | t2capture)
						- ((((uint32_t) t1overflow) << 16) | t1capture);
			printf("Intervalle : %lu ms.\r\n", t);
		}
	}
}

On remarque qu'il y a beaucoup d'endroits où il suffit de remplacer TIM2 par TIM1, mais que ce qui concerne la gestion des interruptions est très différent. L'activation du timer est aussi différente car ils ne sont pas sur le même bus interne.

Mesure de largeur d'impulsion

Dans les programmes précédents, nous avons mesuré les intervalles de temps entre 2 événements consécutifs de même nature (front descendant ou front montant). Or, lorsqu'on veut mesurer la largeur d'une impulsion, on doit mesurer le temps écoulé entre 2 événements consécutifs opposés, c'est-à-dire soit entre un front descendant et le front montant qui le suit, soit entre un front montant et le front descendant qui le suit.

Les canaux de capture du timer peuvent être associés par paire pour faire cela. Le canal 1 peut être configuré pour capturer les événements de sa propre entrée ou de celle du canal 2. Le canal 2 peut aussi être configuré pour capturer les événements de sa propre entrée ou de celle du canal 1. Les canaux 3 et 4 fonctionnent ensemble de la même façon.

Dans notre cas, comme notre poussoir est à l'état 1 au repos, nous devrons pour mesurer la durée d'un appui calculer le temps écoulé entre un front descendant et le front montant qui le suit.

Nous allons pour cela configurer le canal 1 pour capturer les fronts descendants de sa propre entrée, comme actuellement, et le canal 2 pour capturer les fronts montants de l'entrée du canal 1. Ainsi, à chaque front montant, il nous suffira de faire la différence entre la valeur capturée sur le canal 2 et la dernière valeur capturée sur le canal 1 pour avoir la durée pendant laquelle le poussoir est resté appuyé.

Dans la structure de configuration d'un canal de capture, la constante qui indique d'utiliser l'entrée du même canal est TIM_ICSelection_DirectTI, la constante qui indique d'utiliser l'entrée de l'autre canal de la même paire est TIM_ICSelection_IndirectTI.

Ça nous donne le code suivant (avec TIM2) :

#include <ch32v00x.h>
#include <debug.h>
#include <stdbool.h>

#define SW_GPIO_PIN GPIO_Pin_4
#define SW_GPIO_PORT GPIOD
#define SW_GPIO_RCC RCC_APB2Periph_GPIOD

volatile uint16_t overflows = 0;

volatile uint16_t t1overflow;
volatile uint16_t t1capture;
volatile uint16_t t2overflow;
volatile uint16_t t2capture;

volatile bool buttonReleased = false;

__attribute__((interrupt("WCH-Interrupt-fast")))
void TIM2_IRQHandler() {
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
		overflows++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	}

	if (TIM_GetITStatus(TIM2, TIM_IT_CC1) != RESET) {
		// t1 est l'instant du dernier appui sur le poussoir (front descendant).
		t1overflow = overflows;
		t1capture = TIM_GetCapture1(TIM2);
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
	}

	if (TIM_GetITStatus(TIM2, TIM_IT_CC2) != RESET) {
		// t2 est l'instant où le poussoir est relevé (front montant).
		t2overflow = overflows;
		t2capture = TIM_GetCapture2(TIM2);
		// C'est à ce moment-là qu'on veut afficher le résultat.
		buttonReleased = true;
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC2);
	}
}

int main() {
	USART_Printf_Init(115200);

	RCC_APB2PeriphClockCmd(SW_GPIO_RCC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };
	gpioInit.GPIO_Pin = SW_GPIO_PIN;
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(SW_GPIO_PORT, &gpioInit);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

	TIM_TimeBaseInitTypeDef timeBaseInit = { 0 };
	timeBaseInit.TIM_Period = 65535;
	timeBaseInit.TIM_Prescaler = 47999;
	timeBaseInit.TIM_ClockDivision = TIM_CKD_DIV4;
	timeBaseInit.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM2, &timeBaseInit);

	TIM_ICInitTypeDef icInit = { 0 };
	icInit.TIM_Channel = TIM_Channel_1;
	icInit.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	icInit.TIM_ICFilter = 12;
	icInit.TIM_ICPolarity = TIM_ICPolarity_Falling;
	icInit.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM2, &icInit);

	// -- Initialisation du canal 2
	// On veut capturer les fronts montants sur l'entrée du canal 1.
	icInit.TIM_Channel = TIM_Channel_2;
	icInit.TIM_ICPolarity = TIM_ICPolarity_Rising;
	icInit.TIM_ICSelection = TIM_ICSelection_IndirectTI;
	TIM_ICInit(TIM2, &icInit);

	NVIC_InitTypeDef nvicInit = { 0 };
	nvicInit.NVIC_IRQChannel = TIM2_IRQn;
	nvicInit.NVIC_IRQChannelPreemptionPriority = 0;
	nvicInit.NVIC_IRQChannelSubPriority = 1;
	nvicInit.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&nvicInit);

	TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);

	// Autoriser les interruptions du canal 2.
	TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE);

	TIM_ITConfig(TIM2, TIM_IT_Update , ENABLE);

	TIM_Cmd(TIM2, ENABLE);

	while (1) {
		// Si le bouton vient d'être relâché, afficher la durée de l'appui.
		if (buttonReleased) {
			buttonReleased = false;

			uint32_t t = ((((uint32_t) t2overflow) << 16) | t2capture)
						- ((((uint32_t) t1overflow) << 16) | t1capture);
			printf("Intervalle : %lu ms.\r\n", t);
		}
	}
}

Pour aller plus loin

On peut utiliser la même approche pour mesurer la période et le rapport cyclique d'un signal périodique. La période est l'intervalle de temps entre 2 fronts montants et le rapport cyclique est donné par le quotient du temps entre un front montant et le front descendant qui le suit par la période.

Si vous avez un générateur de signaux, ou si pouvez en bricoler un à base de NE555, vous pouvez faire l'exercice.

Ce que nous avons appris

Vous savez maintenant :

  • Configurer le timer pour capturer des impulsions.

  • Utiliser les filtres d'entrée des canaux du timer pour éliminer les rebonds.

  • Mesurer des intervalles de temps longs.

  • Quelles sont les différences d'utilisation entre TIM1 et TIM2.

  • Mesurer des largeurs d'impulsions.


Copyright (c) 2024 Vincent DEFERT - Tous droits réservés