Trilha de aprendizado · Nível 10 · Tutorial 9

Diagnóstico de firmware temporizado

Localizar e corrigir falhas de compilação e execução em firmware com estados, temporizadores, interrupções e PWM, usando a IDE local, saídas de diagnóstico e casos reproduzíveis.

  • Nível: Intermediário
  • Duração: 20 min
  • 8 passos
Diagnóstico de firmware temporizado

O que você vai percorrer

  1. Identifique a etapa da falha Classifique a falha antes de alterar o firmware e confirme a gravação antes de investigar o comportamento observado. 2 min
  2. Encontre a causa na mensagem da IDE Use a primeira mensagem relevante da IDE para corrigir símbolos e definições de vetor de forma localizada. 3 min
  3. Prepare um caso que possa ser repetido Estabeleça uma referência conhecida, registre uma sequência de teste e reduza uma variante sem eliminar a evidência necessária. 3 min
  4. Torne o fluxo observável Marque pontos do firmware com um sinal reservado, interprete o que um LED ou traço realmente mostra e limite a interferência do diagnóstico. 2 min
  5. Diferencie as causas de falta de resposta Diferencie bloqueio, erro de estado e falhas no atendimento de interrupções com marcas de fluxo e sequências reproduzíveis. 3 min
  6. Descubra quem está alterando o recurso Audite escritores de pinos e temporizadores para localizar configurações sobrescritas e conflitos de recursos. 3 min
  7. Localize a falha nos dados compartilhados Audite acessos entre ISR e loop, reconstrua intercalações vulneráveis e escolha uma proteção mínima conforme o tipo de dado. 3 min
  8. Aplicação final: corrija e verifique o conjunto Aplique uma correção localizada em uma variante reproduzível e confirme, pela mesma sequência de testes, o comportamento do botão, da sinalização temporal e do PWM. 3 min

O que você vai aprender

  • Distinguir falhas de compilação, gravação e comportamento observado.
  • Corrigir um erro de símbolo ou de vetor de interrupção a partir do diagnóstico da IDE.
  • Escolher uma observação que diferencie bloqueio, estado incorreto e conflito de temporizador.
  • Localizar um acesso compartilhado vulnerável e justificar sua correção.
  • Repetir testes de botão, sinalização temporal e PWM após a alteração para verificar efeitos sobre as demais funções.

Antes de começar

  • Máquinas de estados no firmware
  • Controle de GPIOs por registradores no ATmega328P
  • Interrupções de comparação do Timer1
  • Dados compartilhados entre interrupções e loop
  • Controle de brilho com PWM no Uno R3

Passo 1 de 8

Identifique a etapa da falha

Classifique a falha antes de alterar o firmware e confirme a gravação antes de investigar o comportamento observado.

Localize onde o fluxo parou

Quatro etapas, quatro evidências

Antes de mudar o firmware, descubra em que etapa ele parou. O fluxo tem quatro etapas:

  1. Compilação: o código é traduzido; erros apontam construções inválidas, nomes desconhecidos ou tipos incompatíveis.
  2. Ligação: as partes já compiladas são combinadas; falhas indicam, por exemplo, uma definição prometida mas não encontrada ou duplicada.
  3. Gravação: o programa gerado é transferido para o Uno pela conexão selecionada.
  4. Execução: a placa inicializa e roda a versão que foi gravada.

Cada etapa exige uma evidência diferente. Um LED com comportamento inesperado só é evidência de execução; ele não explica um erro que impediu a compilação ou a gravação.

Fluxo visual do código no computador até a execução na placa, dividido em compilação, ligação, gravação e execução.

Localize a interrupção no fluxo antes de escolher a próxima ação.

Associe sintoma e evidência

Evidência certa para cada etapa

Use a evidência produzida na etapa correspondente. Leia a saída completa da IDE, mas separe o diagnóstico específico do resumo final genérico.

A pergunta útil muda conforme a última etapa que teve sucesso.
EtapaO que está acontecendoEvidência principalPróxima pergunta
CompilaçãoCódigo é traduzidoDiagnóstico com arquivo e linhaQual construção a mensagem aponta?
LigaçãoDefinições são combinadasMensagem sobre símbolo ou definiçãoQual definição falta ou se repete?
GravaçãoPrograma vai para a placaMensagem de transferência da IDEA placa, porta e conexão foram aceitas?
ExecuçãoVersão gravada começa a rodarObservação da montagem após resetO comportamento coincide com o esperado?

Dica

Regra de triagem

A ordem importa: só investigue o comportamento da nova versão depois que a IDE confirmar a gravação. Até lá, não conclua que uma mudança no código “não funcionou”.

Não confunda firmware antigo com falha nova

Situação: o LED ainda pisca

Você altera o código para mudar o ritmo do LED. A IDE mostra erro de gravação. Mesmo assim, o LED continua piscando como antes.

Qual é a conclusão mais bem sustentada?

Escolha a etapa que precisa ser resolvida antes de interpretar o LED.

A aparência do LED, neste caso, não confirma a execução da alteração.
ObservaçãoConclusão válida
A IDE falhou ao gravarA nova versão pode não estar na placa
O LED mantém o padrão antigoA placa pode estar executando a versão anterior
Ainda não há confirmação de gravaçãoNão é válido diagnosticar a lógica nova

Decida pela evidência

Qual ação vem primeiro?

Escreva pelo menos 20 caracteres (0/20).

