Arduino e interrupções do timer

Olá Habr! Apresento a você a tradução do artigo "Timer interrompe" por E.


Prefácio


A placa Arduino permite que você resolva rápida e minimamente uma variedade de problemas. Mas onde são necessários intervalos de tempo arbitrários (pesquisa periódica de sensores, sinais PWM de alta precisão, pulsos de longa duração), as funções padrão de atraso da biblioteca não são convenientes. Durante a duração da ação, o esboço é suspenso e torna-se impossível gerenciá-lo.


Em uma situação semelhante, é melhor usar os temporizadores AVR integrados. Um artigo bem-sucedido informa como fazer isso e não se perde na natureza técnica das planilhas de dados, cuja tradução é trazida à sua atenção.



Este artigo discute os temporizadores AVR e Arduino e como usá-los em projetos e circuitos do usuário do Arduino.


O que é um temporizador?


Como na vida cotidiana dos microcontroladores, um timer é algo que pode sinalizar no futuro, no momento que você definir. Quando esse momento chegar, o microcontrolador será interrompido, lembrando-o de fazer algo, por exemplo, para executar um determinado pedaço de código.


Os temporizadores, como interrupções externas, funcionam independentemente do programa principal. Em vez de fazer um loop ou repetir a chamada de atraso millis () , você pode atribuir um timer para fazer seu trabalho enquanto seu código faz outras coisas.


Portanto, suponha que exista um dispositivo que precise fazer algo, por exemplo, piscar um LED a cada 5 segundos. Se você não usa temporizadores, mas escreve um código normal, precisa definir uma variável no momento em que o LED acende e verificar constantemente se o momento de sua comutação chegou. Com uma interrupção do cronômetro, você só precisa configurar a interrupção e iniciar o cronômetro. O LED piscará exatamente na hora certa, independentemente das ações do programa principal.


Como o temporizador funciona?


Ele atua incrementando uma variável chamada registro de contagem . O registro de contagem pode contar até um determinado valor, dependendo do seu tamanho. O temporizador incrementa seu contador repetidamente até atingir seu valor máximo; nesse ponto, o contador transborda e redefine para zero. Um cronômetro geralmente define um bit de sinalizador para que você saiba que ocorreu um estouro.


Você pode verificar esse sinalizador manualmente ou pode alternar o temporizador - causar uma interrupção automaticamente quando o sinalizador estiver definido. Como qualquer outra interrupção, você pode atribuir uma ISR ( Interrupt Service Routine ) para executar o código especificado quando o timer estourar. O próprio ISR limpará o sinalizador de estouro, portanto, usar interrupções geralmente é a melhor opção devido à sua simplicidade e velocidade.


Para aumentar os valores do contador em intervalos de tempo exatos, o temporizador deve estar conectado à fonte do relógio. A fonte do relógio gera um sinal constantemente repetido. Cada vez que o timer detecta esse sinal, ele aumenta o valor do contador em um. Como o timer funciona em uma fonte de relógio, a menor unidade de tempo mensurável é o período do ciclo. Se você conectar um sinal de clock de 1 MHz, a resolução do timer (ou período do timer) será:


T = 1 / f (f é a frequência do relógio)
T = 1/1 MHz = 1/10 ^ 6 Hz
T = (1 ∗ 10 ^ -6) s


Assim, a resolução do cronômetro é um milionésimo de segundo. Embora você possa usar uma fonte de relógio externa para temporizadores, na maioria dos casos a fonte interna do próprio chip é usada.


Tipos de temporizador


Nas placas Arduino padrão em um chip AVR de 8 bits, existem vários temporizadores ao mesmo tempo. Os chips Atmega168 e Atmega328 possuem três temporizadores Timer0, Timer1 e Timer2. Eles também têm um cronômetro de vigilância que pode ser usado para proteger contra falhas ou como um mecanismo de redefinição de software. Aqui estão alguns recursos de cada timer.


Timer0:
Timer0 é um timer de 8 bits, o que significa que seu registro de contagem pode armazenar números de até 255 (ou seja, um byte não assinado). O Timer0 é usado pelas funções temporárias padrão do Arduino, como delay () e millis () , portanto, é melhor não confundi-lo se você se importar com as consequências.


Timer1:
Timer1 é um timer de 16 bits com um valor máximo de contagem de 65535 (número inteiro não assinado). Esse timer usa a biblioteca Servo Arduino, lembre-se disso se você o usar em seus projetos.


Timer2:
O Timer2 é de 8 bits e é muito semelhante ao Timer0. É usado na função tone () do Arduino.


