Trilha de aprendizado · Nível 11 · Tutorial 7

Leitura de temperatura por I2C com o MCP9808

Ao concluir, o aluno será capaz de ler a temperatura ambiente de um módulo MCP9808 próprio por I2C, conferindo conexões, endereço, formato do registrador e resultados das transações.

  • Nível: Intermediário
  • Duração: 25 min
  • 9 passos
Leitura de temperatura por I2C com o MCP9808

O que você vai percorrer

  1. Conferir o módulo e montar o barramento Prepare uma ligação I2C segura entre um Uno R3 e um módulo MCP9808 compatível com 5 V, identificando corretamente alimentação, GND, SDA, SCL e os pull-ups já existentes. 4 min
  2. Definir o endereço do MCP9808 Determine o endereço I2C de sete bits do MCP9808 a partir dos pinos A2, A1 e A0 e use 0x18 na configuração desta prática. 2 min
  3. Entender a sequência de uma transação I2C Reconheça os eventos de uma transação I2C e diferencie confirmações esperadas de ausência de confirmação. 3 min
  4. Preparar a Wire com limite de espera Configure a Wire do Uno R3 para 100 kHz e habilite um timeout interno de 25 ms antes de iniciar a leitura do MCP9808. 3 min
  5. Selecionar e solicitar o registrador de temperatura Use as chamadas da Wire para selecionar o MCP9808, apontar o registrador 0x05 e solicitar seus dois bytes em uma única transação de leitura. 3 min
  6. Validar a transação antes de usar os bytes Valide a escrita, o timeout e a presença dos dois bytes antes de permitir qualquer interpretação do registrador. 3 min
  7. Converter o registrador em graus Celsius Transforme os dois bytes já validados do registrador 0x05 em uma temperatura em graus Celsius, isolando alertas, sinal e campo numérico. 3 min
  8. Executar a leitura no equipamento próprio Execute o sketch completo no Uno R3, observe uma leitura válida e teste a rejeição segura de um endereço I2C sem resposta. 3 min
  9. Aplicação final: aceitar ou rejeitar a leitura Revise os critérios que permitem aceitar uma leitura do MCP9808 e aplique-os a casos de sucesso e falha. 2 min

O que você vai aprender

  • Conferir alimentação, pull-ups e conexões SDA e SCL de um módulo compatível.
  • Relacionar o endereço do dispositivo às configurações de seus pinos.
  • Executar uma leitura de registrador e converter os bytes recebidos em temperatura.
  • Distinguir recebimento completo, NACK e timeout.
  • Evitar interpretar bytes ausentes ou uma transação interrompida como temperatura válida.

Antes de começar

  • Entradas definidas com pull-up e pull-down
  • Compatibilidade elétrica entre circuitos digitais
  • Máscaras e operações de bits em C/C++
  • Comunicação UART pelo monitor serial
  • Mensagens UART com delimitação e timeout

Passo 1 de 9

Conferir o módulo e montar o barramento

Prepare uma ligação I2C segura entre um Uno R3 e um módulo MCP9808 compatível com 5 V, identificando corretamente alimentação, GND, SDA, SCL e os pull-ups já existentes.

Identifique as funções do módulo

Antes de conectar

Você fará a prática no seu computador e com seus próprios equipamentos: um Arduino Uno R3, um módulo MCP9808 e cabos. Antes de montar, consulte a documentação do seu módulo e confirme que ele é explicitamente compatível com alimentação e sinais de 5 V.

Módulos podem ter a mesma função, mas pinos em posições, nomes e circuitos auxiliares diferentes. Por isso, siga a função indicada no módulo e na documentação — não uma posição presumida na placa.

Funções, não posição física

Diagrama funcional mostrando Uno R3 ligado a um módulo MCP9808 por alimentação, terra comum, SDA em A4 e SCL em A5; uma segunda placa esquemática reforça que a disposição física dos pinos pode variar.

Use a documentação para localizar cada terminal funcional no seu módulo.

Ligue cada função ao ponto correto

Plano de conexões

No Uno R3, o barramento desta prática usa os pinos analógicos compartilhados:

  • SDA → A4
  • SCL → A5
  • GND do módulo → GND do Uno
  • Alimentação do módulo → terminal de alimentação indicado pela documentação, compatível com 5 V

O GND comum é indispensável: ele fornece a referência de tensão para os sinais SDA e SCL. Faça a montagem com o circuito desligado.

Tabela de ligação

Use esta tabela como roteiro funcional. Ela não substitui a identificação da pinagem específica do seu módulo.

Conexões funcionais para um Uno R3.
Função no MCP9808Conectar no Uno R3Conferência
Alimentação5 V, se a documentação permitirTerminal de alimentação correto
GNDGNDReferência comum
SDAA4Linha de dados
SCLA5Linha de clock

