Trilha de aprendizado · Nível 10 · Tutorial 7

Dados compartilhados entre interrupções e loop

Compartilhar flags e contadores entre uma ISR e o fluxo principal, distinguindo visibilidade dos acessos, atomicidade e preservação dos eventos.

  • Nível: Intermediário
  • Duração: 22 min
  • 8 passos
Dados compartilhados entre interrupções e loop

O que você vai percorrer

  1. Identifique os dados compartilhados Reconheça quais variáveis são acessadas tanto pela ISR quanto pelo fluxo principal antes de decidir como tratá-las. 2 min
  2. Entenda o que volatile garante Use volatile em flags e contadores que podem ser modificados entre a ISR e o loop, sem confundir visibilidade com atomicidade. 2 min
  3. Veja como uma leitura mistura dois instantes Veja como uma interrupção pode se intercalar a uma leitura de vários bytes e produzir um valor que nunca esteve armazenado por inteiro. 3 min
  4. Reconheça a corrida na leitura e limpeza de uma flag Uma flag de um byte admite acessos simples, mas consultar e limpar em seguida é uma operação composta sujeita à intercalação da ISR. 2 min
  5. Copie o contador em uma seção crítica Proteja somente a cópia de um contador compartilhado e use o valor local fora da seção crítica. 4 min
  6. Retire a flag em uma operação indivisível Faça a retirada atômica de uma flag para que uma sinalização posterior da ISR permaneça disponível, mantendo o processamento fora da seção crítica. 3 min
  7. Escolha entre flag e contador Selecione entre flag e contador conforme a informação que precisa sobreviver entre a ISR e o loop, transferindo contagens em lote com proteção curta. 3 min
  8. Aplique: registre na ISR e processe no loop Conclua uma solução em que a ISR registra atendimentos do Timer1, o loop transfere a contagem com proteção e processa o resultado fora da seção crítica. 4 min

O que você vai aprender

  • Identificar quais variáveis de um exemplo precisam de volatile e justificar a escolha.
  • Demonstrar, por uma sequência de acessos, como uma leitura multibyte pode ficar incoerente.
  • Proteger a cópia de um contador e a operação de leitura e limpeza de uma flag.
  • Escolher entre flag e contador conforme a necessidade de registrar ocorrências.
  • Manter a maior parte do processamento fora da ISR e da seção crítica.

Antes de começar

  • Variáveis e tipos no firmware
  • Cálculos e conversões numéricas em C/C++
  • Interrupções de comparação do Timer1

Passo 1 de 8

Identifique os dados compartilhados

Reconheça quais variáveis são acessadas tanto pela ISR quanto pelo fluxo principal antes de decidir como tratá-las.

Localize os dois fluxos

O que é um dado compartilhado

Dois fluxos, uma memória

No Uno R3, o loop() executa normalmente até que uma interrupção do Timer1 seja atendida. A ISR executa, termina e o processador retorna ao ponto do loop() que foi interrompido.

Uma variável é compartilhada quando tanto a ISR quanto o fluxo principal a acessam — lendo, escrevendo ou ambos. Não importa se ela é global por conveniência: o que decide é o mapa de acessos.

Nos exemplos deste tutorial, considere o ATmega328P do Uno R3: há um único núcleo e as interrupções não se aninham. Assim, enquanto uma ISR está em execução, outra interrupção aguarda; mas a ISR ainda pode interromper o loop() entre instruções.

Fluxo principal e ISR acessando dados

O compartilhamento depende de quem acessa cada posição de memória.
VariávelISR do Timer1loop()É compartilhada?
eventosincrementalêSim
atualizarLedescreve truelê e limpaSim
estadoLednão acessalê e escreveNão
amostraLocalnão acessalê e escreveNão

Construa o mapa de acessos

Exemplo: quem toca em cada variável?

Use este pequeno firmware apenas para mapear os acessos; por enquanto, não altere o código. A ISR registra que houve um atendimento e solicita uma atualização. Já o loop() consulta essas informações e mantém seus próprios dados locais de trabalho.

Trecho para analisar

Mapa de acessos

cpp
#include <avr/interrupt.h>