Prática de classificação

Classifique quatro situações

Associe cada situação à etapa em que a evidência deve ser examinada primeiro.

Escreva pelo menos 80 caracteres (0/80).

Exemplo

Modelo de resposta

Use este formato na sua resposta:

  • “nome de registrador desconhecido” → ...
  • “símbolo declarado, mas não encontrado ao combinar o programa” → ...
  • “a IDE não consegue transferir para a placa” → ...
  • “gravação confirmada, mas botão não produz a ação esperada” → ...

Passo 2 de 8

Encontre a causa na mensagem da IDE

Use a primeira mensagem relevante da IDE para corrigir símbolos e definições de vetor de forma localizada.

Leia o diagnóstico como um endereço

Comece pela primeira causa provável

Quando a IDE falhar, não comece pelo resumo final, como “erro ao compilar”. Leia o primeiro diagnóstico que aponta um arquivo, uma linha e um problema específico. Mensagens abaixo dele podem ser consequências do mesmo erro.

Siga este roteiro:

  1. Localize a primeira linha relevante.
  2. Abra o arquivo e a linha indicados.
  3. Relacione o símbolo ou a construção ao código nessa posição.
  4. Faça uma correção localizada.
  5. Compile novamente antes de interpretar o restante.

Assim, você evita corrigir sintomas em cascata ou alterar nomes ao acaso.

O fluxo visual mostra por que a primeira mensagem específica deve guiar a investigação.

Diagrama com a primeira mensagem da IDE destacada e ligada a uma linha de código, enquanto mensagens posteriores aparecem esmaecidas.

A primeira mensagem específica costuma apontar a construção que deve ser verificada primeiro.

Exemplo

Exemplo

Exemplo de leitura:

sketch.ino:24:5: error: 'OCR1AA' was not declared in this scope

A parte útil não é o resumo de falha ao fim da saída. Ela informa o arquivo (sketch.ino), a linha (24), a coluna (5) e o identificador que a compilação não reconheceu (OCR1AA). Ao abrir essa linha, compare o nome usado com o registrador esperado, OCR1A.

Separe placa, grafia e inclusão

Classifique antes de corrigir

Nem todo símbolo não reconhecido tem a mesma causa. Use o contexto do arquivo, da placa selecionada e da linha indicada para separar hipóteses.

No Uno R3, símbolos como OCR1A, TCCR1B e TIMER1_COMPA_vect pertencem ao ambiente AVR. Se a placa selecionada não for compatível com AVR, esses nomes não representam o hardware selecionado. Volte à seleção da placa antes de editar o firmware.

Se a placa for o Uno R3 e apenas um nome próximo falhar, examine a grafia. OCR1AA não é um registrador válido: acrescentar ou remover um caractere pode ser a única correção necessária.

Dica

Confira o contexto do arquivo

Outras possibilidades dependem do contexto:

  • Em um arquivo .ino, o ambiente Arduino normalmente prepara inclusões básicas. Não conclua que falta um cabeçalho apenas porque um símbolo falhou.
  • Em um arquivo adicional .cpp, a inclusão necessária pode realmente não estar presente nesse próprio arquivo. Para usar construções de ISR AVR nele, verifique se ele inclui o cabeçalho apropriado.
  • Se vários símbolos AVR falham juntos, confira primeiro a placa e o pacote selecionados; não tente “consertar” cada nome individualmente.

A evidência deve combinar mensagem, arquivo e configuração atual.

Decida com base na evidência

Qual é a melhor primeira ação diante de OCR1AA was not declared in this scope em uma linha do seu sketch, com Uno R3 selecionado?

Escreva pelo menos 30 caracteres (0/30).

Dê ao vetor um único responsável

Quando o vetor aparece duas vezes

Um erro de ligação como “multiple definition of __vector_11” costuma indicar que duas partes do programa definiram a mesma rotina de interrupção. A causa pode estar em dois trechos do sketch ou em um arquivo incluído.