Atenção

Segurança na montagem

Desligue a alimentação antes de inserir, remover ou trocar cabos. Meça resistência somente com o circuito desenergizado. Não adicione ligações pela posição visual de um pino: confirme seu nome na serigrafia e na documentação.

Entenda as linhas compartilhadas

Por que há pull-ups?

SDA e SCL são linhas de dreno aberto. Em vez de alguém forçar nível alto, cada participante do barramento faz apenas uma destas ações:

  • puxa a linha para nível baixo;
  • libera a linha.

Quando todos a liberam, o resistor de pull-up leva a linha ao nível alto. Assim, vários dispositivos podem compartilhar a mesma linha sem um dispositivo dirigir alto enquanto outro dirige baixo.

Linha liberada ou puxada para baixo

Esquema de linhas SDA e SCL compartilhadas, com resistores de pull-up para 5 V e chaves de dreno aberto dos dispositivos puxando a linha ao GND.

O nível alto vem do pull-up; os participantes somente puxam para baixo ou liberam a linha.

Dica

Cheque antes de acrescentar

Muitos módulos já incluem pull-ups. Antes de acrescentar resistores, confira na documentação:

  1. se há pull-ups no módulo ou em outro ponto do barramento;
  2. a qual tensão eles estão conectados;
  3. o valor de cada resistor;
  4. a resistência equivalente em cada linha, caso existam vários em paralelo.

Não acrescente pull-ups automaticamente: resistores em paralelo reduzem a resistência equivalente e podem aumentar a corrente quando a linha está em nível baixo.

Verifique seu plano

Decisão de montagem

Você encontrou na documentação do módulo dois resistores de pull-up já conectados de SDA e SCL para a alimentação compatível. Qual é a melhor decisão antes de instalar outros pull-ups externos?

Escreva pelo menos 40 caracteres (0/40).

Passo 2 de 9

Definir o endereço do MCP9808

Determine o endereço I2C de sete bits do MCP9808 a partir dos pinos A2, A1 e A0 e use 0x18 na configuração desta prática.

Endereço de sete bits do sensor

Quem o Uno seleciona?

No barramento I2C, o Uno é o controlador: ele inicia a comunicação e escolhe com qual dispositivo quer falar. Essa escolha usa um endereço de 7 bits.

O MCP9808 usa uma base fixa, 0x18. Os pinos de endereço do próprio sensor — A2, A1 e A0 — acrescentam um valor de 0 a 7 a essa base. Assim, vários MCP9808 podem compartilhar SDA e SCL quando recebem configurações diferentes.

Atenção: A0, A1 e A2 aqui são pinos de endereço do MCP9808. Eles não são as entradas analógicas A0, A1 e A2 do Uno.

Endereço definido pelos pinos do sensor

Diagrama do Uno ligado a um módulo MCP9808; os pinos de endereço A2, A1 e A0 do sensor estão em nível baixo, resultando no endereço 0x18.

Na configuração desta prática, os três pinos de endereço do MCP9808 estão em nível baixo.

Faixa de endereços possíveis

Como os três pinos alteram o endereço

Cada pino A contribui com um bit para o final do endereço. A base 0x18 corresponde ao padrão com A2, A1 e A0 baixos:

0x18 + valor binário A2A1A0

Por exemplo, A2=0, A1=1 e A0=1 formam 011₂, que vale 3. Logo, o endereço é 0x18 + 3 = 0x1B. Consulte a documentação do seu módulo para saber como seus pinos ou resistores definem esses níveis.

Tabela de endereços

Endereços possíveis para o MCP9808

A2 é o bit mais significativo e A0 é o menos significativo da seleção.
A2 A1 A0ValorEndereço de 7 bits
00000x18
00110x19
01020x1A
01130x1B
10040x1C
10150x1D
11060x1E
11170x1F

Prática: identificar 0x18

Endereço da prática

Para a montagem deste tutorial, A2, A1 e A0 estão em nível baixo. Complete o valor hexadecimal de sete bits que será passado à Wire.

Verifique o endereço

Wire usará o endereço {{blank}} para selecionar o MCP9808 desta prática.

Sete bits, sem bit extra

O valor que entra na Wire

Não converta 0x18 para um “endereço de leitura” ou “endereço de escrita” ao chamar a Wire. O bit que diferencia leitura e escrita faz parte da transmissão no barramento, mas não deve ser acrescentado manualmente ao argumento da biblioteca.

Portanto, quando chegar o momento de iniciar uma comunicação, o valor será 0x18 — não 0x30 nem 0x31.

Dica

Não desloque o endereço

O endereço de 7 bits é 0x18. Se você o deslocasse uma posição para formar o byte transmitido, obteria 0x30 para escrita ou 0x31 para leitura. A Wire recebe somente 0x18 e cuida dessa parte do protocolo.

