CH32V003 : les watchdogs

Ce que nous allons faire - Prérequis

Nous allons voir dans ce cours ce qu'est un watchdog et comment l'utiliser.

Les prérequis de ce cours sont :

Les watchdogs

Vous avez certainement entendu dire que les conducteurs de train doivent actionner régulièrement des capteurs pour prouver qu'ils sont en état de conduire leur train. Sans action appropriée de leur part, le train s'arrête automatiquement pour en assurer la sécurité. Ce système communément appelé "dispositif homme mort" existe sous différentes formes.

Les systèmes commandés par micro-contrôleurs peuvent aussi provoquer des dégâts matériels et humains en cas de défaillance et sont donc dotés de dispositifs "électronique morte" pour ramener le système dans un état sûr en cas de défaillance, exactement comme dans les trains.

Nous avons déjà parlé de la fonction "brake" des timers, mais il existe un autre dispositif de sécurité proche de celui des conducteurs de train, le watchdog (littéralement "chien de garde").

Un watchdog est un timer simplifié qui décompte à partir d'une valeur programmable. Si le décompte n'est pas réinitialisé (on dit aussi "réarmé") avant d'atteindre 0, une remise à zéro (reset) automatique est effectuée pour remettre le système dans son état initial, supposé sûr. Sur le CH32V003, il existe 2 types de watchdog, indépendant et à fenêtre.

Par défaut, les watchdogs restent en fonction lors de l'utilisation du debugger, ce qui peut être complètement ingérable. Vous pouvez cependant les désactiver. Voir la section 18.2.1 Debug MCU Configuration Register (DBGMCU_CR) du manuel de référence du CH32V003.

Independent watchdog (IWDG)

Le watchdog indépendant est exactement celui que nous venons de décrire, c'est-à-dire un compte à rebours qui doit être relancé avant d'atteindre 0, faute de quoi un reset est automatiquement effectué.

On voit sur ce diagramme extrait du chapitre 4 du manuel de référence du CH32V003 que le IWDG est cadencé par un oscillateur indépendant, le LSI (Low Speed Internal RC oscillator) fonctionnant à 128 kHz, après passage dans un prescaler divisant cette horloge par un facteur allant de 1 à 256. Le compteur proprement dit, et donc sa valeur de rechargement (reload value, la valeur depuis laquelle part le compte à rebours), est sur 12 bits.

On remarque également la présence d'un "key register". Le watchdog étant un dispositif de sécurité, la mise à jour de sa valeur de rechargement ne doit pas pouvoir être modifiée accidentellement. Pour pouvoir modifier le "reload register", il faut écrire une valeur particulière (0x5555) dans le "key register". D'autres valeurs permettent de démarrer le watchdog ou de le réarmer.

Notez que les noms des registres figurant sur le diagramme sont différents des noms réels. Ça sent le méchant copier-coller d'une ancienne documentation... ;) Ceci dit, ça ne change rien au principe de fonctionnement, il suffit de remplacer mentalement IWDG_KR par R16_IWDG_CTLR.

Window watchdog (WWDG)

Le watchdog à fenêtre fonctionne un peu différemment. On a toujours un compte à rebours, mais il n'est possible de réarmer le watchdog qu'entre 2 valeurs du compteur, valeurs qui déterminent donc ladite fenêtre de temps pendant laquelle le réarmement est possible. Tout rafraîchissement en dehors de cette fenêtre est considéré comme une anomalie et doit déclencher un reset.

Le WWDG est décrit au chapitre 5 du manuel de référence du CH32V003. On y voit qu'il est cadencé par l'horloge principale du CH32V003, dans notre cas 48 MHz, après division par 4096 puis passage dans un prescaler divisant encore cette horloge par un facteur allant de 1 à 8. On voit aussi que le compteur est sur 7 bits. On est donc sur des durées beaucoup plus courtes (0.1s max.) qu'avec le IWDG (8s max.).

Sur le diagramme, le mot de 7 bits représenté par la lettre W (registre R16_WWDG_CFGR) correspond à la valeur de début de la fenêtre. Le mot de 7 bits représenté par la lettre T (registre R16_WWDG_CTLR) correspond à la valeur du compteur. Un reset est automatiquement effectué si le bit 6 de T passe à 0, ce qui correspond à la valeur 0x3f du compteur.

En début de cycle, la valeur de T est 0x7f et est décrémentée à chaque impulsion d'horloge. Le watchdog ne peut être réarmé que si T < W et T > 0x3f. L'idée derrière cette condition est qu'un réarmement trop précoce est tout aussi anormal qu'un réarmement trop tardif et que les 2 conditions doivent déclencher un reset.

