Tutoriel · Intermédiaire · 35 min de lecture
Timers 8051 et PWM logicielle pour la commande de moteurs
Le 8051 n'a pas de matériel PWM, donc on la construit au Timer 0. Rechargement automatique, compromis fréquence-résolution, et commande d'un pont en H.
Introduction
Un Arduino règle une vitesse de moteur avec analogWrite(pin, 180). Derrière cet appel unique se tient un périphérique matériel qui génère une forme d’onde en continu, gratuitement, pendant que le CPU fait autre chose.
Le 8051 n’a pas de tel périphérique. Rien sur la puce ne peut produire une forme d’onde à rapport cyclique variable. Si vous voulez faire tourner un moteur à 70 %, vous devez générer la forme d’onde vous-même, depuis une interruption de timer, et vous le paierez en temps CPU. Le construire est la chose la plus instructive à faire sur cette puce, parce que cela rend visible exactement ce que analogWrite() faisait pour vous.
Ceci suppose que vous avez déjà rencontré les ports. Si la PWM elle-même est nouvelle, le tutoriel sur la PWM et le pont en H explique pourquoi commuter un moteur vite fait équivaut à varier sa tension.
Les timores dont vous disposez
Le 8051 classique a deux timores-compteurs 16 bits, Timer 0 et Timer 1 ; l’AT89S52 en ajoute un troisième. Chacun compte des cycles machine — douze périodes d’oscillateur chacun — donc à 11,0592 MHz un timer tic à chaque 1,085 µs.
En pratique vous n’en avez qu’un de libre. Le Timer 1 est presque toujours réquisitionné par le port série, qui dérive son débit des débordements du Timer 1. Cela laisse le Timer 0 comme timer à usage général, et c’est celui sur lequel construire la PWM.
Deux registres les configurent :
TMODchoisit le mode. Le quartet bas est le Timer 0, le quartet haut le Timer 1.TCONtient les bits de marche (TR0,TR1) et les drapeaux de débordement (TF0,TF1).
Le Timer 0 a quatre modes, mais un seul convient pour un tic périodique :
| Mode | Quartet bas de TMOD |
Ce qu’il fait |
|---|---|---|
| 0 | 0x00 |
Comptage 13 bits — une relique de compatibilité |
| 1 | 0x01 |
Comptage 16 bits, rechargé à la main à chaque débordement |
| 2 | 0x02 |
Comptage 8 bits avec rechargement automatique |
| 3 | 0x03 |
Scinde le Timer 0 en deux timores 8 bits |
Le mode 2 est celui qu’il vous faut. Au débordement, le matériel recopie TH0 dans TL0 tout seul, donc la période reste exacte. Le mode 1 fait recharger le comptage par votre routine d’interruption, et chaque cycle machine entre le débordement et votre écriture est un cycle de dérive qui s’accumule indéfiniment.
Régler le tic
En mode 2, le timer compte de TH0 jusqu’à 255 puis déborde, donc la période est la distance qu’il doit parcourir :
cycles per tick = 256 − TH0
tick period = (256 − TH0) × 12 / f_osc
TH0 = 156, f_osc = 11.0592 MHz
cycles = 100
tick = 100 × 12 / 11 059 200 = 108.5 µs
L’activer prend cinq lignes :
#include <reg52.h>
TMOD = (TMOD & 0xF0) | 0x02; // Timer 0 -> mode 2, leave Timer 1 alone
TH0 = 156; // auto-reload value: 100 machine cycles
TL0 = 156; // first count starts from the same place
ET0 = 1; // enable the Timer 0 interrupt
EA = 1; // global interrupt enable
TR0 = 1; // start counting
Ce TMOD & 0xF0 compte. TMOD n’est pas adressable bit à bit, donc écrire TMOD = 0x02 écrase les bits de mode du Timer 1 avec — et si l’UART tourne, vous venez de casser votre sortie série en configurant un moteur.
De la PWM à partir d’un compteur
Avec une interruption périodique en main, la PWM est de la comptabilité. Comptez les tics jusqu’à une longueur de période N, et maintenez la sortie haute tant que le compte est sous la valeur du rapport :
#define PWM_LEVELS 20 // ticks per PWM period
sbit ENA = P2^0; // H-bridge enable — the PWM'd pin
volatile unsigned char tick = 0;
volatile unsigned char duty_a = 0; // 0 .. PWM_LEVELS
void timer0_isr(void) interrupt 1 {
if (++tick >= PWM_LEVELS) tick = 0;
ENA = (tick < duty_a); // high for the first duty_a ticks of each period
}
interrupt 1 est le vecteur du Timer 0 (adresse 0x000B). Le drapeau de débordement TF0 est effacé par le matériel lors de l’aiguillage, donc contrairement aux drapeaux que vous scrutez à la main, il n’y a rien à réarmer.
Régler une vitesse devient juste une variable :
duty_a = 14; // 14/20 = 70% duty
Le compromis auquel on n’échappe pas
Fréquence de PWM, résolution de rapport et charge CPU sont une relation à trois noms :
f_pwm = f_osc / (12 × C × N)
C = machine cycles per tick (256 − TH0)
N = ticks per PWM period (also your number of duty steps)
Fixez C à 100 et variez N, et la résolution s’achète avec la fréquence :
N |
Résolution de rapport | Fréquence PWM | Interruptions/s |
|---|---|---|---|
| 10 | 10% | 921.6 Hz | 9 216 |
| 20 | 5% | 460.8 Hz | 9 216 |
| 50 | 2% | 184.3 Hz | 9 216 |
| 100 | 1% | 92.2 Hz | 9 216 |
Le rythme d’interruption ne change jamais, parce qu’il ne dépend que de C. Ce qui change est combien de tics vous dépensez avant que le motif se répète — et sous environ 100 Hz un petit moteur CC cesse de moyenner les impulsions et commence à les suivre une à une, de façon audible.
Prenez donc l’étape suivante évidente : gardez 460,8 Hz et obtenez 1 % de résolution en posant N = 100 et C = 20. L’arithmétique est d’accord — et la puce meurt. À C = 20 l’interruption part tous les 20 cycles machine, tandis que l’ISR ci-dessus en demande environ 25 juste pour sauver les registres, comparer et revenir. Elle ne finit jamais avant l’arrivée de la suivante, et rien d’autre ne s’exécute jamais.
Voilà le vrai plafond. Avec une ISR coûtant environ 25 cycles machine, C = 100 dépense un quart du CPU en PWM, et C = 50 la moitié. La PWM matérielle sur un Arduino coûte zéro, à n’importe quelle fréquence et résolution. Cette seule comparaison vaut plus que tout tableau de fonctionnalités.
Deux choses achètent de la marge à bon compte : basculez vers le banc de registres 1 dans l’ISR (using 1) pour que le compilateur permute deux bits au lieu d’empiler huit registres, et limitez l’ISR au comptage et aux écritures de broches. Tout ce qui calcule appartient à main.
Commander les moteurs
Un pont en H prend deux entrées de sens et une validation par moteur. Alimentez les broches de sens depuis des broches de port ordinaires et la validation depuis votre broche modulée :
sbit IN1 = P2^1;
sbit IN2 = P2^2;
sbit ENA = P2^0; // PWM'd by the ISR
void motor_forward(unsigned char duty) {
IN1 = 1; IN2 = 0;
duty_a = duty;
}
void motor_reverse(unsigned char duty) {
IN1 = 0; IN2 = 1;
duty_a = duty;
}
void motor_stop(void) {
duty_a = 0;
IN1 = 0; IN2 = 0;
}
Un second moteur est une seconde variable de rapport et une seconde ligne de sortie dans la même ISR — le compteur de tics est partagé, donc les deux voies restent en phase :
void timer0_isr(void) interrupt 1 using 1 {
if (++tick >= PWM_LEVELS) tick = 0;
ENA = (tick < duty_a);
ENB = (tick < duty_b);
}
Un détail de câblage propre à cette puce. Comme couvert dans le tutoriel GPIO, une broche 8051 ne source que des dizaines de microampères. C’est largement assez pour une entrée CMOS, mais un L298N ou L293D a des entrées bipolaires qui peuvent tirer jusqu’à 100 µA au niveau haut logique — pile à la limite de ce que la broche peut fournir. Un rappel de 4,7 kΩ de chaque entrée du driver vers +5 V lève l’ambiguïté pour quelques centimes, et transforme une carte qui marche sur l’établi mais pas sur le robot en une qui marche tout simplement.
Quand ça tourne mal
- Le moteur bourdonne mais tourne à peine. Votre fréquence PWM est trop basse, ou le rapport est sous le seuil de décollage du moteur. Montez la fréquence au-dessus d’environ 100 Hz et attendez-vous à ce qu’un vrai moteur demande 25 à 40 % avant de bouger du tout.
- La sortie série a cassé quand vous avez ajouté le timer. Vous avez écrit
TMODen bloc et effacé les bits de mode du Timer 1. Masquez :TMOD = (TMOD & 0xF0) | 0x02;. - La période PWM dérive. Vous êtes en mode 1 avec rechargement à la main. Passez en mode 2 et laissez le matériel le faire.
- Tout le programme rampe. L’ISR est trop coûteuse pour le rythme de tics. Montez
TH0vers 200, ajoutezusing 1, et sortez tout calcul de l’interruption. - Les changements de rapport glitchent la sortie.
duty_aest écrite parmainpendant que l’ISR la lit. Une écriture d’un octet est atomique sur ce CPU donc uncharest sûr, mais tout ce qui est plus large exige l’interruption masquée autour de la mise à jour. - Un moteur vous ignore entièrement. Vérifiez que la broche de validation est bien celle que l’ISR pilote, et que les entrées du driver ont leurs rappels.
Explorer le graphe
Fait partie de ces réalisations
Les projets et parcours d'apprentissage qui incluent ce tutoriel.
Pour aller plus loin