Não troque o nome do vetor apenas para fazer o erro desaparecer. Para a comparação A do Timer1 no Uno R3, o vetor continua sendo TIMER1_COMPA_vect. Procure todas as ocorrências de ISR( e deixe um único responsável por esse vetor. Se houver comportamentos necessários nas duas rotinas, reúna o registro mínimo de eventos em uma única ISR curta e deixe o processamento para o loop().

Definição única da ISR

Mantenha somente uma definição para o vetor de comparação A do Timer1.

cpp
#include <avr/interrupt.h>

volatile bool tickPendente = false;

ISR(TIMER1_COMPA_vect) {
  tickPendente = true;
}

Verifique o vetor

Complete o nome do vetor na única definição de ISR destinada à comparação A do Timer1. A correção não é criar outro nome: é associar a rotina ao vetor correto e garantir que não exista uma segunda definição dele.

Escreva pelo menos 15 caracteres (0/15).

Compile novamente antes de avançar

Valide uma alteração por vez

Após mudar uma grafia, uma inclusão ou uma definição duplicada, compile de novo imediatamente. Só depois avalie a próxima mensagem que permanecer. Uma correção localizada pode eliminar vários avisos derivados.

Use uma sequência curta de verificação:

  1. Salve a alteração mínima.
  2. Confirme que a placa correta continua selecionada.
  3. Compile.
  4. Se houver erro, leia novamente o primeiro diagnóstico relevante.

Enquanto a compilação falha, não investigue o comportamento da placa: a nova versão ainda não está pronta para ser gravada.

Feche o ciclo de diagnóstico

Uma saída mostra primeiro OCR1AA was not declared in this scope na linha 24 e, depois, um resumo “erro ao compilar”. Após trocar apenas OCR1AA por OCR1A, qual mensagem deve orientar sua próxima ação se a compilação ainda falhar?

Escreva pelo menos 45 caracteres (0/45).

Passo 3 de 8

Prepare um caso que possa ser repetido

Estabeleça uma referência conhecida, registre uma sequência de teste e reduza uma variante sem eliminar a evidência necessária.

Defina o que será repetido

Defina a receita do teste

Um caso reproduzível é uma receita de teste: outra execução, nas mesmas condições, deve permitir comparar o resultado. Antes de remover ou alterar código, registre:

  1. Versão do programa: preserve uma cópia da referência conhecida.
  2. Placa e conexão: Uno R3, porta selecionada e cabo USB de dados.
  3. Montagem: botão entre D2 e GND; LED externo com resistor em série entre D3 e GND.
  4. Condição inicial: por exemplo, após reset e com o botão solto.
  5. Sequência: ações e esperas na mesma ordem.
  6. Esperado e observado: descreva o que deveria ocorrer e o que você realmente viu.

“Não funciona” não é reproduzível. Já “após reset, D13 alterna; após um pressionamento estável em D2, D3 troca de brilho” define uma sequência verificável.

Montagem e sequência conhecidas

Diagrama de uma placa tipo Uno com botão em D2, LED externo em D3 e uma sequência visual de reset, espera, pressionamento e soltura.

A mesma montagem, condição inicial e sequência tornam significativa a comparação entre versões.

Atenção

Segurança da montagem

Monte somente em baixa tensão e alimente a placa por uma única conexão USB apropriada. Use resistor em série com o LED externo para manter a corrente pequena. Desconecte o USB antes de mudar fios. Não conecte uma segunda fonte ao pino de 5 V ou a VIN durante esta prática.

Monte a versão de referência

Prepare a referência conhecida

Crie um sketch novo na sua IDE. Copie os três blocos a seguir no mesmo arquivo e nesta ordem. Esta referência mantém responsabilidades separadas:

  • D2 recebe o botão com pull-up interno;
  • D3 recebe PWM pelo Timer2;
  • D13 alterna lentamente por comparação do Timer1;
  • a ISR registra ticks e permanece curta;
  • o loop() lê ticks como instantâneo protegido;
  • Timer0 não é reconfigurado.

Compile e grave a referência antes de testar uma variante. Só compare o comportamento após a IDE confirmar a gravação.

Referência: declarações, Timer1 e ISR

Bloco 1 — cole no início do mesmo sketch.

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

const uint8_t PINO_BOTAO = 2;
const uint8_t PINO_PWM = 3;

volatile uint16_t ticks = 0;

bool leituraAnterior = HIGH;
bool botaoEstavel = HIGH;
unsigned long mudouEm = 0;
const unsigned long DEBOUNCE_MS = 30;

bool brilhoAlto = false;

void configurarTimer1() {
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 31249;                 // 500 ms com prescaler 256
  TIFR1 = _BV(OCF1A);            // limpa pendência
  TIMSK1 = _BV(OCIE1A);          // comparação A
  TCCR1B = _BV(WGM12) | _BV(CS12); // CTC e prescaler 256
}

ISR(TIMER1_COMPA_vect) {
  PINB = _BV(PB5);               // alterna D13
  ticks++;
}

Referência: inicialização

Bloco 2 — cole logo após o bloco 1.

cpp
void setup() {
  pinMode(PINO_BOTAO, INPUT_PULLUP);
  pinMode(PINO_PWM, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  digitalWrite(LED_BUILTIN, LOW);
  analogWrite(PINO_PWM, 40);
  configurarTimer1();
}

Execute e registre a referência

Referência: leitura do botão e loop

Bloco 3 — cole após o bloco 2. O sketch estará completo. Compile, grave e confira visualmente o resultado na sua montagem.

cpp
void loop() {
  bool leitura = digitalRead(PINO_BOTAO);
  unsigned long agora = millis();

  if (leitura != leituraAnterior) {
    leituraAnterior = leitura;
    mudouEm = agora;
  }

  if ((agora - mudouEm >= DEBOUNCE_MS) &&
      (leitura != botaoEstavel)) {
    botaoEstavel = leitura;

    if (botaoEstavel == LOW) {
      brilhoAlto = !brilhoAlto;
      analogWrite(PINO_PWM, brilhoAlto ? 180 : 40);
    }
  }

  uint16_t copiaTicks;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    copiaTicks = ticks;
  }

  (void)copiaTicks;
}
Saída esperada
Após a gravação bem-sucedida e um reset:
- D13 alterna visualmente aproximadamente a cada 500 ms.
- O LED externo em D3 começa com brilho baixo.
- Após um pressionamento estável em D2, D3 muda para brilho mais alto.
- Após soltar e pressionar D2 de novo de forma estável, D3 retorna ao brilho baixo.
- D13 continua alternando durante os acionamentos.

Resultado visual esperado, não uma medição de precisão: a alternância observada de D13 ocorre aproximadamente a cada 500 ms, e o brilho percebido em D3 depende também do LED e do ambiente.

Registre esperado e observado

Faça a sequência sem Monitor Serial:

  1. Pressione reset e aguarde cerca de 3 segundos.
  2. Observe D13 e o brilho inicial de D3.
  3. Pressione D2 e mantenha por tempo suficiente para o debounce.
  4. Solte D2.
  5. Pressione e solte novamente.

Registre observações, não causas. Exemplo útil: “referência; reset; D13 alternou visualmente; primeiro pressionamento mudou D3 para brilho alto; segundo pressionamento retornou ao brilho baixo”.

Sequência de verificação

Use esta mesma sequência para a referência e para cada variante.

Preencha a última coluna com o que você observou na sua montagem.
AçãoResultado esperadoSeu observado
Reset e esperaD13 alterna; D3 em brilho baixoAnote
Primeiro pressionamento estável em D2D3 muda para brilho altoAnote
Soltura de D2D3 mantém o brilho atualAnote
Segundo pressionamento estávelD3 volta ao brilho baixo; D13 continuaAnote

Reduza mantendo a comparação

Reduza sem apagar a evidência

Ao investigar uma variante, mantenha a referência intacta e trabalhe em uma cópia. Retire ou neutralize uma função por vez, compile, grave e repita a sequência registrada.

Uma simplificação só é útil se o sintoma continuar e se as dependências do sintoma permanecerem. Se a pergunta é “D3 muda de brilho após D2?”, retirar temporariamente o Timer1 pode ser uma redução válida: botão e PWM continuam presentes. Remover a leitura de D2, porém, invalida a comparação, porque o evento que deveria provocar a mudança desapareceu.

Após cada alteração isolada, anote o que permaneceu, o que foi retirado e se o sintoma ainda ocorreu.

Atenção

Organize a reprodução e a redução

Ordene um procedimento para investigar uma variante sem perder a capacidade de comparar resultados.

Ordene o procedimento

Coloque as ações na ordem mais confiável.

  1. Compilar, gravar e executar a sequência conhecida na referência; anotar o observado.
  2. Salvar a referência conhecida e registrar sua montagem, condição inicial e sequência de teste.
  3. Retirar ou neutralizar uma única função na cópia, mantendo as dependências do sintoma.
  4. Criar uma cópia da variante com falha, preservando a referência sem alterações.
  5. Compilar, gravar e repetir exatamente a mesma sequência; comparar o resultado com os registros.

Preserve a dependência essencial

O sintoma de uma variante é: “D3 não muda de brilho após um pressionamento estável em D2”. Qual simplificação mantém uma comparação válida para investigar a participação do Timer1?

Passo 4 de 8

Torne o fluxo observável

Marque pontos do firmware com um sinal reservado, interprete o que um LED ou traço realmente mostra e limite a interferência do diagnóstico.

Escolha uma marca independente

Reserve um indicador de diagnóstico

Uma marca de diagnóstico responde a uma pergunta delimitada: “a execução chegou até este ponto?” Escolha pontos que separem caminhos possíveis: antes e depois de uma chamada suspeita, na transição de estado ou na entrada e na saída de uma ISR.

No caso-base, reserve D7 para a marca. Não use D13, que já é a sinalização temporal, nem D3, que permanece sob PWM. Assim, o diagnóstico não disputa o pino cuja função você quer verificar.

Para observar um nível persistente por LED, desconecte o USB e monte um LED próprio em D7: D7 → resistor em série → ânodo do LED; cátodo → GND. Use corrente pequena, como nas práticas anteriores. Reconecte somente depois de conferir a montagem. Se tiver osciloscópio ou analisador lógico, também pode observar D7 diretamente com a ponta no pino e a referência do instrumento no GND da montagem.

D7 separado das funções observadas

Diagrama de uma placa estilo Arduino com D13 para sinalização funcional, D3 para PWM e D7 ligado a um LED externo de diagnóstico por resistor.

D7 é uma marca independente: D13 e D3 continuam disponíveis para observar as funções originais.

Delimite uma região do fluxo

Acrescente marcas sem duplicar funções

No sketch de referência do step anterior, não copie outro setup() nem outro loop(). Faça somente estas alterações localizadas:

  1. Perto das constantes já existentes, acrescente const uint8_t PINO_MARCA = 7;.
  2. Dentro do setup() que já existe, acrescente a configuração de D7.
  3. Dentro do bloco que trata um pressionamento estável, deixe D7 alto imediatamente antes da chamada real analogWrite(...) e baixo logo depois dela.

Esse primeiro teste usa uma chamada que realmente existe no caso-base. Ela normalmente retorna muito rápido: o LED em D7 pode não mostrar nada visível, mas um instrumento pode revelar um pulso. Em uma variante que contenha uma chamada realmente suspeita, use as mesmas duas marcas ao redor daquela chamada, sem alterar sua posição no fluxo.

Marque a chamada real do caso-base

Linhas para acrescentar ao sketch de referência. A segunda parte substitui somente a chamada analogWrite(...) que já está dentro de if (botaoEstavel == LOW).

cpp
// Acrescente perto das outras constantes globais:
const uint8_t PINO_MARCA = 7;

// Acrescente dentro do setup() que já existe:
pinMode(PINO_MARCA, OUTPUT);
digitalWrite(PINO_MARCA, LOW);

// Dentro de loop(), substitua apenas esta linha existente:
// analogWrite(PINO_PWM, brilhoAlto ? 180 : 40);

// por estas três linhas:
digitalWrite(PINO_MARCA, HIGH);          // antes da chamada
analogWrite(PINO_PWM, brilhoAlto ? 180 : 40);
digitalWrite(PINO_MARCA, LOW);           // só ocorre após o retorno
Saída esperada
Após um pressionamento estável no caso-base:
D3: alterna entre os níveis de brilho 40 e 180.
D7 com LED: pode parecer apagado, pois a marca é muito curta.
D7 com instrumento: pulso que sobe antes de analogWrite e desce após a chamada.

Se uma chamada substituída não retornar:
D7 com LED: permanece aceso.
D7 com instrumento: sobe e permanece em nível alto durante a observação.

O resultado é físico, não uma saída no Monitor Serial. Com um LED, o pulso curto pode não ser perceptível; com instrumento, procure uma subida seguida de descida em D7.

Respeite o limite da observação

Leia o que as bordas permitem concluir

A marca alta antes de uma chamada e baixa depois dela tem uma interpretação limitada, mas útil:

  • D7 permanece alto: o fluxo alcançou a marca anterior, mas não alcançou a marca posterior durante o teste. A chamada pode estar bloqueada ou ter seguido um caminho que não retornou.
  • Subida e descida em D7: o fluxo passou pela região observada. Isso não mede, por si só, o tempo exato da chamada.

Para marcar uma ISR, o princípio é o mesmo: coloque D7 alto na entrada e baixo antes de sair. Não use delay(), laços de espera nem uma marca baseada apenas em millis(). Se a hipótese for interrupções globais desabilitadas, uma observação dependente de serviços temporais pode falhar junto com aquilo que você tenta investigar.

Dica

Não transforme o diagnóstico em nova falha

Um LED mostra bem um nível persistente ou uma mudança lenta. Ele não mede a duração de um pulso: brilho fraco, apagado ou aparentemente contínuo não prova quantos microssegundos D7 ficou alto.

Para pulsos na entrada e saída de ISR, use um osciloscópio ou analisador lógico próprio, se disponível. Trabalhe apenas na montagem de baixa tensão, confirme que os níveis aceitos pelo instrumento são compatíveis e conecte primeiro a referência de terra ao GND da montagem. Uma marca deve ser curta e sem espera; depois de inseri-la, repita a sequência conhecida e compare se o sintoma mudou.

Teste sua interpretação

Interprete um pulso rápido

Após um pressionamento estável, um osciloscópio mostra em D7 um pulso alto de poucos microssegundos: há uma borda de subida e, logo depois, uma borda de descida. O LED externo em D7 parece não mudar. Qual conclusão é mais bem sustentada?

Escolha uma marca robusta

Você suspeita que uma variante manteve as interrupções globais desabilitadas. Qual marca é mais adequada para verificar se o fluxo passa por uma região do loop()?

Passo 5 de 8

Diferencie as causas de falta de resposta

Diferencie bloqueio, erro de estado e falhas no atendimento de interrupções com marcas de fluxo e sequências reproduzíveis.

Construa evidências, não conclusões isoladas

Um sintoma pode ter várias causas

Um LED parado é um sintoma, não um diagnóstico. Para separar causas, observe marcas em três regiões independentes:

  • fluxo principal: uma marca breve em cada passagem relevante de loop();
  • transição: uma marca somente quando o estado realmente muda;
  • ISR: uma marca na entrada e outra na saída da rotina.

Compare essas marcas após uma sequência conhecida: reset, pressionar o botão, manter pressionado, soltar e aguardar. Uma hipótese ganha força quando explica o conjunto de evidências — não apenas uma saída imóvel.

Importante: o PWM é gerado pelo hardware. Um LED com brilho PWM pode continuar aceso mesmo se o loop() estiver bloqueado. Da mesma forma, o Timer1 pode continuar comparando enquanto sua ISR não é atendida.

Marcas em regiões diferentes

Diagrama temporal com traços separados para fluxo principal, transição de estado, comparações de hardware e atendimento da ISR.

As comparações e o PWM podem permanecer ativos no hardware enquanto o atendimento do firmware falha.

Padrões que ajudam a separar hipóteses

Use as marcas como evidência combinada. Elas devem ser curtas e não usar delay(); ao investigar interrupções indisponíveis, não faça a marca depender somente de millis().

Cada padrão é compatível com uma hipótese; confirme com uma observação adicional quando necessário.
HipóteseEvidência mais útilInterpretação inicial
Espera bloqueante no loopMarca antes da chamada aparece; a posterior atrasaO fluxo não retorna durante a espera
Condição de estado impossívelMarca do loop continua; transição esperada não ocorreO evento não satisfaz a regra de transição
ISR excessivamente longaEntrada da ISR aparece; saída demoraA ISR retém a execução e adia o loop
Interrupções desabilitadasComparação ocorre; entrada da ISR não apareceO evento de hardware não está sendo atendido

Use a sequência de eventos para testar a lógica

Procure condições que a sequência não consegue satisfazer

Uma sequência conhecida também revela erros lógicos sem depender de “acontecer por acaso”. Suponha que, após reset, o firmware esteja em ESPERA e que um pressionamento de botão deva levá-lo a ATIVO.

No trecho abaixo, a condição pede simultaneamente botão pressionado e estado ATIVO. Como o botão é justamente o evento que deveria sair de ESPERA, essa transição nunca acontece. A marca do loop pode continuar normal; o que falta é a marca de entrada em ATIVO.

Condição de transição incompatível

Trecho para inspecionar: o estado inicial é ESPERA.

cpp
if (botaoPressionado && estado == ATIVO) {
  estado = ATIVO;
  marcaEntradaEstado();
}

Verifique a marca de entrada do estado

Outro erro frequente preserva a condição correta, mas apaga o tempo decorrido. Se esta linha estiver dentro do trecho executado repetidamente enquanto o estado é AGUARDANDO, a referência é renovada antes de completar 500 ms. A condição temporal permanece falsa.

A evidência esperada é: o fluxo principal continua passando, mas a transição temporal não ocorre. Verifique se a marca de entrada no estado foi alterada uma única vez na transição, e não em toda passagem pelo estado.

Referência temporal reiniciada

Trecho para inspecionar: entradaEstado deveria representar o instante de entrada no estado.

cpp
case AGUARDANDO:
  entradaEstado = millis();

  if (millis() - entradaEstado >= 500) {
    estado = ATIVO;
  }
  break;

Relacione o sintoma à evidência

Associe hipótese e evidência

Relacione cada hipótese à observação que mais diretamente a sustenta.

Toque em um item e depois no par correspondente.

Escolha uma observação que separe hipóteses

Escolha a próxima observação

Durante o teste, o LED PWM em D3 mantém o brilho, mas a resposta ao botão parou. A marca periódica do fluxo principal também deixou de aparecer. Qual é o próximo teste mais discriminante?

Passo 6 de 8

Descubra quem está alterando o recurso

Audite escritores de pinos e temporizadores para localizar configurações sobrescritas e conflitos de recursos.

Faça o inventário dos recursos

Audite quem escreve

Quando uma função deixa de responder, não suponha que a última linha alterada seja a causa. Faça uma auditoria dos escritores: para cada recurso, registre sua função esperada, todos os trechos que podem configurá-lo ou comandá-lo e quando isso acontece.

Um recurso pode ser um pino, um registrador ou um temporizador. Dois escritores podem parecer funcionar isoladamente, mas disputar o mesmo hardware ao longo da execução.

Dica

Neste caso-base, a divisão compatível mantém o Timer0 para os serviços do núcleo Arduino, usa o Timer1 somente para a sinalização temporal em CTC e reserva o Timer2 para o PWM no D3. Um PWM esperado em D9 ou D10 não é compatível com essa configuração do Timer1.

Mapa de recursos do caso-base

Diagrama separando Timer0, Timer1 em CTC para sinalização e Timer2 para PWM no D3, com um conflito destacado entre Timer1 e PWM no D9.

O mesmo Timer1 não deve ser, ao mesmo tempo, reconfigurado para CTC e esperado como fonte do PWM padrão em D9 ou D10.

Localize a sobrescrita

Registre escritores e instantes

Use uma tabela curta para tornar concorrências visíveis. Inclua tanto chamadas da API quanto escritas diretas em registradores: elas podem controlar o mesmo hardware.

Inventário de exemplo para investigar uma perda de PWM

A coluna “momento” ajuda a separar uma configuração feita uma vez de um comando repetido no loop.
RecursoFunção esperadaQuem escreveMomento
Timer0Base de tempo do núcleoNenhum trecho do projetoPreservado
Timer1CTC para sinalizaçãoconfigurarTimer1CTC()setup
D3 / Timer2PWM do LEDanalogWrite(3, nivel)setup ou loop
D3Não deve ser digitaldigitalWrite(3, LOW)loop suspeito
D9 / Timer1Sem uso neste casoanalogWrite(9, nivel)setup suspeito

Atenção

Compare a configuração logo após setup() com a configuração após a rotina suspeita. Se um valor ou comportamento muda nesse intervalo, você encontrou um escritor a investigar. Trocar apenas a ordem das chamadas não resolve: se os dois trechos continuam executando, o último continua sobrescrevendo o anterior.

Escolha um único responsável

Leia o conflito no código

Este trecho reúne duas disputas. A primeira é de temporizador: após colocar o Timer1 em CTC, o programa espera PWM padrão no D9, que depende do Timer1. A segunda é de pino: o digitalWrite() repetido toma o controle digital do D3, que deveria permanecer sob PWM do Timer2.

Dois escritores concorrentes

Trecho para inspecionar; não o use como versão corrigida.

cpp
void setup() {
  configurarTimer1CTC();  // Timer1: sinalização temporal

  analogWrite(9, 128);    // conflito: D9 usa Timer1
  analogWrite(3, 128);    // PWM pretendido no Timer2
}

void loop() {
  digitalWrite(3, LOW);   // conflito: substitui o comando PWM do D3
}

Dica

A correção mínima é atribuir uma responsabilidade exclusiva: mantenha o Timer1 em CTC, use o PWM no D3 e remova qualquer digitalWrite() ou escrita de registrador que commande D3. Não altere o Timer0 para tentar compensar o problema.

Confirme com uma alteração isolada

Aplique a correção mínima

Complete a chamada que mantém o PWM no D3 sem disputar o Timer1 nem alterar o Timer0.

cpp
void setup() {
  configurarTimer1CTC();  // Timer1: sinalização temporal
  (3, 128);         // D3: PWM pelo Timer2
}

void loop() {
  // Nenhum digitalWrite(3, ...) aqui.
  // Timer0 permanece sem reconfiguração.
}

Passo 7 de 8

Localize a falha nos dados compartilhados

Audite acessos entre ISR e loop, reconstrua intercalações vulneráveis e escolha uma proteção mínima conforme o tipo de dado.

1. Comece pela auditoria

Audite o acesso, não só a declaração

Uma variável é compartilhada quando a ISR e o loop() a leem ou escrevem. Antes de corrigir, faça uma auditoria curta: quem escreve, quem lê, qual é a largura do dado e qual operação precisa ser indivisível.

No Uno R3, uma leitura de 16 bits pode envolver mais de uma operação do processador. Também é composta a sequência “testar uma flag e depois limpá-la”. volatile mantém os acessos visíveis ao compilador, mas não impede uma ISR de ocorrer entre essas partes.

Perguntas da auditoria

Use este inventário para decidir o que precisa de proteção.

Exemplos de variáveis acessadas pela ISR e pelo fluxo principal.
VariávelISRloop()Risco principal
ticks (uint16_t)incrementalêleitura formada por partes
pendente (bool)define trueconsulta e limpaocorrência perdida
atendimentos (uint16_t)incrementacopia e zeraquantidade incompleta sem proteção

Uma leitura pode ser interrompida

Diagrama de uma leitura de dois bytes interrompida por uma atualização da ISR.

A sequência, e não a aparência ocasional do LED, evidencia a leitura incoerente.

2. Demonstre a leitura incoerente

Leitura vulnerável de contador

Trecho para analisar; não é necessário executá-lo agora.

cpp
volatile uint16_t ticks = 0;

ISR(TIMER1_COMPA_vect) {
  ticks++;
}

void usarTicks() {
  uint16_t copia = ticks;  // pode ser interrompida entre bytes
  processar(copia);
}

Reconstrua a corrida

Ordene a intercalação que explica uma cópia impossível de ticks. Considere que o valor inicial é 0x01FF.

  1. O loop retoma e lê o byte alto: 0x02.
  2. O loop inicia a cópia e lê o byte baixo: 0xFF.
  3. A ISR ocorre e incrementa ticks de 0x01FF para 0x0200.
  4. A cópia combinada no loop torna-se 0x02FF.

3. Diferencie flag, instantâneo e contador

Escolha a correção pelo tipo de evidência

Nem toda falha compartilhada é uma leitura de vários bytes:

  • Leitura incoerente: uma cópia de dado largo mistura instantes. Use um instantâneo protegido.
  • Flag perdida: o loop() vê pendente, mas a ISR pode definir a flag novamente antes de o loop() limpá-la. A transferência “copiar e limpar” deve ser indivisível.
  • Flag agregada: várias ocorrências enquanto a flag já é verdadeira continuam parecendo apenas uma. Se a quantidade importa, registre-a em um contador.

A seção crítica deve conter somente a cópia ou transferência. Decisão, cálculos e comandos de saída ficam depois dela.

Flag: copie e limpe como uma única operação

Uma transferência protegida consome a pendência sem apagar uma nova ocorrência que chegue no meio da operação.

cpp
volatile bool pendente = false;

bool retirarPendencia() {
  bool houve;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    houve = pendente;
    pendente = false;
  }
  return houve;
}

