Tutorial · Intermediário · 35 min
Temporizadores do 8051 e PWM por software para motores
O 8051 não tem PWM por hardware; crie-o com uma interrupção do Temporizador 0. Veja a recarga, a frequência, a resolução e o acionamento de uma ponte H.
Introdução
Um Arduino define a velocidade de um motor com analogWrite(pin, 180). Por trás dessa única chamada há um periférico dedicado gerando continuamente uma forma de onda, sem custo para a CPU enquanto ela faz outra coisa.
O 8051 não tem esse periférico. Nada no chip pode produzir uma forma de onda com ciclo de trabalho variável. Se quiser operar um motor a 70%, você mesmo terá de gerar a forma de onda a partir de uma interrupção de temporizador e pagará por isso em tempo de CPU. Construir essa solução é uma das atividades mais reveladoras que você pode fazer nesse chip, porque torna visível exatamente o que analogWrite() fazia por você.
Este tutorial pressupõe que você já conheceu as portas. Se o PWM em si for novo, o tutorial sobre PWM e ponte H explica por que ligar e desligar rapidamente um motor equivale a variar a tensão aplicada.
Os temporizadores disponíveis
O 8051 clássico tem dois temporizadores/contadores de 16 bits, Temporizador 0 e Temporizador 1; o AT89S52 acrescenta um terceiro. Cada um conta ciclos de máquina — doze períodos do oscilador cada — portanto, a 11,0592 MHz um temporizador avança a cada 1,085 µs.
Na prática, um deles vem de graça. O Temporizador 1 quase sempre é reservado para a porta serial, que deriva a taxa de baud dos transbordamentos do Temporizador 1. Isso deixa o Temporizador 0 como seu temporizador de uso geral, e é nele que você constrói o PWM.
Dois registradores os configuram:
TMODescolhe o modo. O nibble baixo é o Temporizador 0 e o nibble alto é o Temporizador 1.TCONguarda os bits de controle dos temporizadores (TR0,TR1) e as flags de transbordamento (TF0,TF1).
O Temporizador 0 tem quatro modos, mas apenas um é ideal para uma contagem periódica:
| Modo | Nibble baixo de TMOD |
O que faz |
|---|---|---|
| 0 | 0x00 |
Contagem de 13 bits — relíquia de compatibilidade |
| 1 | 0x01 |
Contagem de 16 bits, com recarga manual a cada transbordamento |
| 2 | 0x02 |
Contagem de 8 bits com recarga automática |
| 3 | 0x03 |
Divide o Temporizador 0 em dois temporizadores de 8 bits |
O modo 2 é o que você quer. No transbordamento, o hardware copia TH0 de volta para TL0 por conta própria, então o período permanece exato. O modo 1 faz sua rotina de serviço de interrupção recarregar a contagem, e cada ciclo de máquina entre o transbordamento e sua escrita acumula um erro que cresce continuamente.
Configurando a contagem periódica
No modo 2, o temporizador conta de TH0 até 255 e então transborda, portanto o período é a distância que ele precisa percorrer:
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
Habilitá-lo leva cinco linhas:
#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
Esse TMOD & 0xF0 importa. TMOD não é endereçável por bit, então escrever TMOD = 0x02 também apaga os bits de modo do Temporizador 1 — e, se a UART estiver rodando, você acabou de interromper a saída serial enquanto configura um motor.
PWM a partir de um contador
Com uma interrupção periódica em mãos, basta manter uma contagem. Conte os tiques até atingir a duração de período N e mantenha a saída alta enquanto a contagem estiver abaixo do valor de ciclo de trabalho:
#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 é o vetor do Temporizador 0 (endereço 0x000B). A flag de transbordamento TF0 é limpa pelo hardware quando o processador desvia para a rotina, portanto, ao contrário das flags de temporizadores que você consulta manualmente, não há nada para zerar.
Para definir uma velocidade, basta atribuir um valor a uma variável:
duty_a = 14; // 14/20 = 70% duty
A escolha que você não pode evitar
Frequência do PWM, resolução do ciclo de trabalho e carga de CPU estão ligados por uma única relação:
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)
Mantenha C em 100 e varie N: a resolução é conquistada à custa da frequência:
N |
Resolução do ciclo de trabalho | Frequência do PWM | Interrupções/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 |
A taxa de interrupção nunca muda, porque depende apenas de C. O que muda é quantas contagens você gasta antes de o padrão se repetir — e abaixo de aproximadamente 100 Hz um pequeno motor CC para de integrar os pulsos e começa a comutar entre eles de forma audível.
Então dê o próximo passo óbvio: mantenha 460,8 Hz e obtenha resolução de 1% definindo N = 100 e C = 20. A aritmética concorda — e o chip morre. Em C = 20, a interrupção dispara a cada 20 ciclos de máquina, enquanto a ISR acima precisa de cerca de 25 apenas para preservar registradores, comparar e retornar. Ela nunca termina antes de a próxima chegar, e nada mais é executado.
Esse é o teto real. Com uma ISR custando cerca de 25 ciclos de máquina, C = 100 gasta um quarto da CPU com PWM, e C = 50 gasta metade. O PWM por hardware em um Arduino custa zero, em qualquer frequência e resolução. Esta única comparação vale mais do que qualquer tabela de recursos.
Duas coisas aumentam a folga de execução de forma barata: mude para o banco de registradores 1 dentro da ISR (using 1), para que o compilador troque dois bits em vez de empilhar oito registradores, e mantenha a ISR apenas contando e escrevendo nos pinos. Tudo que exigir cálculo pertence a main.
Acionando os motores
Uma ponte H recebe duas entradas de direção e uma de habilitação por motor. Para cada motor, os pinos IN1 e IN2 definem o sentido de rotação, enquanto o pino de habilitação recebe o sinal PWM:
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;
}
Um segundo motor é uma segunda variável de ciclo de trabalho e uma segunda linha de saída na mesma ISR — o contador de tiques é compartilhado, portanto os dois canais permanecem em sincronia:
void timer0_isr(void) interrupt 1 using 1 {
if (++tick >= PWM_LEVELS) tick = 0;
ENA = (tick < duty_a);
ENB = (tick < duty_b);
}
Um detalhe de ligação específico deste chip. Como visto no tutorial de GPIO, um pino do 8051 fornece corrente de apenas dezenas de microampere. Isso basta para uma entrada CMOS, mas um L298N ou L293D tem entradas bipolares que podem consumir até 100 µA em nível lógico alto — exatamente no limite do que o pino pode fornecer. Um resistor de pull-up de 4,7 kΩ de cada entrada do driver de motor para +5 V elimina a ambiguidade por poucos centavos e transforma uma placa que funciona na bancada, mas não no robô, em uma que simplesmente funciona.
Quando algo dá errado
- O motor vibra, mas mal gira. Sua frequência de PWM está baixa demais, ou o ciclo de trabalho está abaixo do limite de atrito estático do motor. Aumente a frequência para acima de cerca de 100 Hz e espere que um motor real precise de 25% a 40% antes de se mover.
- A saída serial quebrou quando você adicionou o temporizador. Você escreveu
TMODpor inteiro e limpou os bits de modo do Temporizador 1. Aplique uma máscara:TMOD = (TMOD & 0xF0) | 0x02;. - O período do PWM deriva. Você está no modo 1 e recarregando manualmente. Mude para o modo 2 e deixe o hardware fazer isso.
- O programa inteiro fica lento. A ISR é cara demais para a taxa de tiques. Aumente
TH0na direção de 200, adicioneusing 1e tire qualquer aritmética da interrupção. - Mudanças no ciclo de trabalho causam transitórios na saída.
duty_aestá sendo escrito pormainenquanto a ISR o lê. Uma escrita de um byte é atômica nesta CPU, então umcharé seguro, mas qualquer valor maior precisa da interrupção desabilitada durante a atualização. - Um motor ignora você completamente. Verifique se o pino de habilitação é realmente aquele que a ISR aciona e se as entradas do driver de motor têm os respectivos resistores de pull-up.
Explore o grafo
Faz parte destas montagens
Projetos e caminhos de aprendizagem que incluem este tutorial.
Leitura complementar