Passo 3 de 9

Entender a sequência de uma transação I2C

Reconheça os eventos de uma transação I2C e diferencie confirmações esperadas de ausência de confirmação.

Delimitar a conversa no barramento

START, dados e STOP

Uma transação I2C é delimitada por condições especiais nas duas linhas do barramento:

  • START: SDA passa de nível alto para baixo enquanto SCL está alto.
  • STOP: SDA passa de nível baixo para alto enquanto SCL está alto.

Entre esses limites, os bits de dados normalmente mudam enquanto SCL está baixo. O receptor observa o valor de SDA enquanto SCL está alto. Essa regra permite distinguir dados das condições que abrem e encerram uma conversa no barramento.

Forma geral de uma transação

Diagrama temporal mostrando START, bits de dados e STOP nas linhas SDA e SCL.

As transições de SDA com SCL alto delimitam START e STOP; os dados ficam estáveis na parte alta dos pulsos de SCL.

Confirmar bytes e reconhecer espera

O nono pulso: ACK ou NACK

Cada trecho transmitido tem oito bits e mais um nono pulso de clock destinado à confirmação.

Após os oito bits, quem recebeu o byte pode puxar SDA para baixo no nono pulso: isso é ACK. Se SDA permanece em alto, há um NACK.

O sentido importa: quem transmite os oito bits não é necessariamente quem confirma. Por exemplo, ao controlador enviar endereço e dados, o sensor confirma. Ao sensor enviar um byte de leitura, o controlador confirma ou encerra com NACK.

Confirmação após cada byte

Comparação de um byte I2C seguido por ACK com SDA baixo e outro seguido por NACK com SDA alto no nono pulso.

ACK e NACK são estados do nono pulso. O receptor é quem produz essa resposta.

Dica

SCL pode permanecer baixo

O I2C também prevê que um dispositivo mantenha SCL em nível baixo por mais tempo antes de liberar o próximo pulso. Esse comportamento é chamado de prolongamento do clock.

Ele é uma possibilidade do protocolo; não conclua apenas por isso que o MCP9808 o usará nesta prática. Uma linha mantida baixa também pode indicar outro problema físico no barramento.

Ler dois bytes sem STOP intermediário

START repetido e fim da leitura

Uma leitura de registrador costuma ter duas fases contínuas. Depois de selecionar um registrador para escrita, o controlador gera um START repetido: uma nova condição START sem STOP intermediário. Assim, a comunicação muda para a fase de leitura sem liberar o barramento entre as fases.

Em uma leitura de dois bytes, o controlador confirma o primeiro byte recebido com ACK. No segundo e último byte, ele responde com NACK e então gera STOP. Esse NACK final informa ao dispositivo que não deve enviar outro byte.

Leitura de dois bytes

Fluxo de uma leitura I2C com fase de escrita, START repetido, dois bytes recebidos, ACK no primeiro e NACK no último.

O NACK do controlador depois do último byte é parte normal do encerramento de uma leitura.

Atenção

Um NACK pode ser normal ou indicar falha

Nem todo NACK tem o mesmo significado:

  • NACK final do controlador: esperado ao encerrar a leitura do último byte.
  • NACK do sensor ao receber endereço ou dado: ausência de confirmação naquela fase; não é uma leitura concluída normalmente.

Portanto, interprete o NACK observando quem o enviou e em que ponto da transação ele ocorreu.

Praticar a leitura completa

Monte a sequência de leitura

Organize a sequência de uma leitura de dois bytes com START repetido.

  1. Endereço do registrador e ACK do sensor
  2. Endereço do sensor com escrita e ACK do sensor
  3. Primeiro byte recebido e ACK do controlador
  4. Segundo byte recebido e NACK do controlador
  5. STOP
  6. START
  7. START repetido
  8. Endereço do sensor com leitura e ACK do sensor

Interprete o NACK

Qual NACK faz parte de uma leitura I2C normal de dois bytes?

Passo 4 de 9

Preparar a Wire com limite de espera

Configure a Wire do Uno R3 para 100 kHz e habilite um timeout interno de 25 ms antes de iniciar a leitura do MCP9808.

Escolha a Wire e confira o suporte

Biblioteca e compatibilidade

Preparar a Wire com limite de espera

Nesta prática, use a biblioteca Wire fornecida pelo pacote Arduino AVR Boards: não é necessária uma biblioteca específica para o MCP9808. O Uno será configurado para I2C a 100 kHz.

Antes de escrever a leitura do sensor, confirme no seu ambiente local que a versão instalada do pacote Arduino AVR Boards documenta estas funções:

  • Wire.setWireTimeout(...)
  • Wire.getWireTimeoutFlag()
  • Wire.clearWireTimeoutFlag()