Preserve a quantidade

A ISR pode registrar várias comparações antes de o loop() atendê-las. O programa precisa tratar cada atendimento registrado separadamente. Qual representação é mais adequada?

4. Corrija sem ampliar a seção crítica

Aplique uma correção mínima

Complete a proteção da cópia. Ela restaura o estado de interrupções que já existia antes do bloco, em vez de supor que elas estavam habilitadas. A proteção é curta; processar() continua fora dela.

Proteja o instantâneo

Preencha o modo do bloco atômico para obter um instantâneo coerente sem alterar indevidamente o estado anterior das interrupções.

cpp
#include <util/atomic.h>

volatile uint16_t ticks = 0;

void usarTicks() {
  uint16_t copia;

  ATOMIC_BLOCK() {
    copia = ticks;
  }

  processar(copia);
}

Passo 8 de 8

Aplicação final: corrija e verifique o conjunto

Aplique uma correção localizada em uma variante reproduzível e confirme, pela mesma sequência de testes, o comportamento do botão, da sinalização temporal e do PWM.

Observe antes de supor a causa

Caso final: descreva o sintoma antes de editar

Use uma cópia do caso-base, mantendo a versão conhecida sem alterações. Nesta variante, a gravação é confirmada pela IDE e, após reset, D13 alterna visualmente e D3 começa em brilho baixo.