uint16_t eventos = 0;
bool atualizarLed = false;
bool estadoLed = false;

ISR(TIMER1_COMPA_vect) {
  eventos++;
  atualizarLed = true;
}

void loop() {
  uint16_t amostraLocal = eventos;

  if (atualizarLed) {
    atualizarLed = false;
    estadoLed = !estadoLed;
    digitalWrite(LED_BUILTIN, estadoLed);
  }

  if (amostraLocal >= 10) {
    // Uma decisão posterior usaria esta cópia.
  }
}

Mapa visual dos acessos

Diagrama com ISR e loop acessando o contador de eventos e a flag de atualização; somente o loop acessa estado do LED e amostra local.

As duas caixas centrais são compartilhadas porque recebem acessos dos dois fluxos.

Marque os pontos de contato

A interrupção pode entrar no meio do caminho

Uma linha de C/C++ não é uma zona protegida. Enquanto o loop() executa uma expressão que acessa um dado compartilhado, uma interrupção habilitada pode ser atendida entre instruções geradas para aquela expressão. Após a ISR, o loop() continua.

Por isso, ao encontrar uma variável acessada pela ISR, marque também todo ponto do loop() que a lê ou escreve. Essa marcação revela onde a ordem entre os dois fluxos pode influenciar o resultado.

Ainda não vamos decidir como tratar esses acessos. Primeiro, precisamos identificá-los com precisão.

Dica

Checklist de análise

Pergunte para cada variável:

  1. A ISR lê ou escreve nela?
  2. O loop() lê ou escreve nela?
  3. Se a resposta for “sim” nas duas perguntas, ela é compartilhada.

No trecho anterior, eventos é escrito pela ISR e lido pelo loop(). atualizarLed é escrito pela ISR, depois lido e escrito pelo loop(). Já estadoLed e amostraLocal pertencem somente ao loop().

Pratique a identificação

Teste seu mapa

Associe cada variável do exemplo ao seu mapa de acessos.

Leitores e escritores

Faça as associações corretas.

Toque em um item e depois no par correspondente.

Passo 2 de 8

Entenda o que volatile garante

Use volatile em flags e contadores que podem ser modificados entre a ISR e o loop, sem confundir visibilidade com atomicidade.

Quando volatile entra em cena

Visibilidade de mudanças

Uma variável pode ser compartilhada quando a ISR e o loop() a acessam. Mas isso, sozinho, não decide o uso de volatile.

Use volatile quando há acesso pelos dois fluxos e pelo menos um deles pode modificar a variável. Nesse caso, o valor pode mudar fora da sequência normal observada pelo outro fluxo. O compilador deve então manter as leituras e escritas previstas no código.

Se ISR e loop() apenas leem uma mesma variável que não é modificada por nenhum deles, ela pode ser compartilhada no mapa de acessos, mas não precisa de volatile por esse motivo.

Quem pode alterar a memória?

Diagrama com ISR escrevendo uma flag e um contador usados pelo loop, uma cópia local usada apenas pelo loop e um dado de referência somente lido pelos dois fluxos.

volatile se aplica aos dados que podem mudar entre os fluxos, não apenas por serem acessados em dois lugares.

Aplique o critério ao exemplo

Declarações adequadas

No exemplo iniciado no passo anterior, a ISR altera eventos e atualizarLed. O loop() lê ambos; por isso, as duas declarações usam volatile.

amostraLocal recebe uma cópia de eventos, mas depois é acessada somente pelo loop(). Ela não pode mudar por ação da ISR e não precisa do qualificador.

Exemplo

Variáveis modificadas pela ISR e cópia local do loop

cpp
volatile uint16_t eventos = 0;
volatile bool atualizarLed = false;
bool estadoLed = false;

ISR(TIMER1_COMPA_vect) {
  eventos++;
  atualizarLed = true;
}

void loop() {
  uint16_t amostraLocal = eventos;

  if (atualizarLed) {
    // O trabalho será definido nos próximos passos.
  }

  // Cálculos com amostraLocal.
}

Dica

Compartilhada não é sinônimo de global