Consulte a referência da versão instalada na IDE ou os arquivos locais do pacote. Se essa API não estiver disponível, atualize o pacote antes de continuar. Não remova a proteção de timeout como substituto.

Diagrama do Uno configurando o barramento I2C com clock de 100 kHz e uma proteção de timeout antes de comunicar-se com o sensor.

A Wire configura o barramento; a proteção de timeout limita esperas internas da biblioteca.

Defina clock e limite

Inicialização adotada

Coloque esta configuração no início de setup(), antes das transações que serão criadas nos próximos passos.

Configuração da Wire para esta prática

cpp
#include <Wire.h>

void setup() {
  Wire.begin();
  Wire.setClock(100000L);
  Wire.setWireTimeout(25000, true);
}

void loop() {
}

Dica

Leia os argumentos

100000L representa 100.000 Hz, isto é, 100 kHz. Em Wire.setWireTimeout(25000, true), o primeiro argumento está em microssegundos: 25.000 µs equivalem a 25 ms.

O segundo argumento true permite que a Wire reinicialize o periférico TWI se detectar o timeout. Isso pode recuperar o periférico, mas não garante liberar uma linha SDA ou SCL que permaneça fisicamente presa em nível baixo.

Entenda o alcance da proteção

O que esse timeout protege

O timeout da Wire atua nas esperas internas das chamadas da própria biblioteca. Ele é diferente de medir um tempo fora da chamada.

Se uma chamada bloqueante estiver esperando uma condição no barramento, o código após essa chamada ainda não começou a executar. Por isso, um teste com millis() colocado apenas antes e depois dela não consegue interrompê-la.

A flag de timeout também é persistente: depois de registrada, continua ativa até você limpá-la explicitamente. Nos próximos passos, ela será limpa no começo de cada tentativa e consultada ao longo da transação.

Atenção

Limite importante

Não interprete a reinicialização do TWI como conserto automático da montagem. Com alimentação desligada, confira conexões e pull-ups se o barramento continuar sem responder.

Verifique a configuração

Prática: complete a configuração

Complete apenas as chamadas de configuração. Considere que Wire.h já foi incluída e que o código é destinado ao Uno R3 com uma versão AVR que oferece a API de timeout.

Preencha os valores usados nesta prática.

cpp
void setup() {
  Wire.begin();
  Wire.setClock();
  Wire.setWireTimeout(, true);
}

Passo 5 de 9

Selecionar e solicitar o registrador de temperatura

Use as chamadas da Wire para selecionar o MCP9808, apontar o registrador 0x05 e solicitar seus dois bytes em uma única transação de leitura.

Selecionar o sensor e o registrador

Dois endereços, duas funções

Há dois endereços diferentes nesta leitura:

  • 0x18 é o endereço I2C de sete bits do MCP9808 configurado na montagem. Ele seleciona o dispositivo no barramento.
  • 0x05 é o endereço interno do registrador de temperatura. Ele informa qual dado queremos ler no dispositivo já selecionado.

Portanto, não use 0x05 em Wire.beginTransmission(): essa chamada espera o endereço do dispositivo, isto é, 0x18.

O percurso da leitura

Diagrama de uma leitura I2C: o Uno seleciona o MCP9808 em 0x18, envia o registrador 0x05, emite START repetido e recebe dois bytes.

O endereço 0x18 seleciona o sensor; 0x05 seleciona seu registrador de temperatura.

Apontar o registrador

Fase de escrita sem STOP

A leitura é feita em duas fases contínuas.

1. Apontar o registrador. Wire.beginTransmission(0x18) prepara uma transmissão ao sensor. Wire.write(0x05) coloca no buffer o registrador desejado.

2. Manter o barramento. Wire.endTransmission(false) transmite essa fase, mas o argumento false evita o STOP. Assim, a próxima chamada pode continuar com um START repetido.

O valor retornado por endTransmission() será validado no próximo step. Por enquanto, concentre-se na sequência e no papel de cada chamada.

Enviar o ponteiro 0x05

Fase 1: selecionar o registrador de temperatura

cpp
const uint8_t MCP9808 = 0x18;
const uint8_t REG_TEMPERATURA = 0x05;

Wire.beginTransmission(MCP9808);
Wire.write(REG_TEMPERATURA);
uint8_t status = Wire.endTransmission(false);

Dica

Por que false?

Use false, e não true, nesta chamada. O true padrão enviaria STOP após 0x05, quebrando a sequência que queremos manter até a fase de leitura.

Solicitar e encerrar a leitura

Solicitar os dois bytes

Depois da fase de escrita sem STOP, solicite dois bytes ao mesmo sensor:

uint8_t recebidos = Wire.requestFrom(MCP9808, (uint8_t)2);

A biblioteca inicia a fase de leitura e emite STOP quando ela termina. Durante o recebimento, ela também cuida do protocolo: confirma o primeiro byte com ACK, envia NACK após o segundo e último byte e encerra a transação.

