Cette nouvelle partie est consacrée à la compréhension et aux moyens pour configurer les périphériques que vous trouverez dans la plupart des microcontrôleurs. Vous commencerez par un PWM (modulateur de longueur d’impulsion), puis un ADC (convertisseur analogique) et enfin vous aborderez les bus de communication.
Principes d’un PWM
Commençons par le périphérique désigné sous le terme de PWM pour Pulse Width Moldulation ou autrement dit modulateur à longueur d’impulsion. Ce périphérique permet de produire en sortie un signal périodique avec une impulsion dont on peut contrôler la durée. Le schéma ci-dessous représente ce signal. On y retrouve la période globale du PWM ainsi que sa durée de l’impulsion (duty cycle) notée . Cette durée peut ainsi varier de 0 à .
Un PWM est habituellement couplé à un timer pour lequel un nouveau registre, appelé registre de comparaison (compare register), est utilisé. La période du PWM est ainsi obtenue en configurant la période du timer à l’aide des registres de prescaler et de rechargement. Alors que la durée de l’impulsion est fixée à l’aide du registre de comparaison comme montré sur le schéma ci-dessous.
On voit sur ce schéma que le signal passe à 1 à chaque débordement du timer et est remis à zéro quand le compteur du timer atteint la valeur contenue dans le registre de comparaison.
Remarquez que le registre de comparaison ne peut prendre que des valeurs entre 0 et la valeur du registre de rechargement. Il existe donc un nombre limité de valeurs pour ce registre qui décrit la résolution du PWM.
Mais à quoi ça sert ?
Le signal d’un PWM est souvent utilisé afin de porter une information représentée par la durée de l’impulsion. Un cas d’utilisation classique est le contrôle des servo-moteurs. Ces moteurs sont contrôlés en position, c’est-à-dire que l’on contrôle directement l’angle dans lequel se positionne l’axe du moteur. En fonction du modèle du servo-moteur, le contrôle se fait par un signal d’un PWM ayant une fréquence donnée, généralement de 40HZ ou 50Hz, et dont l’angle à atteindre est proportionnel à la durée de l’impulsion. Ainsi, en faisant varier la durée de l’impulsion, on peut directement faire évoluer l’angle du moteur.
La seconde utilisation a été déjà été exposée dans la partie 1 du chapitre précédent et va consister à utiliser le signal du PWM pour générer un signal pseudo-analogique, c’est-à-dire pour lequel on contrôle la tension en sortie. Nous reviendrons sur ce comportement dans la section suivante.
Configurer un PWM sur le STM32F103
Pour illustrer comment configurer un PWM, vous allez faire varier l’intensité d’une led. Le principe va consister à envoyer le signal d’un PWM sur une led avec une fréquence suffisamment élevée pour que l’œil ne soit pas capable de distinguer les moments où la led est allumée ou éteinte. Par contre, en faisant varier la durée de l’impulsion, l’œil percevra que l’intensité moyenne de la led varie.
Sur un STM32F103 tous les timers peuvent être configurés en PWM. Chacun dispose de quatre canaux indépendants permettant de générer un signal PWM (par contre tous les canaux auront la même période). Ces canaux sont désignés par CH1, CH2, CH3 et CH4 et sont couplés à un registre de comparaison. La figure suivante du manuel RM00008 montre ces canaux et leurs liens avec les registres de comparaison.
Avant d’utiliser un timer en mode PWM, il est aussi nécessaire de savoir sur quelle broche sort le signal produit. Cette information peut se trouver dans la partie sur les fonctions alternatives des GPIO page 177 du manuel RM0008. Ainsi, on apprend par exemple que le CH1 du timer 3 sort sur la broche PA.6.
Vous allez donc dans la suite configurer le canal 1 (CH1) du timer 3 (TIM3) en mode PWM avec une fréquence de 20kHz (période de 50µs). Pour cela, il faut commencer par régler les registres PSC et ARR de TIM3 pour obtenir la fréquence du timer souhaitée. Soit, d’après les explications données dans le chapitre 3 de la partie 3, les valeurs 0 pour PSC et 0xE0F pour ARR.
En partant de nouveau d’un code vide, nous avons donc pour commencer
#include “stm32f10x.h”
int main (void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // validation de l'horloge de TIM3
TIM3->PSC = 0; // Configuration de la période
TIM3->ARR = 0xE0F; // Configuration de la période
while(1)
{
}
return 0;
}
Ajoutons à cela la configuration de la broche PA.6 en sortie et en mode alternate function, soit les lignes :
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // validation de l'horloge de PA
GPIOA->CRL &= ~(0xF << 24); // PA.6 alternate push-pull
GPIOA->CRL |= (0xA << 24); // PA.6 alternate push-pull
Pour configurer le canal CH1 d’un timer en mode PWM, la documentation indique page 386 qu’il faut affecter la valeur 0b110 aux bits OC1M dans le registre CCMR1 du timer. Il suffit donc d’ajouter les lignes
TIM3->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM3->CCMR1 |= TIM_CCMR1_OC1M_1| TIM_CCMR1_OC1M_2;
La documentation indique aussi que le canal CH1 de sortie est validé par le bit CC1E du registre CCER du timer, soit
TIM3->CCER |= TIM_CCER_CC1E;
Enfin, il ne faut pas oublier de lancer le timer avec la ligne
TIM3->CR1 |= TIM_CR1_CEN;
Remarquez que pour l’instant, vous n’avez pas réglé la durée de l’impulsion. Cela se fait simplement en fixant une valeur dans le registre CCR1 du timer. Pour commencer fixons à 20% cette durée, soit une valeur de 719 (20% de la résolution du PWM) :
TIM3->CCR1 = 719;
Il vous est maintenant possible de vérifier le comportement de la PWM en observant en simulation le signal sur la fenêtre Logic Analyzer de uVision.
Un peu de structure
Actuellement votre code doit ressembler à
#include “stm32f10x.h”
int main (void)
{
// Configuration de la broche PA.6
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= ~(0xF << 24); // PA.6 alternate push-pull
GPIOA->CRL |= (0xA << 24); // PA.6 alternate push-pull
//Configuration de la PWM sur le timer 3
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
TIM3->PSC = 0;
TIM3->ARR = 0xE0F;
TIM3->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIM3->CCMR1 |= TIM_CCMR1_OC1M_1| TIM_CCMR1_OC1M_2;
TIM3->CCER |= TIM_CCER_CC1E;
TIM3->CCR1 = 99; // Réglage de l’impulsion
TIM3->CR1 |= TIM_CR1_CEN; // Démarrage de la PWM
while(1)
{
}
return 0;
}
Avant d’ajouter la fonctionnalité permettant de faire varier la durée d’impulsion, vous allez restructurer ce code afin d’en faciliter la lecture et son utilisation. Pour cela, faites des fonctions afin de séparer la configuration des différents périphériques.
void configure_gpio_pa6_alternate_push_pull(){
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
GPIOA->CRL &= ~(0xF << 24);
GPIOA->CRL |= (0xA << 24);
}
void configure_pwm_ch1_20khz(TIM_TypeDef *TIMER){
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
TIMER->PSC = 0;
TIMER->ARR = 0xE0F;
TIMER->CCMR1 &= ~TIM_CCMR1_OC1M_0;
TIMER->CCMR1 |= TIM_CCMR1_OC1M_1| TIM_CCMR1_OC1M_2;
TIMER->CCER |= TIM_CCER_CC1E;
}
void start_timer(TIM_TypeDef *TIMER){
TIMER->CR1 |= TIM_CR1_CEN;
}
void set_pulse_percentage(TIM_TypeDef *TIMER, int pulse){
TIMER->CCR1 = TIMER->ARR*pulse/100;
}
En utilisant ces fonctions, le code de votre main
devient :
#include “stm32f10x.h”
int main (void)
{
configure_gpio_pa6_alternate_push_pull ();
configure_pwm_ch1_20khz(TIM3);
set_pulse_percentage(TIM3, 0x14);
start_timer(TIM3);
while(1)
{
}
return 0;
}
Remarquez que le découpage offre une très faible réutilisabilité car les fonctions sont très spécialisées (configuration d'un port particulier, configuration d'une PWM pour une fréquence donnée, etc.). Il est possible de rendre plus générique ces fonctions, nous verrons un exemple dans les chapitres suivants.
Faire respirer une led
Ce qui manque dans le code précédent est la fonction permettant de faire varier l’intensité de la led. Pour cela, vous allez mettre en place un autre timer, TIM2, qui va régulièrement mettre à jour la valeur de la durée d’impulsion dans sa fonction d’interruption.
Commencez par créer la fonction de configurations de ce nouveau timer avec une période de 10 ms et avec une interruption, soit :
void configure_timer2_with_IT(){
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
TIM2->ARR = 999;
TIM2->PSC = 7199;
TIM2->DIER |= TIM_DIER_UIE;
NVIC->ISER[0] |= NVIC_ISER_SETENA_28;
NVIC->IP[28] |= (7 << 4);
}
et maintenant écrivez la fonction d’interruption pour faire varier l’intensité de la led, autrement dit la durée de l’impulsion de la PWM, soit :
void TIM2_IRQHandler(void) {
static int pulse = 0;
TIM3->SR &= ~TIM_SR_UIF;
pulse = (pulse + 5) % 101;
set_pulse_percentage(TIM3, pulse);
}
Le programme complet est alors :
#include “stm32f10x.h”
int main (void)
{
configure_gpio_pa6_alternate_push_pull();
configure_pwm_ch1_20khz(TIM3);
set_pulse_percentage(TIM3, 0x100);
configure_timer2_with_IT();
start_timer(TIM3);
start_timer(TIM2);
while(1)
{
}
return 0;
}
void TIM2_IRQHandler(void) {
static int pulse = 0;
TIM2->SR &= ~TIM_SR_UIF;
pulse = (pulse + 5) % 101;
set_pulse_percentage(TIM3, pulse);
}
Vous pouvez maintenant observer en simulation dans la fenêtre Logique Analyzer le comportement général de votre PWM. Si vous avez réalisé le montage avec la led, vous pouvez l’observer aussi en réel.
À la fin de ce chapitre, vous êtes capable :
de décrire le fonctionnement d'un PWM,
de configurer et de faire fonctionner un PWM sur un STM32.