Ser global não é critério para usar volatile. Uma variável global usada somente pelo loop() continua comum. Do mesmo modo, uma cópia local não ganha volatile apenas por ter vindo de um dado compartilhado.

Pergunte: esta variável pode ser modificada pela ISR enquanto o loop() a usa, ou pelo loop() enquanto a ISR a usa? Se sim, ela precisa de volatile.

O que ele garante — e o que não garante

O limite de volatile

volatile cuida da visibilidade dos acessos: o compilador deve realizar as leituras e escritas previstas para um dado que pode mudar fora do fluxo em execução.

Ele não torna uma sequência de várias etapas em uma ação única e não impede o atendimento da ISR. Portanto, declarar eventos como volatile é necessário, mas não é uma proteção completa para toda forma de acesso a ele.

Três propriedades diferentes

Compare os papéis sem confundi-los.

O qualificador resolve a visibilidade dos acessos, mas não bloqueia a intercalação entre ISR e loop.
Propriedadevolatile garante?Significado
Visibilidade do acessoSimO acesso previsto não é descartado como se o valor fosse estável.
AtomicidadeNãoUma operação pode envolver mais de uma etapa.
Exclusão da ISRNãoA interrupção ainda pode ocorrer durante o fluxo principal.

Cheque sua interpretação

Declare os dados que podem mudar

Complete os qualificadores das duas variáveis que a ISR pode modificar. Não acrescente qualificador à cópia local.

Verifique o limite

Verdadeiro ou falso: declarar um contador compartilhado como volatile impede que a ISR o altere durante uma leitura feita pelo loop().

Passo 3 de 8

Veja como uma leitura mistura dois instantes

Veja como uma interrupção pode se intercalar a uma leitura de vários bytes e produzir um valor que nunca esteve armazenado por inteiro.

Por que 16 bits exigem cuidado

Um valor maior que um byte

No ATmega328P do Uno R3, o processador trabalha naturalmente com unidades de 8 bits. Por isso, a leitura de uma variável uint16_t na SRAM pode exigir acessos separados aos seus dois bytes; uma variável de 32 bits pode exigir ainda mais acessos.

Se uma ISR atualizar o valor entre essas partes, o loop() pode combinar bytes obtidos em instantes diferentes. O resultado é uma leitura composta incoerente: um número que nunca esteve armazenado inteiro na variável compartilhada.

volatile continua necessário para que os acessos previstos sejam realizados, mas não transforma vários acessos em uma única ação nem impede a ISR de ser atendida entre eles.

Leitura em partes

Um valor de 16 bits ocupa dois bytes. Uma interrupção pode ocorrer depois que uma parte foi lida e antes da leitura da outra parte.

Diagrama de um contador de 16 bits dividido em byte alto laranja e byte baixo azul, com uma interrupção entre as duas leituras.

O risco está no intervalo entre os acessos aos bytes; volatile não elimina esse intervalo.

A mistura passo a passo

Sequência com transporte

Considere uma hipótese didática explícita para rastrear o problema: o loop() lê primeiro o byte baixo e depois o byte alto. Essa não é uma ordem universal garantida para todo código C/C++ compilado; ela serve para mostrar uma intercalação possível.

O contador compartilhado começa em 0x00FF. O loop() lê o byte baixo, que vale 0xFF. Em seguida, a ISR incrementa o contador para 0x0100: o byte baixo passa para 0x00 e ocorre transporte para o byte alto, que passa para 0x01. Por fim, o loop() lê o byte alto.

Exemplo

Rastreamento do valor

Estado inicial completo: 0x00FF

  1. O loop() lê o byte baixo e obtém 0xFF.
  2. A ISR incrementa o contador: 0x00FF → 0x0100.
  3. O loop() lê o byte alto e obtém 0x01.
  4. Ao combinar alto 0x01 com baixo 0xFF, o loop() forma 0x01FF.

Esse resultado não é o valor completo anterior (0x00FF) nem o posterior (0x0100). Ele mistura dois instantes.

Dois instantes em uma leitura

A transição de 0x00FF para 0x0100 altera os dois bytes e deixa visível a origem da mistura.