Não controle SDA ou SCL manualmente para produzir esses eventos.

Ordene a transação

Organize as fases da leitura do registrador 0x05. Pense na ordem que mantém a transação contínua entre o envio do registrador e a solicitação de dados.

Sequência de leitura

Coloque as etapas na ordem correta.

  1. Wire.write(0x05)
  2. Wire.endTransmission(false)
  3. Wire.beginTransmission(0x18)
  4. Wire.requestFrom(0x18, 2)

Fixar a sequência

Prática: completar as chamadas

Complete a sequência que aponta o registrador de temperatura e solicita seus dois bytes. As lacunas representam valores e argumentos essenciais, não nomes de variáveis arbitrários.

Código da transação

Preencha a sequência de leitura do registrador de temperatura.

cpp
Wire.beginTransmission();
Wire.write();
Wire.endTransmission();
Wire.requestFrom((uint8_t)0x18, (uint8_t));

Passo 6 de 9

Validar a transação antes de usar os bytes

Valide a escrita, o timeout e a presença dos dois bytes antes de permitir qualquer interpretação do registrador.

Defina uma barreira antes da conversão

A validação vem antes dos bytes

Uma leitura só pode chegar à etapa de interpretação quando todas as verificações de transporte forem aprovadas. Comece cada tentativa limpando a flag persistente de timeout. Depois, confira essa flag após cada operação da Wire que possa aguardar o barramento.

A fase que seleciona o registrador termina em Wire.endTransmission(false). Se ela falhar, encerre a tentativa imediatamente: não solicite os dois bytes e não produza uma temperatura.

Mesmo que o retorno da escrita seja sucesso, um timeout registrado torna a tentativa inválida.

Portões de aceitação da leitura

Fluxo visual no qual escrita bem-sucedida, ausência de timeout e dois bytes recebidos e disponíveis levam à aceitação; NACK, timeout e recebimento incompleto levam à rejeição.

A conversão só pode ser chamada depois dos quatro portões verdes: escrita válida, sem timeout, dois bytes retornados e dois bytes disponíveis.

Pare quando a seleção falhar

Interprete o resultado da escrita

No núcleo Arduino AVR adotado, o retorno de Wire.endTransmission(false) informa o resultado da fase de escrita. O valor 0 é o único sucesso. Os demais valores devem ser tratados como falha e comunicados como tal, sem inventar um valor de temperatura.

Consulte a documentação da versão local do pacote Arduino AVR Boards para confirmar os códigos disponíveis.

Códigos da fase de escrita

Códigos usuais retornados por Wire.endTransmission(false) no Arduino AVR.

Qualquer código diferente de 0 interrompe a tentativa.
RetornoSignificadoAção nesta tentativa
0Escrita concluídaProsseguir para a leitura
1Buffer de transmissão excedidoRejeitar
2NACK no endereçoRejeitar
3NACK no dado enviadoRejeitar
4Outro erroRejeitar
5TimeoutRejeitar

Exija dois bytes reais

Função de leitura com validação

Esta função valida o transporte e só entrega dois bytes quando a tentativa está completa. Ela ainda não converte esses bytes em graus Celsius.

cpp
bool lerBytesTemperatura(uint8_t &msb, uint8_t &lsb) {
  Wire.clearWireTimeoutFlag();

  Wire.beginTransmission(0x18);
  Wire.write(0x05);
  uint8_t status = Wire.endTransmission(false);

  if (Wire.getWireTimeoutFlag()) {
    Serial.println("Falha: timeout na escrita");
    return false;
  }
  if (status != 0) {
    Serial.print("Falha na escrita, codigo: ");
    Serial.println(status);
    return false;
  }

  uint8_t recebidos = Wire.requestFrom(0x18, 2);
  if (Wire.getWireTimeoutFlag()) {
    Serial.println("Falha: timeout na leitura");
    return false;
  }
  if (recebidos != 2 || Wire.available() != 2) {
    Serial.println("Falha: recebimento incompleto");
    return false;
  }

  int primeiro = Wire.read();
  int segundo = Wire.read();
  if (primeiro < 0 || segundo < 0) {
    Serial.println("Falha: byte ausente");
    return false;
  }

  msb = (uint8_t)primeiro;
  lsb = (uint8_t)segundo;
  return true;
}

Dica

Leia somente após validar

Wire.requestFrom() informa quantos bytes foram efetivamente recebidos. Antes de cada Wire.read(), confirme também a disponibilidade com Wire.available().

Wire.read() retorna um int: quando não há dado, ele pode retornar valor negativo. Fazer uma conversão direta para uint8_t poderia transformar essa ausência em um byte aparentemente válido. Por isso, leia primeiro em int, teste o resultado e só então converta.

