Diretoria de Curso Plano de Aula 1 o semestre Nome do curso Eng. Ciências da Computação + TI + TADS + Eng. Elétrica Nome da Unidade Curricular Microprocessadores e Microcontroladores Aula número 009 Tema Interrupções Período Turma Noturno Tópicos O que são interrupções Uso de interrupções no Arduíno Objetivos Compreender o que são e para que servem as interrupções em um sistema microprocessado Fazer uso das interrupções externas no Arduíno Habilidades Utilização das rotinas de configuração das interrupções externas Aplicação prática, leitura de um encoder incremental Bases Tecnológicas Plataforma online 123d.circuits.io 1
Recomendada Bibliografia Primeiros passos com o Arduíno - Massimo Banzi e Michael Shiloh Microcontrolador 8051 Detalhado - Denys E. C. Nicolosi Parte II - Capítulo 6 Documentação oficial do Arduíno https://www.arduino.cc/en/reference/attachinterrupt https://www.arduino.cc/en/reference/detachinterrupt https://www.arduino.cc/en/reference/interrupts https://www.arduino.cc/en/reference/nointerrupts 2
1 Interrupções Interrupção é um evento, externo ou interno ao microcontrolador, que o obriga a suspender temporariamente suas atividades para atender a este evento que o interrompe. Toda vez que uma interrupção ocorre, o microprocessador irá se desviar para outra parte do código, o chamado serviço de interrupção, adequado para tratar a interrupção gerada. Após completar a execução do serviço de interrupção (que costuma ser um trecho de código bem pequeno e rápido), o microprocessador deve voltar para o código que executava antes de ser interrompido. Repare que a chamada ao serviço de interrupção é semelhante à chamada a uma função ou sub-rotina comum, a grande diferença esta no fato de que uma interrupção não é um evento previsto. Uma analogia válida é pensar no trabalho em escritório para compreender como funcionam as interrupções. Suponha que você esteja trabalhando em uma determinada tarefa A e em um dado momento não programado, seu chefe lhe solicita a execução imediata de uma outra tarefa B. Você é então obrigado a parar completamente a tarefa A, atende a solicitação para executar a tarefa B e, apenas quando esta última estiver concluída, você pode então retornar para o ponto onde parou na execução da tarefa A. 1.1 Propriedades de uma interrupção Vetorada ou não vetorada: Quando o microprocessador é interrompido, ele desvia para o endereço onde se encontra o serviço adequado para tratar a interrupção. Se esse endereço é fixo, chamamos de interrupção não vetorada. Por outro lado, se esse endereço pode ser especificado pelo dispositivo que gera a interrupção, chamamos de interrupção vetorada. Mascaramento: Por meio do mascaramento, realizado via software ou também via hardware, podemos habilitar ou desabilitar uma ou mais interrupções. Isso é especialmente útil na realização de tarefas críticas onde por alguma razão o microprocessador não possa ser interrompido em hipótese alguma. Prioridade: Geralmente um microprocessador pode aceitar diversas interrupções diferentes. Neste caso, é possível programar a prioridade de cada evento de interrupção, de modo que não haja conflitos ou disputas entre elas. Origem: No caso de microcontroladores, a interrupção pode ser interna caso seja gerada por algum periférico interno ao chip (timer ou UART por exemplo), ou externa, caso venha de um dispositivo externo ao chip. Tipo de disparo: É uma propriedade das interrupções externas ao chip, pode-se programar um pino para gerar interrupções de acordo com o nível lógico (0 ou 1) ou de acordo com a borda (transição de 0 para 1 ou transição de 1 para 0). 2 Interrupções externas no Arduíno Uno O Arduíno Uno possui dois pinos capazes de gerar interrupções externas, são os pinos digitais 2 e 3. Além disso, a biblioteca padrão do Arduíno já oferece algumas rotinas para simplificar bastante a programação de interrupções externas void attachinterrupt(int interrupt, int isr, int mode): Configura a interrupção interrupt para chamar a rotina isr (interrupt service routine), segundo o modo de operação mode. Esta rotina não retorna nenhum valor e recebe três parâmetros: interrupt: A interrupção a ser utilizada. No caso de interrupções externas, vamos utilizar o valor retornado pela chamada à função digitalpintointerrupt(pino), onde pino pode assumir o valor 2 ou 3. isr: O nome da rotina que vai tratar a interrupção gerada, é o nosso serviço de interrupção ou interrupt service routine. Deve ser declarada como uma função que não receberá nem retornará parâmetros, por exemplo: void meu_isr ( ) { / / Código do s e r v i ç o de i n t e r r u p ç ã o v a r _ g l o b a l = v a r _ g l o b a l + 1 ; Devemos observar que a rotina isr deve ser pequena e executar rapidamente. Além disso, ao tratar 3
uma interrupção, ou seja, dentro da rotina isr, outras interrupções não podem ocorrer. Isso implica que o código dentro da isr não pode ser interrompido externamente, mas também que funções como delay() e millis() também não irão funcionar (essas duas funções da biblioteca do Arduíno utilizam interrupções internas para operar). Se for imprescindível realizar algum tipo de delay dentro da isr, podemos utilizar a rotina delaymicroseconds(), que recebe um valor em microssegundos, isso permite alguma sincronização para eventos muito rápidos. Outro ponto muito importante é que a isr não recebe nem retorna valores. Assim, qualquer tipo de comunicação entre a isr e o programa principal deve ser realizada por meio de variáveis globais, declaradas como voláteis. Para isso utilizamos a palavra reservada volatile, que diz ao compilador que a variável declarada a seguir pode ser alterada por outros meios além do programa principal. Isso vai desligar qualquer otimização que o compilador julgaria viável em outras situações. mode: O modo de disparo da interrupção. Podemos configurar para um dos quatro valores predefinidos: LOW: Dispara a interrupção quando o pino esta em nível lógico baixo; CHANGE: Dispara a interrupção quando o nível lógico do pino muda; RISING: Dispara a interrupção quando o nível lógico do pino sofre uma transição de 0 para 1; FALLING: Dispara a interrupção quando o nível lógico do pino sofre uma transição de 1 para 0; void detachinterrupt(int interrupt): Desabilita a interrupção em questão. No caso de interrupções externas, devemos utilizar o valor retornado pela chamada à função digitalpintointerrupt(pino), onde pino pode assumir o valor 2 ou 3. interrupts(): Habilita todas as interrupções nointerrupts(): Desabilita todas as interrupções. Essas duas funções podem ser utilizadas para executar partes críticas de um código, onde não será permitida a ocorrência de interrupções. Por exemplo: void s e t u p ( ) { void loop ( ) { n o I n t e r r u p t s ( ) ; / / p a r t e c r í t i c a do c ó d i g o i n t e r r u p t s ( ) ; / / p a r t e não c r í t i c a 2.1 Exemplo No exemplo a seguir, vamos configurar interrupções do tipo RISING nos pinos 2 e 3. Vamos também declarar duas rotinas isr, uma para cada pino gerador de interrupção. O funcionamento do programa será o seguinte. Vamos configurar uma variável do tipo int (inteiro) como um contador. Ao ocorrer uma interrupção no pino 2, esse valor deve ser resetado imediatamente, já ao ocorrer uma interrupção no pino 3, o contador deve assumir o valor 100. Observe que a variável de contagem deve ser declarada como volatile para que não ocorram problemas. 4
/ / v a r i a v e l de contagem / / s e r á m o d i f i c a d a d e n t r o das r o t i n a s i s r / / e n t ã o deve s e r d e c l a r a d a como v o l a t i l e v o l a t i l e i n t c o n t a d o r ; void s e t u p ( ) { / / vamos c o n f i g u r a r as i n t e r r u p ç õ e s a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 2 ), i s r _ p i n o 2, RISING ) ; a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 3 ), i s r _ p i n o 3, RISING ) ; void loop ( ) { / / a r o t i n a loop v a i i n c r e m e n t a r a v a r i á v e l de contagem / / a cada 1 segundo d e l a y ( 1 0 0 0 ) ; c o n t a d o r ++; / / D e c l a r a ç ã o das duas ISRs void i s r _ p i n o 2 ( ) { c o n t a d o r = 0 ; void i s r _ p i n o 3 ( ) { c o n t a d o r = 100; 5
2.2 Exercícios 2.2.1 Crie um programa utilizando interrupção externa do tipo FALLING no pino 2. O programa deve configurar o pino 0 como uma saída digital e o nível lógico escrito neste pino deve ser trocado (de 1 para 0 ou de 0 para 1) quando ocorrer a interrupção / / v a r i a v e l do n í v e l l ó g i c o / / s e r á m o d i f i c a d a d e n t r o das r o t i n a s i s r / / e n t ã o deve s e r d e c l a r a d a como v o l a t i l e v o l a t i l e i n t n i v e l ; void s e t u p ( ) { / / vamos c o n f i g u r a r as i n t e r r u p ç õ e s a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 2 ), i s r _ p i n o 2, FALLING ) ; / / vamos c o n f i g u r a r o pino 0 como s a í d a d i g i t a l pinmode ( 0, OUTPUT ) ; / / c o n f i g u r a r um n í v e l l ó g i c o i n i c i a l n i v e l = LOW; void loop ( ) { d i g i t a l W r i t e ( 0, n i v e l ) ; / / D e c l a r a ç ã o das duas ISRs void i s r _ p i n o 2 ( ) { i f ( n i v e l ==LOW) { n i v e l = HIGH ; n i v e l = LOW; 6
2.2.2 Crie um programa utilizando interrupção externa do tipo RISING no pino 3. O programa deve incrementar um contador do tipo inteiro sempre que uma interrupção for gerada / / v a r i a v e l de contagem / / s e r á m o d i f i c a d a d e n t r o das r o t i n a s i s r / / e n t ã o deve s e r d e c l a r a d a como v o l a t i l e v o l a t i l e i n t c o n t a d o r ; void s e t u p ( ) { / / vamos c o n f i g u r a r as i n t e r r u p ç õ e s a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 3 ), i s r _ p i n o 3, RISING ) ; / / c o n f i g u r a r um v a l o r i n i c i a l c o n t a d o r = 0 ; void loop ( ) { / / D e c l a r a ç ã o da ISR void i s r _ p i n o 2 ( ) { c o n t a d o r ++; 7
2.3 Aplicação prática, leitura de encoder incremental Encoders incrementais são comumente utilizados para monitorar mudanças de posição. Diferentemente dos encoders absolutos, um encoder incremental não fornece uma leitura absoluta da posição, mas permite detectar a sua mudança. No entanto, se assumirmos uma dada posição como sendo o referencial zero, ou seja, devemos fazer o setup ou zeramento do encoder, é possível utilizar encoders incrementais para monitorar posições absolutas. Tipicamente, um encoder incremental disponibiliza dois canais onde monitoramos o nível lógico por mudanças que representam um incremento ou um decremento na posição. A tabela 1 ilustra passo a passo os níveis lógicos ao se avançar a posição do encoder Posição Canal A Canal B X+0 0 0 X+1 0 1 X+2 1 1 X+3 1 0 X+4 0 0 X+5 0 1 X+6 1 1 X+7 1 0 X+8 0 0......... Tabela 1: Encoder incremental ao se avançar as posições A tabela 2 exibe os níveis lógicos ao se retroceder a posição do encoder Posição Canal A Canal B X+0 0 0 X-1 1 0 X-2 1 1 X-3 0 1 X-4 0 0 X-5 1 0 X-6 1 1 X-7 0 1 X-8 0 0......... Tabela 2: Encoder incremental ao se avançar as posições Note que a cada posição, apenas um dos canais muda seu nível lógico. Para determinar a posição atual, devemos verificar a configuração atual e a configuração anterior dos canais A e B do encoder. Assim, se anteriormente tínhamos os canais A e B em nível lógico baixo (zero) e agora verificamos que o canal A apresenta nível lógico alto (um) e o canal B continua com nível lógico baixo, então verificamos que houve um decremento de uma posição na leitura do encoder. Com base nessas informações e nas tabelas 1 e 2, podemos resumir o incremento ou decremento da posição de acordo com a tabela 3 A leitura de um encoder incremental é um caso típico onde é interessante fazer uso de interrupções. Perceba que se deixarmos de monitorar os canais do encoder por um tempo muito longo, podem ocorrer mais de uma mudanças de posição e, neste caso, perderemos a contagem. Por outro lado, monitorar frequentemente os canais do encoder pode demandar muito tempo de processamento, tempo que poderia ser utilizado em outras funções e cálculos. 8
Leitura Anterior Leitura Atual Canal A Canal B Canal A Canal B Ação 0 0 0 0 Não Muda 0 0 0 1 Incrementa 0 0 1 0 Decrementa 0 0 1 1 Erro 0 1 0 0 Decrementa 0 1 0 1 Não Muda 0 1 1 0 Erro 0 1 1 1 Incrementa 1 0 0 0 Incrementa 1 0 0 1 Erro 1 0 1 0 Não Muda 1 0 1 1 Decrementa 1 1 0 0 Erro 1 1 0 1 Decrementa 1 1 1 0 Incrementa 1 1 1 1 Não Muda Tabela 3: Ação no encoder conforme mudança nos canais A e B A seguir apresentamos uma possível implementação para a leitura de um encoder incremental utilizando interrupções. Este código pode ser testado online em https://123d.circuits.io/circuits/2076667-leitor-de-encoder-incremental. Inicie a simulação e abra o monitor serial localizado dentro da aba do editor de código. No monitor serial será possível visualizar a indicação da posição lida pelo encoder. 9
/ / E s t e programa f a z uso de i n t e r r u p ç õ e s p a r a m o n i t o r a r um / / e n c o d e r i n c r e m e n t a l l i g a d o aos p i n o s 2 e 3 do Arduíno UNO. / / Ao o c o r r e r uma mudança, a v a r i á v e l e n c o d e r é a t u a l i z a d a / / e a i n f o r m a ç ã o é d i s p o n i b i l i z a d a na p o r t a s e r i a l / / v a r i á v e i s g l o b a i s e v o l á t e i s / / e n c o d e r armazena o v a l o r da p o s i ç ã o do e n c o d e r / / c a n a l _ a armazena o e s t a d o l ó g i c o a n t e r i o r do c a n a l A / / c a n a l _ b armazena o e s t a d o l ó g i c o a n t e r i o r do c a n a l B / / change s i n a l i z a se houve mudança de e s t a d o, usada / / apenas p a r a a t u a l i z a r a i n f o r m a ç ã o na p o r t a s e r i a l v o l a t i l e long i n t e n c o d e r = 0 ; v o l a t i l e b y t e c a n a l _ a = LOW; v o l a t i l e b y t e c a n a l _ b = LOW; v o l a t i l e b y t e change = LOW; / / r o t i n a de s e t u p void s e t u p ( ) { / / C o n f i g u r a a p o r t a s e r i a l p a r a monitorarmos / / Configuramos uma t a x a de 9600 bauds S e r i a l. b e g i n ( 9 6 0 0 ) ; / / Mensagem de a p r e s e n t a ç ã o do programa S e r i a l. p r i n t l n ( ) ; S e r i a l. p r i n t l n ( T e s t e Encoder : ) ; / / c o n f i g u r a as i n t e r r u p ç õ e s nos p i n o s / / pino 2 > c a n a l A / / pino 3 > c a n a l B a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 2 ), i s r _ c a n a l _ a, CHANGE) ; a t t a c h I n t e r r u p t ( d i g i t a l P i n T o I n t e r r u p t ( 3 ), i s r _ c a n a l _ b, CHANGE) ; / / r o t i n a p r i n c i p a l void loop ( ) { / / se houve mudança, e n v i a novo v a l o r p a r a p o r t a s e r i a l i f ( change == HIGH) { S e r i a l. p r i n t ( e n c o d e r = ) ; S e r i a l. p r i n t l n ( e n c o d e r ) ; change = LOW; 10
/ / r o t i n a s de s e r v i ç o de i n t e r r u p ç ã o ISRs void i s r _ c a n a l _ a ( ) { change = HIGH ; i f ( c a n a l _ a == LOW) { i f ( c a n a l _ b == LOW) { / / AB e r a 00 e f o i p a r a 10, d e c r e m e n t a o e n c o d e r encoder ; c a n a l _ a = HIGH ; / / AB e r a 01 e f o i p a r a 11, i n c r e m e n t a o e n c o d e r e n c o d e r ++; c a n a l _ a = HIGH ; i f ( c a n a l _ b == LOW) { / / AB e r a 10 e f o i p a r a 00, i n c r e m e n t a o e n c o d e r e n c o d e r ++; c a n a l _ a = LOW; / / AB e r a 11 e f o i p a r a 01, d e c r e m e n t a o e n c o d e r encoder ; c a n a l _ a = LOW; void i s r _ c a n a l _ b ( ) { change = HIGH ; i f ( c a n a l _ a == LOW) { i f ( c a n a l _ b == LOW) { / / AB e r a 00 e f o i p a r a 01, d e c r e m e n t a o e n c o d e r e n c o d e r ++; c a n a l _ b = HIGH ; / / AB e r a 01 e f o i p a r a 00, d e c r e m e n t a o e n c o d e r encoder ; c a n a l _ b = LOW; i f ( c a n a l _ b == LOW) { / / AB e r a 10 e f o i p a r a 11, d e c r e m e n t a o e n c o d e r encoder ; c a n a l _ b = HIGH ; / / AB e r a 11 e f o i p a r a 10, i n c r e m e n t a o e n c o d e r e n c o d e r ++; c a n a l _ b = LOW; 11