Você está na página 1de 9

Microcontroladores

AULA 08
Prof. Rodrigo Rech

Aula 08: Temporizadores e PWM

1. Timers

O timer é um periférico do microcontrolador que trabalha de forma independente à CPU, ou


seja, seu funcionamento não depende, necessariamente, que esteja rodando um programa no
processador, fazendo com que esta seja a forma mais eficiente de se realizar contagens. Mas
isto não significa que os blocos não se comunicam internamente, os temporizadores possuem
flags de sinalização que avisam a CPU o momento em que terminaram a sua contagem. Este
flag é chamado de OVERFLOW.

Prescaler Registrador de
(Divisor de contagem do Interrupção
Frequência) timer

Entrada de clock do
Frequência dividida
relógio

Figura 1: Diagrama de funcionamento de um timer

No AVR Atmega328P existem 3 timers (2 timers de 8 bits e um timer de 16 bits). O número


de bits de um timer determina a quantidade de vezes que ele deve ser incrementado até que ocorra
um estouro. Considerando um timer de 8 bits, é necessário que ele seja incrementado 256 vezes
para que o estouro ocorra, já em um timer de 16 bits, é necessário que ele seja incrementado 65536
vezes para que ocorra o overflow.

Para que seja possível incrementar o timer de uma forma mais lenta existe um componente
chamado “Prescaler”, ou simplesmente, divisor de frequência. Com ele é possível inserir um fator
de divisão antes de incrementar o temporizador. Este fator pode ser de 1, 8, 64, 256 ou 1024.

Considerando o diagrama apresentado na figura 1, um clock de 16MHz, o divisor de


frequência ajustado para 1 e um timer de 8 bits, temos o seguinte tempo para estouro do timer:

256 ∗ 𝑃𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 256 ∗ 1


𝑡𝑒𝑚𝑝𝑜 = = = 𝟏𝟔𝒖𝒔
𝐹𝑜𝑠𝑐 16000000
Este é um tempo muito pequeno para geração de interrupção. Agora, vamos mudar para um
clock de 16MHz, o divisor ajustado para 1024 e o mesmo timer de 8 bits:

256 ∗ 𝑃𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 256 ∗ 1024


𝑡𝑒𝑚𝑝𝑜 = = = 𝟏𝟔, 𝟑𝟖𝟒𝒎𝒔
𝐹𝑜𝑠𝑐 16000000
Considerando o último exemplo, este é o maior tempo que um timer de 8 bits pode contar
utilizando como base um oscilador de 16MHz. Se utilizarmos o mesmo exemplo, mas agora
considerando um timer de 16 bits, o valor será de:

65536 ∗ 𝑃𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 65536 ∗ 1024


𝑡𝑒𝑚𝑝𝑜 = = = 𝟒, 𝟏𝟗𝒔
𝐹𝑜𝑠𝑐 16000000
Mesmo o valor máximo de um timer de 8 bits ser pequeno (na ordem de poucos ms), é possível,
por meio de técnicas de software, aumentar o valor desejado.

É possível, também, alterar o valor de início da contagem de um timer, fazendo com que o
valor final seja menor do que o valor máximo, por exemplo: é necessário que o estouro do timer de
8 bits aconteça com um tempo de 10ms, então podemos utilizar o seguinte cálculo:
𝑡𝑒𝑚𝑝𝑜𝑑𝑒𝑠𝑒𝑗𝑎𝑑𝑜 ∗𝐹𝑜𝑠𝑐 0,01∗16000000
𝑽𝒂𝒍𝒐𝒓 𝒊𝒏𝒊𝒄𝒊𝒂𝒍 𝒅𝒆 𝒄𝒐𝒏𝒕𝒂𝒈𝒆𝒎 = 256 − = 256 − = 98,75 ≅ 𝟗𝟗
𝑃𝑟𝑒𝑠𝑐𝑎𝑙𝑒𝑟 1024

Neste caso, iniciamos a contagem do timer em 99 e não em 0, e o estouro ainda ocorrerá


quando a contagem chegar em 256, fazendo com que o tempo final resultante seja menor.

1.1. Timers no AVR

No AVR são encontrados os Timers 0 e 2 (8 bits) e o Timer 1 (16 bits). A configuração de


todos é semelhante, mudando poucos aspectos nos nomes dos registradores. A figura 2 ilustra o
diagrama em blocos do Timer 0.

2 1

Figura 2: Diagrama em blocos do Timer 0