Se a função retornar false, mostre a falha e não chame a conversão. Zero será uma temperatura válida mais adiante; não deve ser usado como substituto de erro.

Pratique a condição de aceitação

Bloqueie os caminhos de falha

Complete os critérios numéricos que aceitam a escrita e exigem o recebimento completo antes de ler os bytes.

cpp
uint8_t status = Wire.endTransmission(false);
if (status !=  || Wire.getWireTimeoutFlag()) {
  return false;
}

uint8_t recebidos = Wire.requestFrom(0x18, 2);
if (Wire.getWireTimeoutFlag() ||
    recebidos !=  || Wire.available() != ) {
  return false;
}

Passo 7 de 9

Converter o registrador em graus Celsius

Transforme os dois bytes já validados do registrador 0x05 em uma temperatura em graus Celsius, isolando alertas, sinal e campo numérico.

Ler o formato antes de converter

Dois bytes, uma palavra

A conversão só começa depois de a tentativa ter sido validada: escrita concluída, sem timeout e dois bytes disponíveis. O registrador de temperatura é o 0x05.

O MCP9808 envia primeiro o byte mais significativo (MSB) e depois o menos significativo (LSB). Portanto, se a leitura fornecer msb e lsb, a palavra de 16 bits é formada como MSB:LSB, e não na ordem inversa.

Os bits 15 a 13 trazem indicadores de alerta. Eles fazem parte da palavra recebida, mas não fazem parte do valor numérico da temperatura.

Formato do registrador de temperatura

Diagrama de uma palavra de 16 bits dividida em MSB e LSB, com os três bits superiores de alerta, o bit de sinal e os 12 bits do campo numérico.

Os alertas ocupam os bits 15 a 13; o bit 12 informa o sinal; os bits 11 a 0 formam o campo numérico.

Aplicar máscaras, escala e sinal

Montagem e regra de conversão

Monte a palavra com uint16_t. A conversão explícita antes do deslocamento faz o deslocamento ocorrer em um tipo sem sinal de 16 bits, em vez de depender do comportamento de int com sinal no AVR.

Depois, use máscaras separadas: 0x0FFF conserva apenas o campo numérico, e 0x1000 testa o bit de sinal. A divisão por 16.0 preserva a fração: cada unidade do campo vale 1/16 °C.

Converter MSB e LSB em Celsius

Trecho para usar somente após validar os dois bytes recebidos.

cpp
uint16_t raw = (static_cast<uint16_t>(msb) << 8) | lsb;

uint16_t campo = raw & 0x0FFF;
float temperaturaC = campo / 16.0;

if (raw & 0x1000) {
  temperaturaC -= 256.0;
}

Dica

Sinal e alertas

A fórmula é aplicada ao campo mascarado, não à palavra inteira:

  • bit 12 desligado: temperatura = campo / 16.0
  • bit 12 ligado: temperatura = campo / 16.0 - 256.0

Os bits de alerta não alteram o cálculo. Eles ficam fora porque a máscara 0x0FFF remove os bits 15 a 12 antes da divisão.

Conferir valores positivos, negativos e zero

Exemplos calculados

Confira alguns valores completos. Em todos os casos, o campo numérico é dividido por 16. Os alertas ativos no último caso não entram no resultado.

Conversões do registrador 0x05 do MCP9808.
Palavra recebidaCampo 0x0FFFCálculoTemperatura
0x01980x0198408 / 1625,5 °C
0x1FF80x0FF84088 / 16 − 256−0,5 °C
0x00000x00000 / 160,0 °C
0xE1900x0190400 / 1625,0 °C

Exemplo

Como interpretar o último exemplo

Em 0xE190, os três bits de alerta estão ativos (111 nos bits 15 a 13). Ainda assim, o valor é 25,0 °C: a máscara mantém somente 0x0190. Zero também é uma temperatura válida quando a transação foi completa; não é, por si só, sinal de falha.

Validar a lógica no código

Pratique a conversão

Complete as lacunas para montar a palavra e converter uma leitura negativa. Os alertas devem permanecer fora do valor calculado.

Complete o trecho

Preencha os elementos da conversão do registrador MCP9808.

cpp
uint16_t raw = ( << 8) | lsb;
uint16_t campo = raw & ;
float temperaturaC = campo / ;

if (raw & ) {
  temperaturaC -= ;
}

Passo 8 de 9

Executar a leitura no equipamento próprio

Execute o sketch completo no Uno R3, observe uma leitura válida e teste a rejeição segura de um endereço I2C sem resposta.

Preparar a execução

Resultado esperado

Com a montagem já conferida e o MCP9808 configurado em 0x18, a execução terá dois estados:

  • Leitura válida: o Monitor Serial mostra uma temperatura em °C.
  • Falha: o programa informa a etapa que falhou e não imprime uma temperatura.