Ao pressionar D2 e manter o botão pressionado, D3 não muda. Ao soltar D2, porém, D3 muda de brilho. D13 continua alternando durante toda a sequência.

Classifique primeiro: como a compilação e a gravação foram confirmadas, a evidência aponta para comportamento em execução. A continuidade de D13 também torna menos provável atribuir o sintoma ao Timer1. Antes de editar, reproduza a mesma sequência e registre exatamente em qual ação D3 muda.

Sequência que separa as funções

Diagrama de botão em D2, LED PWM em D3 e LED integrado em D13, com D13 alternando em toda a sequência e D3 mudando apenas após a soltura do botão.

A ordem das ações é evidência: compare pressionamento, manutenção e soltura sem atribuir a causa antes de examinar o código.

Formule uma hipótese verificável

Registre sua hipótese inicial em uma ou duas frases: qual etapa falhou, qual observação a sustenta e que parte do comportamento merece auditoria? Não nomeie uma correção ainda.

Escreva pelo menos 50 caracteres (0/50).

Reproduza, localize e corrija

Variante reproduzível para auditar

Crie uma cópia do sketch de referência e substitua seu conteúdo por esta variante completa. Ela é intencionalmente defeituosa, mas compila e pode ser gravada no Uno R3. Use a montagem já conhecida: botão entre D2 e GND e LED externo com resistor em D3. Não altere nada antes de executar a sequência da tela anterior e conferir o comportamento.

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