No bloco “1” é feita a seleção da fonte de clock. Caso a fonte escolhida ser a “clk_I/O”, o
incremento do timer será realizado a partir de um pino do microcontrolador, transformando-o em
um contador de eventos. Caso a fonte escolhida ser o “T/C oscilator”, a fonte será o oscilador.

O bloco “2” é responsável pelo modo de funcionamento do Timer. Pelo fato de existirem várias
funções internas, como o PWM por exemplo, é necessário configurá-lo antes do uso. Para o
funcionamento normal do timer (figura 1), o modo de funcionamento é o “0”.

E o bloco “3” representa o registrador de contagem do timer, é nele que estão os 8 bits (Timers
0 e 2) ou os 16 bits (Timer 1). Sempre que o valor deste registrador ultrapassa seu limite, o evento
de overflow é gerado e ele é zerado.
Para a configuração do timer devem ser utilizados até 7 registradores: TCCRnA, TCCRnB,
OCRnA, OCRnB, TCNTn, TIMSKn e TIFRn, sendo “n” o número do timer que será utilizado.
Para este primeiro momento, para configurar o timer no modo normal, utilizaremos apenas 3 e os
demais podem ser consultados no datasheet do componente ou no livro indicado.

1. TCCRnB (Timer/Counter Register B): Utilizado para configurar o modo de geração de


formas de onda, fator de divisão do Prescaler e borda de acionamento quando operado como
contador:

OBS: Os valores dos prescalers para cada timer são diferentes, portanto, consulte o datasheet
para verificar as configurações dos timers 1 e 2.

2. TCNTn (Timer/Counter Register): Registrador que contém o valor da contagem:

3. TIMSKn (Timer/Counter Interrupt Mask Register): Responsável por habilitar ou desabilitar


as interrupções dos timers. TOIEn (hab./des. Interrupção por Overflow); OCIEnA e OCIEnB
(hab./des. Interrupção dos registradores de comparação).

4. TIFRn (Timer/Counter n Interrupt Flag Register): Contém os flags de estouro dos timers.
Exemplo 1: Configurando o Timer 0 para um estouro no tempo máximo (16,384ms)

#include <avr/io.h>
#include <avr/interrupt.h>

#define cpl_bit(y,bit) (y^=(1<<bit))


#define LED PD5

ISR(TIMER0_OVF_vect) //interrupção do TC0


{
cpl_bit(PORTD, LED);
}

int main() {
DDRD = 0b00100000; //somente pino do LED como saída
PORTD = 0b00000000; //apaga LED e habilita pull-ups nos pinos não utilizados
TCCR0B = 0b00000101; //TC0 com prescaler de 1024, a 16 MHz gera uma interrupção a cada 16,384 ms
TIMSK0 = 0b00000001; //habilita a interrupção do TC0
sei(); //habilita a chave de interrupção global

while (1) {
/*A cada estouro do Timer 0 o programa desvia para ISR(TIMER0_OVF_vect)*/
}
}

Exemplo 2: Criando um tempo de 1 segundo com o Timer 1 (16 bits).

#include <avr/io.h>
#include <avr/interrupt.h>

#define cpl_bit(y,bit) (y^=(1<<bit)) //troca o estado lógico do bit x da variável Y


#define LED PD5

ISR(TIMER1_OVF_vect) //interrupção do TC1


{
TCNT1 = 49911; //Recarrega o registrador para gerar 1s novamente
cpl_bit(PORTD, LED); //Inverte o estado do LED
}

int main() {
DDRD = 0b00100000; //somente pino do LED como saída
PORTD = 0b00000000; //apaga LED e habilita pull-ups nos pinos não utilizados
TCCR1B = 0b00000101; //TC1 com prescaler de 1024, a 16 MHz gera uma interrupção a cada 1s
TCNT1 = 49911; //Inicia a contagem em 49911 para, no final, gerar 1s
TIMSK1 = 0b00000001; //habilita a interrupção do TC1
sei(); //habilita a chave de interrupção global

while (1) {
/*A cada estouro do Timer 1 o programa desvia para ISR(TIMER1_OVF_vect)*/
}
}
Exemplo 3: Utilizando 2 timers simultaneamente. Timer 0 (inverte o pino a cada 500ms) e Timer
1 (inverte o pino a cada 1s).

#include <avr/io.h>
#include <avr/interrupt.h>

#define cpl_bit(y,bit) (y^=(1<<bit)) //troca o estado lógico do bit x da variável Y


#define LED2 PD5
#define LED1 PD3

unsigned int contador = 0;

ISR(TIMER1_OVF_vect)
{
TCNT1 = 49911; //Recarrega o registrador para gerar 1s novamente
cpl_bit(PORTD, LED2); //Inverte o estado do LED
}