Nas próximas telas, copie os dois blocos para um único sketch, na ordem apresentada. Compile, grave no Uno e abra o Monitor Serial em 9600 baud. A primeira tentativa acontece cerca de um segundo após a placa iniciar; as próximas ocorrem a cada segundo.

A temperatura ambiente não tem um valor predeterminado e pode oscilar um pouco. Não aqueça nem resfrie o sensor para este teste.

Temperatura ou falha

Ilustração de um Uno conectado a um MCP9808 e de dois resultados possíveis: uma medição de temperatura válida ou um aviso de comunicação.

O programa só mostra uma temperatura depois de concluir e validar toda a transação.

Montar o sketch: leitura

Leitura validada do MCP9808

Bloco 1 de 2 — coloque este trecho no início do sketch. Ele reúne a leitura validada e a conversão já estudadas.

cpp
#include <Wire.h>

constexpr uint8_t SENSOR_ADDR = 0x18;
constexpr uint8_t TEMP_REG = 0x05;
constexpr unsigned long INTERVALO_MS = 1000;

enum ReadStatus {
  READ_OK,
  WRITE_ERROR,
  TIMEOUT_ERROR,
  INCOMPLETE_DATA
};

ReadStatus readTemperature(float &celsius) {
  Wire.clearWireTimeoutFlag();

  Wire.beginTransmission(SENSOR_ADDR);
  Wire.write(TEMP_REG);
  uint8_t writeStatus = Wire.endTransmission(false);

  if (Wire.getWireTimeoutFlag()) {
    return TIMEOUT_ERROR;
  }
  if (writeStatus != 0) {
    return WRITE_ERROR;
  }

  uint8_t received = Wire.requestFrom(SENSOR_ADDR, (uint8_t)2);
  if (Wire.getWireTimeoutFlag()) {
    return TIMEOUT_ERROR;
  }
  if (received != 2 || Wire.available() != 2) {
    return INCOMPLETE_DATA;
  }

  int msb = Wire.read();
  int lsb = Wire.read();
  if (msb < 0 || lsb < 0) {
    return INCOMPLETE_DATA;
  }

  uint16_t raw = ((uint16_t)msb << 8) | (uint8_t)lsb;
  celsius = (raw & 0x0FFF) / 16.0;
  if (raw & 0x1000) {
    celsius -= 256.0;
  }
  return READ_OK;
}

Gravar e observar

Inicialização e saída serial

Bloco 2 de 2 — acrescente este trecho abaixo do bloco anterior. Juntos, os dois blocos formam o sketch completo para compilar e gravar.

cpp
unsigned long lastAttempt = 0;

void printResult(ReadStatus status, float celsius) {
  if (status == READ_OK) {
    Serial.print("Temperatura: ");
    Serial.print(celsius, 4);
    Serial.println(" C");
  } else if (status == WRITE_ERROR) {
    Serial.println("Falha I2C: escrita ou endereco sem resposta");
  } else if (status == TIMEOUT_ERROR) {
    Serial.println("Falha I2C: timeout");
  } else {
    Serial.println("Falha I2C: recebimento incompleto");
  }
}

void setup() {
  Serial.begin(9600);
  Wire.begin();
  Wire.setClock(100000L);
  Wire.setWireTimeout(25000, true);
}

void loop() {
  unsigned long now = millis();
  if (now - lastAttempt >= INTERVALO_MS) {
    lastAttempt = now;

    float celsius = 0.0;
    ReadStatus status = readTemperature(celsius);
    printResult(status, celsius);
  }
}
Saída esperada
Temperatura: 23.5000 C
Temperatura: 23.5000 C
Falha I2C: escrita ou endereco sem resposta
Falha I2C: escrita ou endereco sem resposta
Temperatura: 23.5000 C

Com `SENSOR_ADDR = 0x18`, as linhas de temperatura são exemplos: o valor real varia com o ambiente. Após trocar apenas a constante para `0x19` e gravar novamente, espere as linhas de falha. Depois de restaurar `0x18`, uma nova linha de temperatura deve voltar a aparecer.

Verificar a rejeição da falha

Dica

Leia o estado antes do valor

Ao usar 0x18, registre um valor aproximado visto no Monitor Serial. O número pode mudar levemente entre uma tentativa e outra.

Embora celsius comece como 0.0, ele só é impresso quando status == READ_OK. Assim, uma falha não se torna falsamente 0.0000 C e não reutiliza uma temperatura anterior como se fosse uma nova leitura.

Testar um endereço sem resposta

Agora teste uma ausência de resposta sem alterar a fiação:

  1. Troque somente SENSOR_ADDR = 0x18 por SENSOR_ADDR = 0x19.
  2. Compile e grave novamente, mantendo apenas o sensor configurado em 0x18 no barramento.
  3. Observe a mensagem Falha I2C: escrita ou endereco sem resposta. Não deve aparecer uma temperatura nessa tentativa.
  4. Restaure 0x18, grave o sketch e confirme que a temperatura volta a aparecer.

