Documente Academic
Documente Profesional
Documente Cultură
UBERLÂNDIA
FACULDADE DE ENGENHARIA
ELÉTRICA - FEELT
LASEC
FEELT TIMER COM ISR
Sumário
TIMER ......................................................................................................................... 3
PROCESSAMENTO DE INTERRRUPÇÃO ................................................................ 7
CONTEXTO: ............................................................................................................ 8
COMUNICAÇÃO ENTRE ROTINA PRINCIPAL E A ROTINA DE TRATAMENTO
DE INTERRUPÇÃO: ................................................................................................ 9
INTERRUPÇÃO NO ATmega328 USANDO A LINGUAGEM DE PROGRAMAÇÃO C
.................................................................................................................................. 13
BIBLIOGRAFIA ......................................................................................................... 16
TIMER
Cada timer é composto por um contador que é incrementado a cada ciclo de clock. O clock do
Arduíno UNO opera a 16MHz, esta é a velocidade mais rápida que o contador do timer pode
ser incrementado. A 16MHz cada ciclo de clock representa 1/16000000 de segundos (≈63ns),
deste modo, levará 10/16000000 para o contador sair de 0 e atingir o valor 9 e levará
100/16000000 para o contador sair de 0 e atingir o valor 99.
Em muitas situações, incrementar o timer em uma frequência de 16MHz é muito rápido, pois o
timer0 e o timer2 podem armazenar um valor máximo de 255. Quando um contador atinge o
máximo, ele retornará para o valor zero no próximo ciclo de clock (esta situação é denominada
de overflow). Isto significa que a 16MHz o timer0 e o timer2 levará 256/16000000 segundos
(≈16us) para atingir o valor máximo e o timer1 demora 65536/16000000 (~4 ms) segundos para
atingir o valor máximo, pois é um contador de 16 bit. Assim, se o timer precisar ser utilizado
para gerar uma interrupção a cada segundo, mesmo o valor máximo do timer1 de 16 bit não
será suficiente.
Para configurar a frequência em que o timer irá gerar a interrupção é necessário encontrar o
valor do registrador de comparação e configurar a frequência em que o timer irá incrementar.
A velocidade com que o timer é incrementado pode ser controlada por meio da seguinte
equação:
Assim sendo, um prescaler igual a 1 irá incrementar o contador a uma frequência de 16MHz,
um prescaler igual a 8 irá incrementá-lo a 2MHz e um igual a 64 irá incrementar o contador a
250kHz. Como indicado na tabela abaixo, o prescaler do ATMEGA 328p, pode assumir os
valores 1, 8, 64, 256 e 1024, dependendo do valor dos bits CS12, CS11 e CS10.
Isto posto, a frequência de interrupção pode ser calculada com a seguinte equação:
interrupt frequency (Hz) = (Arduino clock speed 16000000Hz) / (prescaler * (compare match
register + 1))
Rearranjando a equação acima é possível achar o valor do compare match register que irá gerar
a frequência desejada:
Deste modo, para se obter uma interrupção a cada segundo (frequência de 1Hz):
Considerando que 256 < 15624 < 65535, deve-se usar o timer1 para que esta interrupção seja
implementada.
Para que o timer1 seja inicializado e para que possa operar no modo Limpar o Timer na
Comparação (Clear Timer on Compare or CTC mode Configuration), o código de inicialização
do timer1 a seguir pode ser utilizado.
https://mega.nz/#F!BwsC1ZqT!aw7S24tYwCI0ziwHMgymGg
void InitializeTimer1(void )
{
// ICIE1: Timer/Counter1, Input Capture Interrupt Enable
// OCIE1B: Timer/Counter1, Output Compare B Match Interrupt Enable
// OCIE1A: Timer/Counter1, Output Compare A Match Interrupt Enable
// TOIE1: Timer/Counter1, Overflow Interrupt Enable
TIMSK1 &= ~((1 << ICIE1) | (1 << OCIE1B) | (1 << OCIE1A) | (1 << TOIE1));
//TIMSK1 |= (1 << TOIE1);
TIMSK1 |= (1 << OCIE1A);
Para maiores informações sofre o Timer/Counter1 de 16 bit consultar a Pag. 111 do manual
Atmel-8-bit-AVR-Microcontroller-ATmega328, disponibilizado em:
https://mega.nz/#F!wwkmTZaT!WEgW1kLqINnnhwwxsiKgEw
PROCESSAMENTO DE INTERRRUPÇÃO
Quando um programa precisa de uma amostra de sinal ele pode obtê-la por meio de polling.
Este conceito é mostrado na figura a seguir:1
Cada bloco na figura indica uma instrução a ser executada e as linhas mostram o caminho que
o Contador de Programa (PC) irá seguir. Este exemplo mostra a execução do Loop Principal
(Main Loop) que realiza duas chamadas de funções diferentes. Uma das chamadas é para a
Função A (Function A) e a outra é para a Função B (Function B).
O processo de polling por software consome processamento da CPU e se o programa for escrito
de tal modo que realize polling a partir de várias fontes. Ex: canais de A/D, teclado, botões,
buffer de dados, dentre outras entradas, a CPU ficará tão ocupada executando o polling por
software que não restará recurso computacional para realizar outras tarefas.
Para não sobrecarregar a CPU através do polling por software, o processo de polling pode ser
realizado por meio de interrupção. Assim, ao invés do polling ser realizado de modo
determinístico, ele pode ocorrer a qualquer momento. Quando uma interrupção ocorre, a CPU
para o que estiver fazendo e pula para a Rotina de Tratamento de Interrupção (RTI). A RTI é
uma função especial escrita para lidar com o evento que necessita ser tratado de modo urgente
pela CPU. Deste modo, a vantagem da interrupção é que a CPU não precisará ficar verificando
constantemente se um evento ocorreu, ela irá atender o evento somente quando ele ocorrer, isto
é, quando a interrupção acontecer. Interrupções de hardware foram introduzidas como forma
de evitar o desperdício de tempo valioso do processador em polling por software, a espera de
eventos externos.
1 As informações apresentadas neste item (Processamento de Interrupção), com exceção do programa, foram retiradas do livro Introduction to
Embedded Systems: Using ANSI C and the Arduino Development Environment, do autor David Russell.
(a) Programa com a Rotina de Tratamento de Interrupção - RTI (Interrupt Service Routine
-ISR) e (b) Chamando a RTI a partir de qualquer ponto do programa
CONTEXTO:
O contexto consiste no conteúdo dos registradores gerais da CPU, além dos registradores de
uso específico, como o program counter (PC), o stack pointer (SP) e o registrador de status.
Para que a Rotina de Tratamento de Interrupção não interfira na execução das instruções que
estavam sendo executadas antes da interrupção ocorrer, o conteúdo dos registradores de uso
geral, program counter, stack pointer e status register devem ser salvos no início da Rotina de
Tratamento de Interrupção e restabelecidos ao final de sua execução. Este procedimento evita
que as instruções que estavam utilizando os registradores de uso geral, antes da interrupção
ocorrer, possam finalizar as operações que estavam realizando após o retorno da interrupção.
A figura a seguir mostra uma interrupção que ocorre na terceira instrução da rotina principal
(Main Loop). Como resultado, o endereço da quarta instrução (fornecido pelo program
counter), bem como os registradores de uso geral que serão usados pela Função A, serão
armazenados como parte do contexto. Quando a Função A terminar, o contexto e o program
counter serão restaurados. Assim, a CPU prossegue executando a quarta instrução da rotina
principal com os valores dos registradores de uso geral correspondentes ao término da terceira
instrução, como se uma interrupção não houvesse ocorrido.
Visão conceitual de um software que possui uma Rotina de Tratamento de Interrupção. A interrupção
ocorre na terceira instrução do algoritmo apresentado em (a).
Como pode ser observado na figura a seguir, a razão disso ocorrer é porque as instruções C são
compostas por diversas instruções de máquina, especialmente quando se lida com variáveis de
16, 32, 64 bit ou maiores. Isto ocorre porque a arquitetura do ATmega328, por exemplo é de 8
bit e, consequentemente, necessita executar várias instruções para realizar uma operação com
variáveis de 16, 32, 64 bit ou maiores.
Por exemplo, vamos supor que uma variável de 32bit está sendo copiada pela CPU do
ATmega328. Considerando que este microcontrolador possui uma arquitetura de 8 bit e que
necessitará executar várias instruções para realizar a cópia, se no meio da operação ocorrer uma
interrupção, a RTI poderá mudar o valor da variável que está sendo copiada e quando ocorrer
o retorno da interrupção, isto é, quando a CPU for terminar a cópia interrompida da variável,
seu valor terá mudado e o resultado será de uma cópia com a metade do valor antigo e metade
do valor novo. O que não corresponde nem com a realidade antiga e nem com a nova.
Para resolver esse problema, a memória que é compartilhada entre a RTI e qualquer outra parte
do programa, precisa ser protegida. Esta proteção é realizada desabilitando-se a interrupção
global antes de ler a variável global e habilitando-a novamente depois que a variável for lida.
O programa a seguir mostra como realizar uma operação de leitura/escrita na variável global
timerCountISR, de modo a evitar que a RTI altere o valor dela durante a operação. Neste
exemplo, a interrupção ocorre quando o contador do timer1 atinge a condição de overflow, isto
é, passa do valor máximo 65535, para o valor inicial 0.
https://mega.nz/#F!BwsC1ZqT!aw7S24tYwCI0ziwHMgymGg
timer-isr.c
#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <avr/interrupt.h>
ISR(TIMER1_OVF_vect)
{
timerCountISR++;
}
int main(void)
{
unsigned long timerCountCopyISR = 0;
DDRB |= (1 << DDB5); // habilita o pino 13 para saída digital (OUTPUT).
InitializeTimer ();
while(1)
{
//----- Memory Protection ---------------------------------------
SREG &= ~(1 << SREG_GLOBAL_INT); //Disable global interrupts.
timerCountCopyISR = timerCountISR; // Copy timerCountISR variable
SREG |= (1 << SREG_GLOBAL_INT); // Restore the global interrupt bit.
//----- End of Memory Protection --------------------------------
timerCountCopyISR = 0;
Muito cuidado deve ser tomado quando se escreve um programa em assembly ou linguagem C
que possui uma Rotina de Tratamento de Interrupção devido a mudança de contexto. Entretanto,
a maioria dos compiladores fornecem macros especiais ou funções que permitem ao compilador
gerenciar o contexto.
Quando um algoritmo precisa manusear uma interrupção, basta escrever uma função com o
nome apropriado da função. Cada nome corresponde a um endereço na memória a partir da qual
a RTI será alocada. Os endereços dessas rotinas são chamados vetores de interrupção.
A lista completa de interrupções com seus respectivos nomes de função, são apresentados na
Tabela a seguir:
NÚMERO ORIGEM DA NOME DA FUNÇÃO
ENDREÇO DESCRIÇÃO
DO VETOR INTERRUPÇÃO ISR C
1 0x0000 RESET System reset (power-on)
2 0x0002 INT0 INT0_vect External Interrupt Request 0
3 0x0004 INT1 INT1_vect External Interrupt Request 1
4 0x0006 PCINT0 PCINT0_vect Pin Change Interrupt Request 0
5 0x0008 PCINT1 PCINT1_vect Pin Change Interrupt Request 1
6 0x000A PCINT2 PCINT2_vect Pin Change Interrupt Request 2
7 0x000C WDT WDT_vect Watchdog Time-out Interrupt
8 0x000E TIMER2 COMPA TIMER2_COMPA_vect Timer/Counter2 Compare Match A
9 0x0010 TIMER2 COMPB TIMER2_COMPB_vect Timer/Counter2 Compare Match B
10 0x0012 TIMER2 OVF TIMER2_OVF_vect Timer/Counter2 Overflow
11 0x0014 TIMER1 CAPT TIMER1_CAPT_vect Timer/Counter1 Capture Event
12 0x0016 TIMER1 COMPA TIMER1_COMPA_vect Timer/Counter1 Compare Match A
13 0x0018 TIMER1 COMPB TIMER1_COMPB_vect Timer/Counter1 Compare Match B
14 0x001A TIMER1 OVF TIMER1_OVF_vect Timer/Counter1 Overflow
15 0x001C TIMER0 COMPA TIMER0_COMPA_vect Timer/Counter0 Compare Match A
16 0x001E TIMER0 COMPB TIMER0_COMPB_vect Timer/Counter0 Compare Match B
17 0x0020 TIMER0 OVF TIMER0_OVF_vect Timer/Counter0 Overflow
18 0x0022 SPI, STC SPI_STC_vect SPI Serial Transfer Complete
19 0x0024 USART, RX USART_RX_vect USART Receive Complete
20 0x0026 USART, UDRE USART_UDRE_vect USART Data Register Empty
21 0x0028 USART,TX USART_TX_vect USART Transmit Complete
22 0x002A ADC ADC ADC_vect ADC Conversion Complete
23 0x002C EE READY EE_READY_vect EEPROM Ready
24 0x002E ANALOG COMP ANALOG_COMP_vect Analog Comparator
25 0x0030 TWI TWI_vect 2-wire Serial Interface
26 0x0032 SPM READY SPM_READY_vect Store Program Memory Ready
ISR() é o macro usado para registrar e marcar uma função como sendo um manipulador de
interrupção. Este macro foi definido para usar o nome da função como sendo o primeiro
parâmetro seguido dos seguintes atributos opcionais SR_BLOCK, ISR_NOBLOCK,
ISR_NAKED e ISR_ALIASOF(vect).
ISR_BLOCK - (default behavior) corresponde a uma Rotina de Tratamento de Interrupção - ISR sem um
atributo especificado. A interrupção global (global interrupts) do ATmega328P é desabilitada no início da
RTI sem que o compilador modifique este estado, isto é, a interrupção global não é reestabelecida
automaticamente, esta operação deve ser realizada pelo programador.
ISR_NOBLOCK - A interrupção global (global interrupts) são reativadas pelo código gerado pelo
compilador no início da RTI, permitindo que outras interrupções de maior prioridade possam ocorrer dentro
da RTI. Isto significa que a RTI pode ser interrompida por interrupções de maior prioridade.
ISR_NAKED - O compilador não gera código para realizar o gerenciamento de contexto. A RTI necessita
salvar, restaurar e adicionar a instrução reti ao final da RTI para retornar o program counter para sua posição
anterior.
ISR_ALIASOF(vect) - A RTI é ligada a uma RTI especificada por um parâmetro de vetor propiciando a
uma RTI manusear múltiplos sinais de interrupção.
Neste exemplo, o timer1 está operando no modo Clear Timer on Compare or CTC mode
Configuration. Deste modo, a interrupção do timer é ativada quando o contador atinge o valor
armazenado no registrador de comparação (compare match register). Neste caso a interrupção
ocorre quando o valor do contador do timer1 é igual ao valor do registrador de comparação
(OCR1A).
timer-isr.c
#include <stdint.h>
#include <avr/io.h>
#include <util/delay.h>
#include <stdbool.h>
#include <avr/interrupt.h>
#define SREG_GLOBAL_INT_ENABLE 7
ISR(TIMER1_COMPA_vect)
{
if (ledON == true)
{
ledON =false;
void InitializeTimer1(void )
{
// ICIE1: Timer/Counter1, Input Capture Interrupt Enable
// OCIE1B: Timer/Counter1, Output Compare B Match Interrupt Enable
// OCIE1A: Timer/Counter1, Output Compare A Match Interrupt Enable
// TOIE1: Timer/Counter1, Overflow Interrupt Enable
TIMSK1 &= ~((1 << ICIE1) | (1 << OCIE1B) | (1 << OCIE1A) | (1 << TOIE1));
//TIMSK1 |= (1 << TOIE1);
TIMSK1 |= (1 << OCIE1A);
int main(void)
{
InitializeTimer1();
while(1)
{
// Restore the global interrupt bit to previous value.
SREG |= (1 << SREG_GLOBAL_INT_ENABLE);
}
}
BIBLIOGRAFIA
1. RUSSELL, D. Introduction to Embedded Systems: Using ANSI C and the Arduino Development
Environment, Morgan & Claypool, 2010.
www.omegaflix.com