Linha do tempo visual em que o loop guarda o byte baixo do estado anterior, a ISR atualiza o contador com transporte e o loop obtém o byte alto do estado posterior.

As cores indicam que as duas partes usadas pelo loop vieram de estados completos diferentes.

Pratique o rastreamento

Organize a intercalação

Usando exatamente a hipótese didática da tela anterior, coloque em ordem os três acessos que produzem a leitura incoerente. O contador começa em 0x00FF.

Ordem que produz a mistura

Ordene os acessos para representar a intercalação possível.

  1. O loop lê o byte baixo e obtém 0xFF.
  2. A ISR incrementa o contador de 0x00FF para 0x0100.
  3. O loop lê o byte alto e obtém 0x01.

Diferencie os resultados

Explique o valor misturado

Com base na ordem que você organizou, qual valor de 16 bits o loop() monta? Explique por que esse valor não corresponde a um instantâneo coerente e por que declarar o contador como volatile não evita essa combinação.

Escreva pelo menos 40 caracteres (0/40).

Coerente não significa permanente

Uma leitura coerente é uma fotografia de um único instante: ela pode ficar antiga logo depois se a ISR atualizar o contador. Isso não é o mesmo que uma leitura incoerente.

No exemplo, 0x00FF seria uma fotografia coerente anterior e 0x0100 uma fotografia coerente posterior. Já 0x01FF não é uma fotografia de nenhum instante completo. No próximo passo, será apresentada uma forma de obter uma cópia coerente antes de usar o valor no restante do loop().

Passo 4 de 8

Reconheça a corrida na leitura e limpeza de uma flag

Uma flag de um byte admite acessos simples, mas consultar e limpar em seguida é uma operação composta sujeita à intercalação da ISR.

A operação tem mais de um acesso

Um byte não resolve toda a sequência

Uma flag como eventoPendente pode ocupar 1 byte na SRAM do ATmega328P. Ler esse byte ou escrever nele é um acesso simples. Mas uma decisão como “se estiver ativa, faça algo e depois limpe” contém vários passos separados.

Entre a consulta e a limpeza, a ISR pode executar e sinalizar a flag outra vez. Portanto, o tamanho de 1 byte evita o problema de uma leitura multibyte incoerente, mas não torna a sequência inteira indivisível.

Consulta seguida de limpeza

Trecho analisado: a ISR registra uma ocorrência, enquanto o loop responde a ela.

cpp
volatile bool eventoPendente = false;

ISR(TIMER1_COMPA_vect) {
  eventoPendente = true;
}

void loop() {
  if (eventoPendente) {
    // Executa a resposta ao evento.
    eventoPendente = false;
  }
}

A janela entre consultar e limpar

Linha do tempo mostrando o loop ler uma flag ativa, a ISR ativá-la novamente e o loop limpá-la depois.

A nova sinalização da ISR pode cair exatamente entre a leitura feita pelo loop e sua escrita de limpeza.

Rastreie a intercalação

Sequência que perde uma sinalização

Considere que eventoPendente já vale true porque houve uma comparação anterior do Timer1. Uma intercalação possível é:

  1. O loop avalia if (eventoPendente) e lê true.
  2. Antes de chegar à atribuição seguinte, ocorre outra comparação; a ISR executa eventoPendente = true.
  3. A ISR termina, e o loop continua.
  4. O loop executa eventoPendente = false com base na decisão tomada no passo 1.

No final, a flag fica false. A sinalização do passo 2 foi apagada sem ser percebida em uma passagem posterior do loop.

Dica

O instante muda o resultado

O resultado depende do instante da interrupção. Se a ISR sinalizasse depois da limpeza, a flag terminaria como true e continuaria pendente. Se sinalizasse antes da consulta, o loop veria a flag ativa normalmente.

A corrida está na janela entre a consulta e a limpeza. Proteger somente uma delas ainda deixaria a outra ponta da janela exposta. A solução precisa tornar a operação lógica completa — retirar o estado e limpá-lo — uma única região indivisível.

Verifique sua análise

Localize a corrida

No código da tela anterior, em qual momento uma nova sinalização da ISR pode ser perdida?