Pour finir, je mentionnerai la possibilité de déclencher une interruption appelée EWI (Early Wake-up Interrupt) lorsque T atteint 0x40, c'est-à-dire juste avant que le reset soit effectué. La documentation indique qu'on peut réarmer le watchdog lorsque survient cette interruption, mais on peut se demander à quoi sert d'utiliser un watchdog si on l'empêche de se déclencher ? Le seul cas où ça puisse avoir du sens est si on veut déclencher un reset si le réarmement intervient trop tôt, mais pas s'il intervient trop tard.

Application du IWDG

Nous allons utiliser le IWDG du CH32V003 pour réaliser un "dispositif homme mort". Nous aurons un délai de 5s pour appuyer sur un bouton poussoir. Tant que nous le ferons, une LED verte sera allumée. Sinon, un reset sera effectué et une LED rouge sera allumée. Le schéma de câblage est le suivant :

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 <stdbool.h>

// Fréquence de l'oscillateur interne qui pilote le IWDG, en Hz.
#define LSI_FREQ 128000ul
// Délai au bout duquel le watchdog déclenche un reset, en secondes.
#define WD_DURATION 5

// La LED rouge est allumée quand un reset a eu lieu.
#define LED_RED GPIO_Pin_4
// La LED verte est allumée quand on a réarmé le watchdog (appui sur poussoir).
#define LED_GREEN GPIO_Pin_5

static volatile bool buttonPressed = false;

__attribute__((interrupt("WCH-Interrupt-fast")))
void EXTI7_0_IRQHandler() {
	if (EXTI_GetITStatus(EXTI_Line7) != RESET) {
		buttonPressed = true;
		EXTI_ClearITPendingBit(EXTI_Line7);
	}
}

int main() {
	SystemCoreClockUpdate();

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);

	GPIO_InitTypeDef gpioInit = { 0 };

	// Configuration des sorties commandant les LED
	GPIO_WriteBit(GPIOC, LED_RED | LED_GREEN, Bit_SET);
	gpioInit.GPIO_Pin = LED_RED | LED_GREEN;
	gpioInit.GPIO_Mode = GPIO_Mode_Out_OD;
	gpioInit.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOC, &gpioInit);

	// Configuration de l'entrée du bouton poussoir
	gpioInit.GPIO_Pin = GPIO_Pin_7;
	gpioInit.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOC, &gpioInit);

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource7);

	EXTI_InitTypeDef extiInit = { 0 };
	extiInit.EXTI_Line = EXTI_Line7;
	extiInit.EXTI_Mode = EXTI_Mode_Interrupt;
	extiInit.EXTI_Trigger = EXTI_Trigger_Falling;
	extiInit.EXTI_LineCmd = ENABLE;
	EXTI_Init(&extiInit);

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

	// Configuration et démarrage du watchdog
	IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
	// On choisit le prescaler le plus élevé possible pour avoir une durée confortable pour un humain.
	IWDG_SetPrescaler(IWDG_Prescaler_256);
	IWDG_SetReload(LSI_FREQ / 256 /* = prescaler */ * WD_DURATION - 1);
	// On arme le watchdog
	IWDG_ReloadCounter();
	// On lance le décompte
	IWDG_Enable();

	// Mise à jour des LED rouge et verte
	GPIO_WriteBit(GPIOC, LED_RED, Bit_RESET);
	GPIO_WriteBit(GPIOC, LED_GREEN, Bit_SET);

	while (1) {
		if (buttonPressed) {
			buttonPressed = false;
			// On a appuyé sur le bouton poussoir :
			// 1. on réarme le watchdog
			IWDG_ReloadCounter();
			// 2. on met à jour les LED rouge et verte
			GPIO_WriteBit(GPIOC, LED_RED, Bit_SET);
			GPIO_WriteBit(GPIOC, LED_GREEN, Bit_RESET);
		}
	}
}

Utilisation du WWDG

Compte-tenu du faible temps de cycle du WWDG, je n'ai pas trouvé d'exemple d'utilisation aussi simple et parlant que pour le IWDG. Je me contenterai donc des 2 extraits de code suivants.

Configuration et démarrage du WWDG :

// Valeur de rechargement du compteur. Doit être > 0x40 et <= 0x7f.
#define RELOAD_VALUE 0x7f

// Valeur de début de la fenêtre. Avec la valeur choisie,
// le réarmement du watchdog ne peut avoir lieu qu'entre 
// 0x5f et 0x40 (bornes incluses).
#define WINDOW_VALUE 0x60

RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
// Les valeurs valides sont WWDG_Prescaler_[1, 2, 4, 8].
WWDG_SetPrescaler(WWDG_Prescaler_8);
WWDG_SetWindowValue(WINDOW_VALUE);
WWDG_Enable(RELOAD_VALUE);

Réarmement du WWDG :

WWDG_SetCounter(RELOAD_VALUE);

Ce que nous avons appris

Vous savez maintenant :

  • Ce qu'est un watchdog et à quoi il sert.

  • Comment fonctionnent les watchdogs du CH32V003.

  • Utiliser les watchdogs du CH32V003.


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