const uint8_t PINO_BOTAO = 2;
const uint8_t PINO_PWM = 3;

volatile uint16_t ticks = 0;

bool leituraAnterior = HIGH;
bool botaoEstavel = HIGH;
unsigned long mudouEm = 0;
const unsigned long DEBOUNCE_MS = 30;

bool brilhoAlto = false;

void configurarTimer1() {
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1 = 0;
  OCR1A = 31249;
  TIFR1 = _BV(OCF1A);
  TIMSK1 = _BV(OCIE1A);
  TCCR1B = _BV(WGM12) | _BV(CS12);
}

ISR(TIMER1_COMPA_vect) {
  PINB = _BV(PB5);
  ticks++;
}

void setup() {
  pinMode(PINO_BOTAO, INPUT_PULLUP);
  pinMode(PINO_PWM, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  digitalWrite(LED_BUILTIN, LOW);
  analogWrite(PINO_PWM, 40);
  configurarTimer1();
}

void loop() {
  bool leitura = digitalRead(PINO_BOTAO);
  unsigned long agora = millis();

  if (leitura != leituraAnterior) {
    leituraAnterior = leitura;
    mudouEm = agora;
  }

  if ((agora - mudouEm >= DEBOUNCE_MS) &&
      (leitura != botaoEstavel)) {
    botaoEstavel = leitura;

    if (botaoEstavel == HIGH) {
      brilhoAlto = !brilhoAlto;
      analogWrite(PINO_PWM, brilhoAlto ? 180 : 40);
    }
  }

  uint16_t copiaTicks;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    copiaTicks = ticks;
  }

  (void)copiaTicks;
}
Saída esperada
Após reset e cerca de 3 segundos:
D13 alterna visualmente.
D3 permanece em brilho baixo.