Timer3, Timer4, Timer5:
Os chips ATmega1280 e ATmega2560 (instalados nas variantes Arduino Mega) possuem três temporizadores adicionais. Todos eles são de 16 bits e funcionam de maneira semelhante ao Timer1.


Registrar a configuração


Para usar esses temporizadores, o AVR possui registros de configurações. Os temporizadores contêm muitos desses registros. Dois deles - registros de controle de timer / contador contêm variáveis ​​de configuração e são chamados de TCCRxA e TCCRxB, onde x é o número do timer (TCCR1A e TCCR1B, etc.). Cada registro contém 8 bits e cada bit armazena uma variável de configuração. Aqui estão os detalhes da folha de dados do Atmega328:


TCCR1A
Bit76543210 0
0x80COM1A1COM1A0COM1B1COM1B0--Wgm11Wgm10
ReadwriteRwRwRwRwRRRwRw
Valor inicial0 00 00 00 00 00 00 00 0

TCCR1B
Bit76543210 0
0x81ICNC1CIEM1-Wgm13Wgm12CS12CS11CS10
ReadwriteRwRwRRwRwRwRwRw
Valor inicial0 00 00 00 00 00 00 00 0

Os mais importantes são os últimos três bits no TCCR1B: CS12, CS11 e CS10. Eles determinam a frequência do relógio do temporizador. Escolhendo-os em diferentes combinações, você pode ordenar que o timer atue em velocidades diferentes. Aqui está uma tabela de dados descrevendo o efeito dos bits selecionados:


CS12CS11CS10Acção
0 00 00 0Nenhuma fonte de relógio (temporizador / contador parado)
0 00 01clk_io / 1 (sem divisão)
0 010 0clk_io / 8 (divisor de frequência)
0 011clk_io / 64 (divisor de frequência)
10 00 0clk_io / 256 (divisor de frequência)
10 01clk_io / 1024 (divisor de frequência)
110 0Fonte de relógio externo no pino T1. Clock de recessão
111Fonte de relógio externo no pino T1. Relógio Frontal

Por padrão, todos esses bits são definidos como zero.


Suponha que você queira que o Timer1 seja executado em uma frequência de relógio com uma amostra por período. Quando estiver cheio, você deseja chamar a rotina de interrupção, que alterna o LED conectado à perna 13 para o estado ligado ou desligado. Neste exemplo, escreveremos o código do Arduino, mas usaremos os procedimentos e funções da biblioteca avr-libc sempre que isso não tornar as coisas muito complicadas. Os apoiadores do AVR puro podem adaptar o código como desejarem.


Primeiro, inicialize o cronômetro:


// avr-libc library includes #include <avr/io.h> #include <avr/interrupt.h> #define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); //  Timer1 cli(); //    TCCR1A = 0; //  TCCR1A   0 TCCR1B = 0; //   Timer1 overflow: TIMSK1 = (1 << TOIE1); //  CS10  ,      : TCCR1B |= (1 << CS10); sei(); //    } 

O registro TIMSK1 é um registro de máscara de interrupção do temporizador / contador1. Controla a interrupção que o temporizador pode causar. A configuração do bit TOIE1 indica ao cronômetro que interrompa quando o cronômetro exceder. Mais sobre isso mais tarde.


Quando você define o bit CS10, o timer começa a contar e, assim que ocorre uma interrupção de estouro, o ISR (TIMER1_OVF_vect) é chamado. Isso sempre acontece quando o temporizador transborda.


Em seguida, definimos a função de interrupção ISR:


 ISR(TIMER1_OVF_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); } 

Agora podemos definir o ciclo loop () e alternar o LED, independentemente do que acontece no programa principal. Para desligar o timer, defina TCCR1B = 0 a qualquer momento.


Com que frequência o LED pisca?


Timer1 está definido para interromper a transbordamento e vamos supor que você esteja usando um Atmega328 com uma frequência de clock de 16 MHz. Como o temporizador é de 16 bits, pode contar até o valor máximo (2 ^ 16 - 1) ou 65535. Em 16 MHz, o ciclo executa 1 / (16 × 10 ^ 6) segundos ou 6,25e-8 s. Isso significa que 65535 amostras ocorrerão em (65535 ± 6,25e-8 s) e o ISR será chamado após aproximadamente 0,0041 s. E assim, vez após vez, a cada quatro milésimos de segundo. É muito rápido para ver cintilação.


Se aplicarmos um sinal PWM muito rápido com 50% de cobertura ao LED, o brilho parecerá contínuo, mas menos brilhante que o normal. Esse experimento mostra o incrível poder dos microcontroladores - mesmo um chip de 8 bits barato pode processar informações muito mais rapidamente do que podemos detectar.