Passo 5 de 8

Copie o contador em uma seção crítica

Proteja somente a cópia de um contador compartilhado e use o valor local fora da seção crítica.

Isole apenas a cópia

O que proteger

Um contador de 16 ou 32 bits alterado pela ISR pode ser copiado pelo loop() enquanto a ISR o atualiza. Como o ATmega328P é um processador de 8 bits, essa cópia pode reunir partes de dois valores, como visto no passo anterior.

A solução é uma seção crítica curta: durante a cópia, as interrupções ficam desabilitadas. Assim, a ISR não se intercala nessa operação. Ao sair da seção, o estado anterior das interrupções é restaurado.

Uma cópia sem intercalação

Diagrama de uma ISR que atualiza um contador compartilhado e do loop que o copia dentro de uma região curta protegida.

A proteção cobre somente a transferência do contador compartilhado para a cópia local.

Use ATOMIC_RESTORESTATE

Código para o Uno R3

No AVR, inclua util/atomic.h e use ATOMIC_BLOCK(ATOMIC_RESTORESTATE).

ATOMIC_RESTORESTATE guarda como as interrupções estavam na entrada do bloco e restaura essa condição na saída. Portanto, se elas já estavam desabilitadas por outro motivo, o bloco não as habilita indevidamente ao terminar.

A ISR é a única escritora de ticks; o loop() apenas obtém uma cópia coerente.

Cópia de um contador de 16 bits

Contador compartilhado e cópia protegida

cpp
#include <avr/interrupt.h>
#include <util/atomic.h>

volatile uint16_t ticks = 0;

ISR(TIMER1_COMPA_vect) {
  ticks++;
}

void loop() {
  uint16_t ticksLocal;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    ticksLocal = ticks;
  }

  // Use ticksLocal daqui em diante.
  if (ticksLocal >= 1000) {
    // Decisões e comandos de saída ficam fora do bloco.
  }
}

Atenção

Mantenha o bloqueio curto

A seção crítica não congela o Timer1: o hardware continua contando. Ela apenas adia o atendimento de interrupções enquanto o bloco executa.

Por isso, não coloque cálculos longos, delay(), laços de espera ou comandos de saída dentro do bloco. Copie o dado e volte a permitir o atendimento o quanto antes.

Verifique a estrutura

Pratique a cópia coerente

Complete o cabeçalho e a região protegida. O objetivo é copiar ticks para ticksLocal sem permitir que a ISR atualize o contador no meio da cópia. A condição e o comando de saída devem continuar fora do bloco.

Complete a seção crítica

Preencha as lacunas do código.

cpp


volatile uint16_t ticks = 0;

void loop() {
  uint16_t ticksLocal;

   {
    
  }

  if (ticksLocal >= 1000) {
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

Confira a restauração

Preveja a saída

Considere este mesmo bloco: ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ticksLocal = ticks; }.

Qual é o estado das interrupções logo após o bloco se elas estavam desabilitadas antes de sua entrada?

Escreva pelo menos 20 caracteres (0/20).

Estado restaurado e instantâneo

Se as interrupções estavam habilitadas ao entrar no mesmo bloco, elas estarão habilitadas após sua saída. Em ambos os casos, o bloco preserva a condição que encontrou.

A cópia local é um instantâneo coerente, não uma promessa de que o contador não mudará logo depois: a ISR pode executar assim que as interrupções voltarem a ser permitidas.

Passo 6 de 8

Retire a flag em uma operação indivisível

Faça a retirada atômica de uma flag para que uma sinalização posterior da ISR permaneça disponível, mantendo o processamento fora da seção crítica.

Transfira o sinal para o loop

Retirar não é apenas consultar

Uma flag compartilhada pode ser retirada sem perder uma sinalização que chegue depois da retirada. A ideia é curta:

  1. copiar a flag compartilhada para uma variável local;
  2. limpar a flag compartilhada;
  3. só então decidir o trabalho com base na cópia local.

As etapas 1 e 2 precisam formar uma única operação protegida. O trabalho em si fica fora do bloco atômico.

A transferência da flag

Diagrama mostrando a ISR sinalizando uma flag, o loop copiando e limpando a flag em uma região protegida e o processamento ocorrendo depois.

A seção crítica transfere o sinal pendente para uma cópia local; ela não deve conter o processamento demorado.

Copie e limpe no mesmo bloco

Código da retirada protegida

Considere eventoPendente escrita pela ISR e consultada por loop(). A cópia local não é compartilhada: depois de receber o valor, ela pode ser usada normalmente fora da região protegida.

Copie, limpe e processe

Retirada atômica de uma flag sinalizada pela ISR.

cpp
#include <util/atomic.h>

volatile bool eventoPendente = false;

ISR(TIMER1_COMPA_vect) {
  eventoPendente = true;
}

void loop() {
  bool atenderEvento;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    atenderEvento = eventoPendente;
    eventoPendente = false;
  }

  if (atenderEvento) {
    // Faça aqui o trabalho associado ao evento.
  }
}