Durante pressionamento estável de D2:
D13 continua alternando visualmente.
D3 permanece no brilho anterior.

Após soltura estável de D2:
D13 continua alternando visualmente.
D3 muda de brilho.

A saída é uma sequência de observações visuais esperadas para esta variante com falha. O instante exato em que D13 está alto ou baixo depende da fase do Timer1; a inspeção visual não mede sua precisão nem a frequência do PWM.

Aplique uma alteração localizada

Após reproduzir o sintoma, localize a decisão feita logo depois de o novo estado estável ser aceito. Complete a condição mínima que faz a troca de brilho ocorrer no pressionamento, e não na soltura.

cpp
if ((agora - mudouEm >= DEBOUNCE_MS) &&
    (leitura != botaoEstavel)) {
  botaoEstavel = leitura;

  if () {
    brilhoAlto = !brilhoAlto;
    analogWrite(PINO_PWM, brilhoAlto ? 180 : 40);
  }
}

Compare antes e depois das três funções

Faça uma verificação de regressão

Salve a alteração mínima, compile e grave novamente. Só compare a versão corrigida depois da confirmação de gravação da IDE. Repita a sequência idêntica: reset; aguarde alguns intervalos; pressione D2; mantenha pressionado; solte; aguarde novamente.

Preencha a matriz no seu registro local. “D13 alternou visualmente” e “D3 pareceu manter brilho” são observações. “O intervalo é preciso” e “o PWM tem certa frequência” não são conclusões verificadas apenas olhando LEDs.