Divisor do temporizador e modo CTC


Para controlar o período, você pode usar um divisor, que permite dividir o sinal do relógio em diferentes graus de dois e aumentar o período do timer. Por exemplo, você deseja que o LED pisque em intervalos de um segundo. Existem três bits CS no registro TCCR1B configurando a resolução mais apropriada. Se você definir os bits CS10 e CS12 usando:


 TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12); 

então a frequência da fonte do relógio será dividida por 1024. Isso fornece uma resolução de timer de 1 / (16 × 10 ^ 6/1024) ou 6,4e-5 s. Agora o temporizador transbordará a cada (65535 ∗ 6,4e-5s) ou por 4,194s. É muito longo.


Mas há outro modo de timer AVR. É chamado de reset coincidente do temporizador ou CTC. Em vez de contar para transbordar, o timer compara seu contador com a variável que foi armazenada anteriormente no registro. Quando a contagem corresponde a essa variável, o cronômetro pode definir um sinalizador ou causar uma interrupção, como no caso de um estouro.


Para usar o modo CTC, você precisa entender quantos ciclos são necessários para obter um intervalo de um segundo. Suponha que a taxa de divisão ainda seja 1024.


O cálculo será o seguinte:


 (target time) = (timer resolution) * (# timer counts + 1) (# timer counts + 1) = (target time) / (timer resolution) (# timer counts + 1) = (1 s) / (6.4e-5 s) (# timer counts + 1) = 15625 (# timer counts) = 15625 - 1 = 15624 

Você deve adicionar uma unidade adicional ao número de amostras, porque no modo CTC, se o contador corresponder ao valor definido, ele será redefinido para zero. A redefinição leva um período de relógio, que deve ser levado em consideração nos cálculos. Em muitos casos, um erro em um período não é muito significativo, mas em tarefas de alta precisão, pode ser crítico.


A função setup () será assim:


 void setup() { pinMode(LEDPIN, OUTPUT); //  Timer1 cli(); //    TCCR1A = 0; //    0 TCCR1B = 0; OCR1A = 15624; //    TCCR1B |= (1 << WGM12); //   CTC  //   CS10  CS12    1024 TCCR1B |= (1 << CS10); TCCR1B |= (1 << CS12); TIMSK1 |= (1 << OCIE1A); //     sei(); //    } 

Você também precisa substituir a interrupção de estouro por uma interrupção coincidente:


 ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); } 

Agora o LED acende e apaga por exatamente um segundo. E você pode fazer qualquer coisa em um loop loop (). Até você alterar as configurações do timer, o programa não tem nada a ver com interrupções. Você não tem restrições quanto ao uso de um timer com diferentes modos e configurações do divisor.


Aqui está um exemplo inicial completo que você pode usar como base para seus próprios projetos:


 // Arduino  CTC  // avr-libc library includes #include <avr/io.h> #include <avr/interrupt.h> #define LEDPIN 13 void setup() { pinMode(LEDPIN, OUTPUT); //  Timer1 cli(); //    TCCR1A = 0; //    0 TCCR1B = 0; OCR1A = 15624; //    TCCR1B |= (1 << WGM12); //  CTC  TCCR1B |= (1 << CS10); //      1024 TCCR1B |= (1 << CS12); TIMSK1 |= (1 << OCIE1A); //      sei(); //    } void loop() { //   } ISR(TIMER1_COMPA_vect) { digitalWrite(LEDPIN, !digitalRead(LEDPIN)); } 

Lembre-se de que você pode usar as funções ISR integradas para estender as funções do timer. Por exemplo, você precisa pesquisar o sensor a cada 10 segundos. Mas não há configurações de timer que forneçam uma contagem tão longa sem estouros. No entanto, você pode usar o ISR para incrementar a variável de contagem uma vez por segundo e, em seguida, pesquisar o sensor quando a variável atingir 10. Usando o modo STS do exemplo anterior, a interrupção pode ficar assim:


 ISR(TIMER1_COMPA_vect) { seconds++; if(seconds == 10) { seconds = 0; readSensor(); } } 

Como a variável será modificada dentro do ISR, ela deverá ser declarada como volátil . Portanto, ao descrever variáveis ​​no início do programa, você precisa escrever:


 volatile byte seconds; 

Posfácio do tradutor


Ao mesmo tempo, este artigo me salvou muito tempo ao desenvolver um protótipo de gerador de medição. Espero que seja útil para outros leitores.

Source: https://habr.com/ru/post/pt453276/


All Articles