A alteração seleciona um endereço diferente e produz um teste seguro de NACK de endereço.

Atenção

Segurança no teste

Não altere conexões com o circuito energizado. Faça este teste exclusivamente pela mudança do endereço no código. Não provoque curto-circuito nem tente prender SDA ou SCL em nível baixo.

Endereço do teste de NACK

Complete a constante usada temporariamente para testar o NACK de endereço.

cpp
constexpr uint8_t SENSOR_ADDR = ;

Registrar o teste

Relate o que você observou: informe um valor aproximado obtido com 0x18, a mensagem ou estado visto com 0x19 e o que aconteceu depois de restaurar 0x18.

Escreva pelo menos 50 caracteres (0/50).

Passo 9 de 9

Aplicação final: aceitar ou rejeitar a leitura

Revise os critérios que permitem aceitar uma leitura do MCP9808 e aplique-os a casos de sucesso e falha.

O critério que protege a leitura

Regra de aceitação

Antes de converter, siga sempre esta ordem: montagem compatível → endereço do sensor → registrador 0x05 → transação → validação → conversão.

Uma tentativa só pode virar temperatura quando a escrita do registrador teve sucesso, não houve timeout e chegaram exatamente dois bytes disponíveis. Um valor de 0,00 °C pode ser válido: zero não é sinal automático de falha.

Fluxo para decidir

A conversão vem somente depois de validar a comunicação.
EtapaEvidência para prosseguirSe falhar
SelecionarEndereço do MCP9808 configuradoNão iniciar leitura como válida
Apontar registradorendTransmission() retorna 0Classificar como falha de escrita/NACK
Solicitar bytesSem flag de timeoutClassificar como timeout
Receber2 bytes retornados e disponíveisClassificar como recebimento incompleto
ConverterDois bytes brutos válidosCalcular temperatura em °C

Validar antes de converter

Diagrama do caminho de leitura do MCP9808, com uma barreira de validação antes da conversão de bytes em temperatura.

A barreira só libera bytes completos, obtidos sem falha de escrita nem timeout.

Aplicação: quatro resultados possíveis

Classifique as tentativas

Associe cada evidência ao resultado correto. Considere um MCP9808 configurado no endereço 0x18 e a seleção do registrador 0x05.

Os bytes só aparecem nos casos em que houve recebimento.
CasoendTransmission()Timeout?Bytes recebidosBytes brutos
A0não20x00, 0x00
B2não0—
C0sim0—
D0não10x01

Decisão final

Faça a associação entre o caso e sua conclusão.

Associe cada caso à classificação correta.

Toque em um item e depois no par correspondente.

Fechamento da leitura confiável

Justifique uma aceitação

Considere esta tentativa: o sensor está configurado em 0x18; endTransmission(false) retorna 0; a flag de timeout está limpa; requestFrom() retorna 2 e há 2 bytes disponíveis: 0x01 e 0x90.

Ela pode ser aceita? Se puder, calcule a temperatura e explique brevemente por que os bits de alerta não alteram o valor.

Escreva pelo menos 80 caracteres (0/80).

Critérios mínimos para aceitar

Use esta lista antes de apresentar qualquer número como leitura atual.
CritérioDeve ser verdadeiro
MontagemMódulo compatível, alimentação, GND e pull-ups conferidos
EndereçoO endereço usado corresponde a A2, A1 e A0
EscritaA seleção de 0x05 terminou com retorno 0
EsperaNão há flag de timeout
RecebimentoExatamente 2 bytes retornados e disponíveis
ConversãoBytes montados e interpretados conforme o formato do MCP9808

Síntese

O resultado da transação determina se há algo a converter.
Se ocorrerFaça
NACKInforme a falha; não gere temperatura
TimeoutInforme timeout; não use bytes
Menos de 2 bytesInforme recebimento incompleto
Todos os critérios atendidosConverta e apresente a temperatura, inclusive se for 0,00 °C

Próximo passo

Resumo

Você concluiu

  • A leitura do MCP9808 começa pela montagem e pelo endereço correto, mas só termina com dois bytes validados.
  • Retorno de escrita diferente de zero, timeout e recebimento incompleto impedem a conversão.
  • Uma temperatura de 0,00 °C é válida quando a transação atende a todos os critérios.
  • Nunca apresente bytes ausentes, leitura anterior ou valor substituto como temperatura atual.

Tutorial concluído

Parabéns! Você concluiu: Leitura de temperatura por I2C com o MCP9808

Parabéns! Você consegue ler e validar uma temperatura do MCP9808 por I2C, distinguindo uma medição válida de NACK, timeout e recebimento incompleto.

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