Cet article explore les techniques de contrôle d'un servomoteur SG90 à l'aide d'un microcontrôleur de la famille 8051. Le servomoteur SG90, un composant compact et léger, est fréquemment utilisé dans les systèmes embarqués, la robotique et les modèles réduits pour sa capacité à positionner un axe avec précision sur un angle donné.
Le pilotage d'un servomoteur repose sur la modulation de largeur d'impulsion (PWM). Le microcontrôleur 8051, tel que le STC89C52, offre les capacités nécessaires (ports GPIO et temporisateurs) pour générer ces signaux PWM. La variation de la largeur de l'impulsion détermine la position angulaire du servomoteur. Pour un SG90, le signal PWM a généralement une période de 20 millisecondes (ms), avec une largeur d'impulsion variant entre 0.5 ms et 2.5 ms, permettant une rotation typique de 0 à 180 degrés.
Afin de réaliser des mouvements angulaires dans les deux sens (avant et arrière), il est impératif d'ajuster dynamiquement la largeur de cette impulsion. En manipulant la durée de l'état haut du signal PWM au sein de la période fixe, nous pouvons dicter au servomoteur la position souhaitée. Les sections suivantes détaillent comment implémenter ces signaux PWM en utilisant les ressources du microcontrôleur 8051.
Présentation du Servomoteur SG90
Le SG90 est un servomoteur miniature largement adopté dans une multitude de projets électroniques. Il se caractérise par :
- Principe de fonctionnement : Il intègre un petit moteur à courant continu, un réducteur et un circuit de contrôle avec potentiomètre pour le retour de position. Le microcontrôleur envoie un signal PWM qui est interprété par le circuit interne pour ajuster la position de l'arbre via le moteur.
- Caractéristiques clés : - **Compacité :** Ses dimensions réduites et son faible poids le rendent idéal pour les applications où l'espace est limité. - **Amplitude de mouvement :** Il offre généralement une plage de rotation d'environ 180 degrés, ajustable via le signal de commande. - **Précision :** Le SG90 offre une bonne précision et stabilité de positionnement pour sa catégorie. - **Efficacité énergétique :** Sa faible consommation le rend adapté aux systèmes alimentés par batterie. - **Coût abordable :** C'est une solution économique pour le contrôle angulaire.
- Mode de commande : La broche de signal du SG90 reçoit un signal PWM. - Connectez le VCC du servomoteur à une alimentation de 5V et le GND à la masse. - La broche de signal est reliée à un port de sortie numérique du microcontrôleur. - Le microcontrôleur génère un signal PWM avec une période de 20 ms. Une impulsion de 0.5 ms correspond généralement à la position d'un extrême (par exemple, 0°), tandis qu'une impulsion de 2.5 ms correspond à l'autre extrême (par exemple, 180°). Une impulsion de 1.5 ms place l'arbre au centre (90°). Les valeurs précises peuvent varier légèrement entre les modèles et nécessitent une calibration. Le SG90 opère généralement entre 4.8V et 6V. Une alimentation stable et capable de fournir les pics de courant nécessaires au démarrage ou en charge est essentielle.
Implémentation du Contrôle
- Contrôle par Délai Logiciel Cette méthode, également appelée "bit-banging PWM", utilise des fonctions de temporisation pour générer manuellement le signal PWM. Elle est simple à comprendre mais consomme des ressources CPU et sa précision dépend fortement de la fréquence d'horloge du microcontrôleur et de la calibration des fonctions de délai.
#include <reg52.h> // Inclure le fichier d'en-tête pour le 8051 (ou reg51.h)
#include <intrins.h> // Pour la fonction _nop_() si disponible
// Définition de la broche de commande du servomoteur (Port P1, Broche 0)
sbit BROCHE_SIGNAL_SERVO = P1^0;
// Constantes pour le servomoteur SG90 (en microsecondes)
#define PERIODE_SERVO_US 20000 // Période totale du signal PWM (20 ms)
#define IMPULSION_MIN_US 500 // Largeur d'impulsion pour 0° (0.5 ms)
#define IMPULSION_MAX_US 2500 // Largeur d'impulsion pour 180° (2.5 ms)
#define PLAGE_IMPULSION_US (IMPULSION_MAX_US - IMPULSION_MIN_US) // Plage de variation (2.0 ms)
// Fonction de temporisation approximative en microsecondes.
// ATTENTION : Cette fonction est une approximation et doit être calibrée.
// Pour 11.0592 MHz, chaque _nop_() dure environ 1.085 µs.
void microsecondes_de_retard(unsigned int us_duree) {
while (us_duree--) {
_nop_(); // Consomme 1 cycle machine (~1.085 µs @ 11.0592MHz)
_nop_(); // Consomme 1 cycle machine (~1.085 µs @ 11.0592MHz)
// Ajuster le nombre de NOPs ou la boucle interne pour une meilleure précision.
// Ici, nous utilisons 2 NOPs par "microseconde" demandée, ce qui est une approximation.
}
}
// Fonction de temporisation en millisecondes.
// Calibrée approximativement pour 11.0592 MHz.
void millisecondes_de_retard(unsigned int ms_duree) {
unsigned int i, j;
for (i = 0; i < ms_duree; i++) {
// Cette boucle interne doit être calibrée pour prendre environ 1 milliseconde.
// Pour 11.0592 MHz, environ 230 itérations de cette boucle consomment ~1 ms.
for (j = 0; j < 230; j++);
}
}
// Fonction pour positionner le servomoteur à un angle spécifié (0-180 degrés)
// Génère une seule impulsion PWM. Pour maintenir la position, cette fonction
// doit être appelée répétitivement dans une boucle.
void definir_position_angulaire(unsigned char angle_cible) {
unsigned int largeur_impulsion; // Durée de l'état haut du signal en µs
unsigned int temps_faible; // Durée de l'état bas du signal en µs
// Calcul de la largeur d'impulsion en utilisant l'arithmétique entière
// largeur = IMPULSION_MIN + (angle_cible / 180.0) * PLAGE_IMPULSION_US
largeur_impulsion = IMPULSION_MIN_US + (unsigned int)((unsigned long)angle_cible * PLAGE_IMPULSION_US / 180);
// Calcul du temps restant pour compléter la période PWM
temps_faible = PERIODE_SERVO_US - largeur_impulsion;
// Génération de l'impulsion
BROCHE_SIGNAL_SERVO = 1; // Mettre la broche à l'état haut
microsecondes_de_retard(largeur_impulsion / 2); // Retard pour la largeur d'impulsion
microsecondes_de_retard(largeur_impulsion / 2); // Divisé pour potentiellement améliorer la précision des retards
BROCHE_SIGNAL_SERVO = 0; // Mettre la broche à l'état bas
microsecondes_de_retard(temps_faible / 2); // Retard pour le temps faible
microsecondes_de_retard(temps_faible / 2); // Divisé pour potentiellement améliorer la précision des retards
}
void main() {
while (1) {
// Déplacement à 0 degrés
for (unsigned char k = 0; k < 50; k++) { // Répéter l'impulsion pour stabiliser le servo
definir_position_angulaire(0);
}
millisecondes_de_retard(1500); // Pause de 1.5 seconde
// Déplacement à 90 degrés
for (unsigned char k = 0; k < 50; k++) {
definir_position_angulaire(90);
}
millisecondes_de_retard(1500); // Pause de 1.5 seconde
// Déplacement à 180 degrés
for (unsigned char k = 0; k < 50; k++) {
definir_position_angulaire(180);
}
millisecondes_de_retard(1500); // Pause de 1.5 seconde
// Balayage de 180 à 0 degrés
for (unsigned char angle_courant = 180; angle_courant != 255; angle_courant--) { // '!= 255' gère le passage de 0 pour unsigned char
definir_position_angulaire(angle_courant);
microsecondes_de_retard(PERIODE_SERVO_US / 5); // Courte pause entre chaque pas (4 ms)
if (angle_courant == 0) break; // Arrêter si 0 degré est atteint
}
millisecondes_de_retard(1500); // Pause finale
}
}
- Contrôle PWM basé sur Temporisateur Cette approche utilise un temporisateur matériel du microcontrôleur pour générer le signal PWM de manière plus précise et sans monopoliser le CPU avec des boucles de délai. Le temporisateur 0 du 8051 est configuré pour déclencher des interruptions aux moments critiques de la période PWM, permettant ainsi de basculer la broche de commande du servomoteur.
#include <reg52.h> // Inclure le fichier d'en-tête pour le 8051
#include <intrins.h> // Pour la fonction _nop_()
// Fréquence de l'oscillateur du microcontrôleur en Hz
#define FREQUENCE_OSCILLATEUR 11059200UL
// Constantes pour la commande du servomoteur (en microsecondes)
#define PERIODE_SERVO_US 20000 // Période du signal PWM (20 ms)
#define LARGEUR_MIN_US 500 // Impulsion minimale pour 0° (0.5 ms)
#define LARGEUR_MAX_US 2500 // Impulsion maximale pour 180° (2.5 ms)
// Déclaration de la broche de commande du servomoteur
sbit BROCHE_PWM_SERVO = P1^0;
// Variable volatile pour stocker la largeur d'impulsion désirée en microsecondes
volatile unsigned int largeur_impulsion_courante_us = LARGEUR_MIN_US;
// Fonction utilitaire pour configurer le temporisateur 0 pour un délai donné en µs
void charger_valeur_temporisateur(unsigned int microsecondes_a_compter) {
unsigned int valeur_initiale_timer;
// Calculer le nombre de cycles machine (MC) pour le délai désiré.
// 1 MC = 12 / FREQUENCE_OSCILLATEUR secondes.
// valeur_initiale_timer = microsecondes_a_compter * (FREQUENCE_OSCILLATEUR / 12) / 1000000
// Pour éviter les flottants et optimiser:
valeur_initiale_timer = (unsigned int)(((unsigned long)microsecondes_a_compter * FREQUENCE_OSCILLATEUR) / 12000000UL);
// Le temporisateur compte de `65536 - valeur_initiale_timer` jusqu'à `65536` pour déclencher une interruption.
// Si `valeur_initiale_timer` est trop grand, cela peut déborder. On limite au besoin.
if (valeur_initiale_timer == 0) valeur_initiale_timer = 1; // Éviter une durée nulle
if (valeur_initiale_timer > 65535) valeur_initiale_timer = 65535; // Limiter au maximum pour un timer 16-bit
valeur_initiale_timer = 65536 - valeur_initiale_timer; // Valeur à pré-charger dans TH0/TL0
TH0 = (unsigned char)(valeur_initiale_timer / 256);
TL0 = (unsigned char)(valeur_initiale_timer % 256);
}
// Initialisation du temporisateur 0 pour la génération PWM
void configurer_temporisateur_pwm() {
TMOD |= 0x01; // Configurer le Timer0 en Mode 1 (16 bits)
EA = 1; // Activer les interruptions globales
ET0 = 1; // Activer l'interruption du Timer0
BROCHE_PWM_SERVO = 0; // S'assurer que la broche est initialement à l'état bas
charger_valeur_temporisateur(PERIODE_SERVO_US - LARGEUR_MIN_US); // Charger le timer pour le premier état bas
TR0 = 1; // Démarrer le Timer0
}
// Routine de service d'interruption (ISR) du Temporisateur 0
void isr_temporisateur_zero() interrupt 1 {
TR0 = 0; // Arrêter le temporisateur pendant la reconfiguration
if (BROCHE_PWM_SERVO == 1) { // Si la broche était à l'état haut (fin de l'impulsion)
BROCHE_PWM_SERVO = 0; // Passer la broche à l'état bas
// Recharger le temporisateur pour la durée restante de la période PWM
charger_valeur_temporisateur(PERIODE_SERVO_US - largeur_impulsion_courante_us);
} else { // Si la broche était à l'état bas (début d'une nouvelle période)
BROCHE_PWM_SERVO = 1; // Passer la broche à l'état haut
// Recharger le temporisateur pour la durée de l'impulsion (état haut)
charger_valeur_temporisateur(largeur_impulsion_courante_us);
}
TR0 = 1; // Redémarrer le temporisateur
}
// Fonction pour définir l'angle du servomoteur
void regler_angle_servo(unsigned char angle_en_degres) {
// Calculer la largeur d'impulsion correspondante en microsecondes
// Largeur = MIN_US + (angle / 180.0) * (MAX_US - MIN_US)
largeur_impulsion_courante_us = LARGEUR_MIN_US + (unsigned int)((unsigned long)angle_en_degres * (LARGEUR_MAX_US - LARGEUR_MIN_US) / 180);
// S'assurer que la valeur reste dans les limites de sécurité
if (largeur_impulsion_courante_us < LARGEUR_MIN_US) largeur_impulsion_courante_us = LARGEUR_MIN_US;
if (largeur_impulsion_courante_us > LARGEUR_MAX_US) largeur_impulsion_courante_us = LARGEUR_MAX_US;
}
// Fonction de temporisation simple en millisecondes pour des pauses dans le programme principal
void delai_simple_ms(unsigned int duree_ms) {
unsigned int i, j;
for (i = 0; i < duree_ms; i++) {
for (j = 0; j < 230; j++); // Calibré pour 1ms @ 11.0592 MHz
}
}
// Programme principal
void main() {
configurer_temporisateur_pwm(); // Initialise le Temporisateur 0 pour le PWM
while (1) {
// Balayage angulaire du servomoteur de 0 à 180 degrés
for (unsigned char angle = 0; angle <= 180; angle += 5) {
regler_angle_servo(angle);
delai_simple_ms(50); // Petite pause pour un mouvement fluide
}
delai_simple_ms(1000); // Pause d'une seconde à 180 degrés
// Balayage angulaire inverse de 180 à 0 degrés
for (unsigned char angle = 180; angle != 255; angle -= 5) { // '!= 255' gère le passage de 0 pour unsigned char
regler_angle_servo(angle);
delai_simple_ms(50); // Petite pause pour un mouvement fluide
if (angle == 0) break; // Sortir explicitement après 0 degré
}
delai_simple_ms(1000); // Pause d'une seconde à 0 degrés
}
}