Matriz de comparação

Registre o antes e o depois com a mesma sequência de ações. Inclua funções que não eram o sintoma principal.

Matriz mínima para a variante final
FunçãoEsperado após correçãoAntesDepois
D2 → PWM em D3Muda no pressionamento estávelNão mudou ao pressionar; mudou ao soltarAnote
D13 pelo Timer1Continua alternando visualmenteAnoteAnote
PWM em D3 após solturaMantém o brilho escolhidoMudou na solturaAnote
Compilação e gravaçãoIDE confirma ambasAnoteAnote

Documente e encerre

Relate evidência, mudança e limite

Produza um relato curto da sua prática local. Inclua: a evidência que classificou a etapa; o comportamento reproduzido antes da alteração; a causa localizada; a única mudança aplicada; e os resultados antes e depois para D2/D3, D13 e a permanência do brilho em D3 após a soltura. Termine com uma conclusão que a inspeção visual ainda não permite afirmar.

Escreva pelo menos 120 caracteres (0/120).

Resumo

Ciclo de diagnóstico concluído

  • Classifique a etapa da falha antes de mudar código ou montagem.
  • Preserve uma versão conhecida e reproduza uma sequência de ações definida.
  • Use observações para formular uma hipótese; localize a causa antes de corrigi-la.
  • Altere somente o trecho sustentado pela evidência e confirme nova compilação e gravação.
  • Repita os testes de botão, sinalização temporal e PWM para procurar regressões.
  • Relate o que foi observado e separe isso de precisão ou frequência ainda não medidas.

Tutorial concluído

Parabéns! Você concluiu: Diagnóstico de firmware temporizado

Diagnóstico concluído! Use o mesmo ciclo para investigar efeitos inesperados em firmware temporizado.

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