ISR(TIMER0_OVF_vect)
{
contador++;
TCNT0 = 100; //Recarrega o registrador para gerar 10ms novamente
if(contador == 50){
contador = 0;
cpl_bit(PORTD, LED1);
}
}

int main() {
DDRD = 0b00101000;
PORTD = 0b00000000;

TCCR1B = 0b00000101; //TC1 com prescaler de 1024


TCNT1 = 49911; //Inicia a contagem em 49911 para, no final, gerar 1s
TIMSK1 = 0b00000001; //habilita a interrupção do TC1

TCCR0B = 0b00000101; //TC0 com prescaler de 1024


TCNT0 = 100; //Inicia a contagem em 100 para, no final, gerar 10ms
TIMSK0 = 0b00000001; //habilita a interrupção do TC0
sei(); //habilita a chave de interrupção global

while (1) {

}
}
Exemplo 4: Criando vários tempos diferentes com apenas um Timer:

#include <avr/io.h>
#include <avr/interrupt.h>

#define cpl_bit(y,bit) (y^=(1<<bit))

unsigned int millis = 0;

ISR(TIMER0_OVF_vect)
{
TCNT0 = 99; //Recarrega o Timer 0 para que a contagem seja 10ms novamente
millis++; //Incrementa a variável millis a cada 10ms
}

int main() {

//variáveis para controle de temporização


unsigned int tempo_atual = 0;
unsigned int tempo_passado_led0 = 0;
unsigned int tempo_passado_led1 = 0;
unsigned int tempo_passado_led2 = 0;

DDRD = 0b11111111;
PORTD = 0b00000000;

TCCR0B = 0b00000101; //TC0 com prescaler de 1024


TCNT0 = 99; //Inicia a contagem em 100 para, no final, gerar 10ms
TIMSK0 = 0b00000001; //habilita a interrupção do TC0
sei(); //habilita a chave de interrupção global

while (1) {

tempo_atual = millis; //Armazena o tempo decorrido atual (múltiplos de 10ms)

if((tempo_atual - tempo_passado_led0) >= 50){ //Verifica se passou 500ms


cpl_bit(PORTD, 0); //Inverte o estado do LED0
tempo_passado_led0 = tempo_atual; //Armazena o tempo atual
}

if((tempo_atual - tempo_passado_led1) >= 100){ //Verifica se passou 1s


cpl_bit(PORTD, 1);
tempo_passado_led1 = tempo_atual;
}

if((tempo_atual - tempo_passado_led2) >= 200){ //Verifica se passou 2s


cpl_bit(PORTD, 2);
tempo_passado_led2 = tempo_atual;
}
}
}
2. Criando um PWM via software

No AVR e na grande maioria dos microcontroladores, as saídas analógicas não fornecem


um sinal DC no nível desejado (estes são obtidos por meio de conversores Digital-Analógicos ou
DACs), eles utilizam uma técnica denominada PWM (Pulse Width Modulation), na qual é
possível obter valores médios de tensão a partir do chaveamento de um pino em alta frequência. A
figura 3 ilustra como este controle é realizado.

Figura 3: Funcionamento do PWM


Fonte: http://tot.eng.br/controlar-brilho-led-arduino-pwm/

É possível notar na figura 2 que, quanto maior o tempo ativo, isto é, ligado, maior é a
intensidade do LED. Este efeito ocorre porque a frequência de chaveamento (ligado-desligado) é
alta e o componente “enxerga” este sinal como uma tensão média sobre ele.

Para se determinar o nível médio de tensão sob a carga, é utilizado o seguinte cálculo:

𝑣𝑚𝑒𝑑𝑖𝑎 (𝑉 ) = 𝑉𝐶𝐶𝑀𝑎𝑥 ∗ 𝐶𝑖𝑐𝑙𝑜𝐴𝑡𝑖𝑣𝑜(%)

Outro detalhe importante sobre o PWM é que sua frequência deve ser determinada de acordo
com a carga que será controlada. Comumente cargas resistivas e semicondutores costumam
utilizar frequências altas (na ordem de KHz), já cargas indutivas, como motores, o ideal é que se
utilize frequências baixas, pois a resposta do componente é mais lenta.

Cada um dos Timers do AVR permite a geração de 2 PWMs em pinos específicos, porém, para
isso, é necessária uma configuração dos limites máximos e mínimos da contagem dos timers, o
que exige um conhecimento um pouco maior sobre este periférico. A proposta deste capítulo é realizar
a configuração do modo normal do Timer para se obter, em qualquer pino do microcontrolador,
um sinal de PWM configurável. Para configuração dos timers no modo de PWM, consulte o
capítulo 9 do livro “AVR e Arduino: Técnicas de projetos”.
Passos para se desenvolver um PWM via software:

1-Defina a frequência e o período de trabalho desejado, por exemplo 1KHz (1ms);

2- Defina a resolução desejada para este PWM, isto é, em quantas partes o ciclo ativo será
dividido. Neste exemplo, usaremos 100, assim a resolução ficará entre 0 e 100%;

3- Configure um timer para que ele gere uma interrupção respeitando o seguinte cálculo:

𝑃𝑒𝑟í𝑜𝑑𝑜𝑃𝑊𝑀 0,001
𝑃𝑒𝑟í𝑜𝑑𝑜𝑒𝑠𝑡𝑜𝑢𝑟𝑜 = = = 10𝑢𝑠
𝑟𝑒𝑠𝑜𝑙𝑢çã𝑜 100

4- Na interrupção, incremente uma variável de contagem e ative o pino desejado quando ela
atingir o valor da resolução e desligue o pino quando a contagem atingir o ciclo ativo
desejado.

Exemplo 5: Criando um PWM de 1KHz via software:

#include <avr/io.h>
#include <avr/interrupt.h>

#define set_bit(y,bit) (y|=(1<<bit))


#define clr_bit(y,bit) (y&=~(1<<bit))
#define PWM1 PD1 //escolha do pino para o sinal PWM1
#define Resol_PWM1 100 //PWM1 com 100 passos temporais de resolução

/*O modificador "volatile" permite que estas variáveis possam ser utilizadas em qualquer
momento do programa sem ser modificada pela otimização do compilador*/
volatile unsigned int Passo_PWM1 = 0;
volatile unsigned int Ciclo_Ativo_PWM1;

//Interrupção do Timer que controla o PWM


ISR(TIMER0_OVF_vect)
{
TCNT0 = 96; //Recarrega o timer para garantir a próxima interrupção com 10us
Passo_PWM1++; //incremento do passo de tempo
if (Passo_PWM1 == Resol_PWM1) {
Passo_PWM1 = 0; //inicializa o contador
set_bit(PORTD, PWM1); //na igualdade de comparação coloca o pino PWM1 em 1
}
else if (Passo_PWM1 == Ciclo_Ativo_PWM1)
clr_bit(PORTD, PWM1); /*quando o contador atinge o valor do ciclo ativo do
PWM1, o pino vai a zero*/
}

int main() {
DDRD = 0b11111111; //PORTD como saída
PORTD = 0b00000001; //acende apenas o LED0
TCCR0B = 0b00000001; //TC0 com prescaler de 1
TIMSK0 = 0b00000001; //habilita a interrupção do TC0
TCNT0 = 96; //Inicia a contagem em 96 para gerar a interrupção a cada 10us
sei(); //habilita as interrupções
Ciclo_Ativo_PWM1 = 5; //Determina o ciclo ativo para o PWM1 (0 - 100)

while (1) {
}
}
Exercícios:

1. Configure o Timer 1 (Exemplo 2) para que ele gere uma interrupção e inverta o estado de um
LED a cada 2 segundos;

2. Configure o PWM do exemplo 5 para que a frequência seja alterada para 500Hz e uma
resolução de 200.

3. Apresente no display LCD um relógio simples no formato HH:MM:SS. O incremento dos


segundos deve ser feito a partir da interrupção do Timer 1;

Dica: Pode ser utilizada a função sprintf(buffer, “%d:%d:%d”, hora, minuto, segundo).

4. Implemente, no programa anterior, um ajuste de horas e minutos. O incremento das horas


(0 – 23) deve ser feito pela interrupção do botão PC2 e o incremento dos minutos pela
interrupção do botão PC3 (0 – 59).

5. Utilizando a técnica de PWM (Com Timer 0), crie um efeito “fade” na iluminação do display
LCD. O display deverá atingir o brilho total em 5 segundos e, depois, diminuir o brilho até
o mínimo também em 5 segundos. Utilize para este controle o Timer 2 juntamente com a
sua interrupção. O pino responsável por controlar a luminosidade do display é o PC5. É
necessário, também, que mude o jumper do Backlight da posição “ON” para a posição
“PWM”.

6.Por fim, adicione um alarme fixo neste relógio. Quando as horas e os minutos atingirem um
valor pré-determinado, o LED 3 deve ser ligado. Ele será desligado após passar 1 minuto do
horário definido. Os testes condicionais para acionamento do alarme deverão ser feitos no
loop infinito do programa principal.

Você também pode gostar