Dica

Por que o bloco é curto

ATOMIC_RESTORESTATE devolve as interrupções ao estado que tinham antes do bloco. Portanto, não habilite interrupções incondicionalmente após essa retirada.

Verifique o padrão

Pratique a operação indivisível

Complete as duas instruções que fazem a retirada da flag. A primeira guarda o sinal para esta passagem do loop(). A segunda libera a flag compartilhada para receber uma nova sinalização.

Complete a retirada

Preencha a retirada protegida da flag.

cpp
bool atenderEvento;

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
  atenderEvento = ;
  eventoPendente = ;
}

if (atenderEvento) {
  // processar fora da seção crítica
}

Separe o sinal atual do próximo

Uma sinalização posterior permanece pendente

Rastreie esta sequência. Inicialmente, eventoPendente vale true.

  • O loop entra no bloco atômico, copia true para atenderEvento e limpa eventoPendente para false.
  • O bloco termina e as interrupções voltam ao estado anterior.
  • A ISR ocorre depois e grava true em eventoPendente.
  • O loop atende o trabalho porque atenderEvento é true.

Organize as consequências na ordem em que acontecem.

Rastreie a nova sinalização

Ordene os passos após a retirada inicial da flag.

  1. Em uma passagem posterior, o loop pode retirar a nova flag pendente.
  2. O bloco atômico termina após copiar true e limpar a flag compartilhada.
  3. A ISR executa e grava true novamente em eventoPendente.
  4. O loop processa o evento indicado pela cópia local atenderEvento.

Atenção

Limite importante

Essa técnica preserva apenas a informação “houve ao menos uma ocorrência”. Se a ISR sinalizar várias vezes enquanto a flag já estiver em true, a flag continua sendo somente true; ela não informa quantas ocorrências houve.

Passo 7 de 8

Escolha entre flag e contador

Selecione entre flag e contador conforme a informação que precisa sobreviver entre a ISR e o loop, transferindo contagens em lote com proteção curta.

Escolha a informação que precisa sobreviver

Duas formas de registrar ocorrências

Uma flag responde a: “houve pelo menos uma ocorrência desde a última retirada?”. Se a ISR executar várias vezes enquanto a flag já vale true, a informação continua sendo apenas “sim”.

Um contador responde a: “quantos atendimentos da ISR ocorreram?”. Cada atendimento incrementa o valor, até o limite do tipo. Escolha pelo dado que o loop realmente precisa preservar.

Comparação visual: várias ocorrências formam uma única flag ativa à esquerda e incrementam um contador à direita.

A flag agrega ocorrências; o contador preserva a quantidade de atendimentos, dentro da faixa disponível.

Exemplo

Escolha pelo requisito

Use uma flag quando basta solicitar uma atualização pendente — por exemplo, recalcular uma saída uma vez no loop. Use um contador quando cada atendimento precisar entrar no processamento, como acumular quantos intervalos foram atendidos.

Flag ou contador?

Compare o que cada representação mantém enquanto o loop ainda não consumiu o dado.

A representação define qual informação sobre ocorrências repetidas permanece disponível.
Necessidade do loopRepresentaçãoInformação preservada
Há trabalho pendente?FlagPelo menos uma ocorrência
Processar cada atendimento?ContadorQuantidade de atendimentos
Três sinais chegam com flag ativaFlagContinua apenas ativa
Três atendimentos incrementamContadorValor aumenta em três

Transfira a contagem; processe depois

Retire um lote de uma vez

Para consumir um lote, copie o contador compartilhado e zere-o no mesmo bloco atômico. Assim, o lote retirado fica em lote; um incremento da ISR depois do bloco permanece em atendimentos para uma passagem futura do loop().

O processamento do lote — decisões, cálculos e comandos de saída — fica fora do bloco. A região protegida só transfere a informação compartilhada.

Exemplo

Exemplo de transferência

Neste padrão, a ISR é a única escritora por incremento; o loop transfere o valor acumulado e o reinicia dentro da região curta.

Cópia e zeragem protegidas

Contador compartilhado incrementado pela ISR e lote local consumido pelo loop.

cpp
#include <util/atomic.h>

volatile uint16_t atendimentos = 0;

ISR(TIMER1_COMPA_vect) {
  atendimentos++;
}

void loop() {
  uint16_t lote;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    lote = atendimentos;
    atendimentos = 0;
  }

  if (lote > 0) {
    // Processa o lote aqui, fora da seção crítica.
  }
}

Conheça os dois limites da contagem

Atenção

Capacidade também é requisito

O contador não é infinito. Se ele alcançar o maior valor do tipo e receber outro incremento, ocorre retorno a zero. Um tipo mais largo adia esse problema, mas não elimina a necessidade de o loop consumir a contagem a tempo.

Defina uma capacidade coerente com a frequência de atendimentos e com o maior tempo previsto até a retirada.

O contador registra atendimentos, não tudo que ocorreu

Há ainda dois níveis diferentes de informação:

  1. Eventos do periférico: comparações físicas do Timer1.
  2. Atendimentos da ISR: vezes que a rotina conseguiu executar.

Uma pendência de comparação no hardware é uma indicação, não uma fila. Se novas comparações ocorrerem enquanto uma pendência já está aguardando atendimento, elas podem se agregar. Portanto, contar execuções da ISR não recupera automaticamente todas as comparações físicas que não ficaram registradas separadamente.

Diagrama em camadas mostrando várias comparações do temporizador se agregando em uma pendência de hardware e um único atendimento da ISR incrementando o contador de software.

O software conta atendimentos que ocorreram; ele não cria registros separados para eventos físicos que o hardware já agregou.

Verifique suas decisões

Pratique a escolha

Associe cada requisito à representação mais adequada.

Toque em um item e depois no par correspondente.

Retire o lote sem perder a sinalização

Complete a retirada atômica do lote de atendimentos.

cpp
uint16_t lote;

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
   = atendimentos;
   = 0;
}

if (lote > 0) {
  // Processamento fora do bloco
}

Passo 8 de 8

Aplique: registre na ISR e processe no loop

Conclua uma solução em que a ISR registra atendimentos do Timer1, o loop transfere a contagem com proteção e processa o resultado fora da seção crítica.

Arquitetura aplicada

Um fluxo com responsabilidades separadas

Uma solução completa, com papéis separados

Neste firmware, o Timer1 gera atendimentos periódicos. A ISR registra cada atendimento com rapidez; o loop() transfere a quantidade acumulada e decide o estado do LED depois que as interrupções foram restauradas.

Mapa de acessos de atendimentos:

  • ISR: lê e incrementa atendimentos.
  • Loop: lê atendimentos ao copiar o lote e escreve 0 ao concluir a transferência.
  • lote e ledAceso: pertencem somente ao loop().

Como a ISR pode modificar atendimentos fora da sequência normal do loop(), ele é volatile. Como a cópia e a zeragem são uma operação composta, ambas ficam na mesma seção crítica. O contador é apropriado porque a aplicação preserva quantos atendimentos da ISR ocorreram, e não apenas se houve algum.

Caminho dos dados

Diagrama do Timer1 acionando uma ISR curta, seguido pela transferência atômica de um contador e pelo processamento posterior no loop.

A seção crítica transfere o lote: copia e zera o contador. O processamento e o comando do LED ficam fora dela.

Atenção

Limite da contagem

uint16_t comporta valores de 0 a 65535. O loop() precisa retirar os lotes com frequência suficiente para que atendimentos não retorne a zero por excesso de incrementos.

O contador representa atendimentos da ISR, não necessariamente todas as comparações físicas do periférico. Se várias comparações se acumularem em uma única pendência de hardware antes do atendimento, elas podem já ter sido agregadas antes de a ISR executar.

Programa completo para testar

Sketch completo: registrar e processar

Prática opcional no Uno R3: crie um sketch, grave-o na placa alimentada somente pelo USB e observe o LED integrado. A ISR só incrementa o contador. O loop() retira o lote protegido, aplica as alternâncias necessárias e comanda o LED fora da seção crítica.

cpp
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>

volatile uint16_t atendimentos = 0;

ISR(TIMER1_COMPA_vect) {
  atendimentos++;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);

  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 31249;

  TCCR1B |= _BV(WGM12);
  TIFR1 = _BV(OCF1A);
  TIMSK1 |= _BV(OCIE1A);
  TCCR1B |= _BV(CS12);
}

void loop() {
  uint16_t lote;
  static bool ledAceso = false;

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    lote = atendimentos;
    atendimentos = 0;
  }

  if (lote > 0) {
    if ((lote & 1U) != 0U) {
      ledAceso = !ledAceso;
    }
    digitalWrite(LED_BUILTIN, ledAceso ? HIGH : LOW);
  }
}
Saída esperada
LED integrado: alterna aproximadamente a cada 0,5 s.
Nenhuma mensagem é exibida no monitor serial.

O resultado é físico, não uma saída no monitor serial. Se o loop estiver sendo atendido normalmente, cada comparação gera uma alternância. Um lote maior também é processado fora da seção crítica.

Dica

O que observar

O Timer1 foi configurado para comparação a cada aproximadamente 0,5 s. Em condições normais, o loop() retira um atendimento por vez e o LED alterna nesse ritmo.

A aparência regular do LED é uma observação funcional, mas não prova por si só que não existe corrida. A justificativa está na sequência de acessos: cópia e zeragem ocorrem juntas no bloco atômico; cálculos e escrita no LED acontecem depois dele.

Verifique as decisões essenciais

Transfira o lote sem janela de corrida

Complete a transferência do contador compartilhado para o lote local. A seção crítica deve conter apenas a transferência; o processamento continua fora dela.

cpp
uint16_t lote;

ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
   = ;
  atendimentos = ;
}

if (lote > 0) {
  // Processar o lote fora da seção crítica.
}

Justifique e rastreie a transferência

Explique por que este firmware usa um contador, e não uma flag, para atendimentos. Em seguida, rastreie a sequência: o contador vale 3; o loop entra no bloco, copia 3 para lote e zera atendimentos; logo depois de sair do bloco, a ISR executa uma vez.

Qual lote é processado agora e qual valor fica disponível para a próxima passagem?

Escreva pelo menos 90 caracteres (0/90).

Aplique e conclua

Resumo

Síntese final

  • Uma variável acessada pela ISR e pelo loop deve ter leitores e escritores mapeados corretamente. Neste exemplo, a ISR incrementa atendimentos e o loop o zera durante a transferência.
  • volatile exige os acessos previstos ao dado compartilhado, mas não torna uma sequência atômica.
  • Para retirar um lote, copie atendimentos e zere-o no mesmo ATOMIC_BLOCK(ATOMIC_RESTORESTATE).
  • Use flag para registrar “houve ao menos uma ocorrência” e contador para preservar a quantidade de atendimentos dentro da capacidade escolhida.
  • ISR registra, seção crítica transfere e loop processa. Evite colocar decisões, cálculos demorados e comandos de saída na ISR ou na seção crítica.

Tutorial concluído

Parabéns! Você concluiu: Dados compartilhados entre interrupções e loop

Você concluiu o tutorial: registre rapidamente na ISR, transfira os dados compartilhados de modo indivisível e processe o resultado no loop.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso