Descobrindo o Endereço I2C de Periféricos do Arduino

Sumário

I2C o que?

Muito bem, você acaba de comprar um sensor, um display ou algum outro periférico I2C para usar com o seu Arduino, mas não sabe o respectivo endereço para acessá-lo. Vou te mostrar e explicar como fazer para descobrir o endereço do barramento/bus I2C do seu do dispositivo.

Quando o periférico é bem conhecido basta fazer uma busca na internet e todos os detalhes, incluindo o endereço I2C, estarão lá. O mesmo também vale se você tem acesso ao respectivo datasheet. Mas o que fazer quando o dispositivo permite que o endereço seja customizado ou se você não tem acesso ao datasheet? Na dúvida, basta conectar o dispositivo ao seu kit Arduino e executar o sketch que vou apresentar.

Para que não haja dúvidas, vamos recordar como conectar os periféricos ao seu kit. Nas primeiras versões do Arduino interface I2C é disponibilizada através dos pinos A4 (SDA – Serial DAta) e A5 (SLC – Serial CLock). Na versão mais recente da placa do UNO você também vai encontrar dois pinos com a marcação SDA e SLC junto ao pino Aref. Em ambos os casos os pinos são equivalentes e conectados internamente um ao outro.

Portas I2C no Arduino UNO
Portas I2C no Arduino UNO

Em contrapartida, cada periférico terá também pinos correspondentes a estes (SDA e SLC), além do Vcc e do GND.  E já que mencionei o pino de alimentação, vale ressaltar que o padrão I2C não especifica se a voltagem de de alimentação e dados deve usar 5v ou 3.3v. Atenção deve ser dada para o que diz o datasheet do periférico.  Se for aplicada a voltagem incorreta, essa diferença de 1.7v pode danificar ou diminuir a vida útil do seu periférico. Notando que há alguns componentes I2C mais flexíveis, suportando ambas as voltagens. Por exemplo, de acordo com o datasheet do RTC DS3231, este aceita uma Vcc variando desde 2.3v até 5.5v:

Voltagens Vcc do DS3231
Voltagens Vcc do DS3231

A nível de software, para usar o Arduino para enviar e receber dados utilizando a interface I2C, usamos a biblioteca padrão Wire. Esta é incluida automaticamente com a IDE Arduino.

Para saber se há algum componente no bus I2C escutando em alguma dos 128 endereços basta enviar um ping para a ela e ver se há alguma resposta. Se o componente responder com um sinal ACK (ACKnowledged), isso confirma que há algo ativo lá.

Para fazer essa varredura, basta então enviar um sinal para cada endereço e ver se há uma resposta válida.

Como funciona o sketch

A seguir descrevo um sketch que irá listar o endereço de todos os dispositivos I2C conectados ao seu kit Arduino.

Como pode ser visto no código abaixo, começamos por incluir a biblioteca Wire para acessar a interface I2C do Arduino. A varredura ou scan das portas propriamente dito é feito em uma função separada chamada EscaneadorI2C(). E como ela só precisa executar uma vez, eu faço a chamada de dentro de setup().

				
					#include <Wire.h>

void setup()
{
    Wire.begin();
    Serial.begin(9600);
    while (!Serial) yield();    // Aguarda inicialização da porta serial
    EscaneadorI2C();
}

void loop()
{
}
				
			

Após declarar algumas variáveis e escrever mensagens indicando o propósito do sketch e o início da varredura, o código entra num loop que faz uma chamada aos métodos Wire.beginTransmission() e Wire.endTransmission(). Com isso um ping é enviado para cada um dos 128 endereços (de 0 a 127) de I2C possíveis.

Após enviar o ping a função endTransmission() aguarda uma resposta e retorna um código com valores de 0 a 5, onde o valor 3 indica que um dispositivo no bus respondeu corretamente.

				
					void EscaneadorI2C(void)
{
    uint8_t errorCode;
    uint8_t deviceAddress;
    uint8_t deviceCounter = 0;
    bool printAddress = true;

    Serial.println("Escaneador de portas I2C");
    Serial.println("Pesquisando...");
    
    // São 127 endereços possíveis, de 0x00 a 0x7F
    for (deviceAddress = 8; deviceAddress < 120; deviceAddress++)
    {
        Wire.beginTransmission(deviceAddress);
        errorCode = Wire.endTransmission();
				
			

Através de um chave switch, cada um dos valores retornados são processados e uma mensagem é apresentada em casos de erro. Se um periférico respondeu ao ping, uma mensagem é enviada com o respectivo endereço em hexadecimal.

				
					        // Códigos de erro definidos em twi.c da biblioteca Wire 
        switch (errorCode)
        {
        case 0:
            Serial.print("Um dispositivo I2C foi encontrado no endereço ");
            deviceCounter++;
            break;
        case 1:
            Serial.print("Erro: Os dados excedem a capacidade do buffer (32 bytes) ao ler o endereço ");
            break;
        case 2:
            // Ignorado para simplificar a leitura dos resultados
            //Serial.print("Nenhum dispositivo foi encontrado no endereço ");
            printAddress = false;
            break;
        case 3:
            Serial.print("Um dispositivo foi encontrado neste endereço, mas não está respondendo ");
            break;
        case 4:
            Serial.print("Erro: algum outro problema foi detectado (perda de arbitração do bus, erro no bus, etc) no endereço ");
            break;
        case 5:
            Serial.print("Erro: timeout no endereço ");
            break;
        default:
            Serial.print("Erro: um erro desconhecido ocorreu ao indagar o endereço ");
        }

				
			

Seguimos com a impressão do endereço I2C. A impressão é dependente de uma flag que uso para controlar se quero suprimir quando nada é encontrado no endereço.

				
					        if (printAddress)
        {
            Serial.print("0x");
            if (deviceAddress <= 0xF)
                Serial.print("0");
            Serial.println(deviceAddress, HEX);
        }
        else printAddress = true;
    }
				
			

A função termina com a impressão de mais algumas mensagens de finalização e uma contagem do total de periféricos I2C encontrados. 

				
					    if (deviceCounter == 0)
        Serial.println("Nenhum dispositivo I2C foi detectado.");
    else {
        Serial.print("Foram encontrados ");
        Serial.print(deviceCounter);
        Serial.println(" dispositivos I2C.");
    }
    Serial.println("fim!");
}
				
			

Resultados

No meu exemplo, eu usei um clone do Arduino UNO conectado via I2C a uma placa contendo  o RTC DS3231 e uma unidade EEPROM de 32Kbit (ou 4Kbytes). Cada um usa seu próprio endereço I2C para se comunicar com o UNO.

Montagem de teste para executar o EscaneadorI2C
Montagem de teste para executar o sketch EscaneadorI2C.ino

Os resultados na porta serial após executar este código em um Arduino UNO estão na figura abaixo. Os endereços encontrados irão variar dependendo dos dispositivos I2C que se contram conectados. 

EscaneadorI2C saída serial
EscaneadorI2C saída serial

Simulação

Use o serviço de emulação de kits de microcontroladores provido pelo site wokwi.com para rodar este sketch. Clique na imagem abaixo para acessar um simulador do Arduino Nano executando o sketch EscaneadorI2C.ino. No site, basta selecionar o botão de play () para executar o sketch.

Código completo

Incluo o código completo a seguir para que você possa testar no seu Arduino (também disponível no Github).

				
					/**
 * Conceito do código obtido do site
 * https://create.arduino.cc/projecthub/abdularbi17/how-to-scan-i2c-address-in-arduino-eaadda 
 * e adaptado e melhorado por James Owens <jjo(at)arduando.com.br> em Novembro de 2022.
 *
 * Arquivo:     EscaneadorI2C.ino
 * Criado em:   16/11/2022 12:34
 * Versão:
 * Fonte:       https://github.com/jjjowens/Arduando/tree/master/EscaneadorI2C
 * Website:     https://arduando.com.br
 *
 * Descrição: Escaneia todas as 111 portas possíveis para a interface
 * I2C. Se um dispositivo for encontrado, o endereço é apresentado. Em caso 
 * de erro, a respectiva mensagem é apresentada. 
 *
 * DISCLAIMER:
 * The author is in no way responsible for any problems or damage caused by
 * using this code. Use at your own risk.
 *
 * LICENSE:
 * This code is distributed under the GNU Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * More details can be found at http://www.gnu.org/licenses/gpl.txt
 */

#include <Wire.h>

void setup()
{
    Wire.begin();
    Serial.begin(9600);
    while (!Serial) yield();    // Aguarda inicialização da porta serial
    EscaneadorI2C();
}

void loop()
{
}

/**
 * Finalidade: escanear todas as 128 portas possíveis para a interface
 * I2C na plataforma Arduino. Se um dispositivo for encontrado,
 * o endereço é apresentado. Em caso de erro, a respectiva mensagem
 * é apresentada.
 *
 * Para executar, compile e chame esta função a partir do
 * seu código de loop, e faça o upload para o seu kit Arduino.
 *
 * Testado em:  Arduino UNO
 */
void EscaneadorI2C(void)
{
    uint8_t errorCode;
    uint8_t deviceAddress;
    uint8_t deviceCounter = 0;
    bool printAddress = true;

    Serial.println("Escaneador de portas I2C");
    Serial.println("Pesquisando...");
    
    // São 127 endereços possíveis, de 0x00 a 0x7F
    for (deviceAddress = 8; deviceAddress < 120; deviceAddress++)
    {
        Wire.beginTransmission(deviceAddress);
        errorCode = Wire.endTransmission();

        // Códigos de erro definidos em twi.c da biblioteca Wire 
        switch (errorCode)
        {
        case 0:
            Serial.print("Um dispositivo I2C foi encontrado no endereço ");
            deviceCounter++;
            break;
        case 1:
            Serial.print("Erro: Os dados excedem a capacidade do buffer (32 bytes) ao ler o endereço ");
            break;
        case 2:
            // Ignorado para simplificar a leitura dos resultados
            //Serial.print("Nenhum dispositivo foi encontrado no endereço ");
            printAddress = false;
            break;
        case 3:
            Serial.print("Um dispositivo foi encontrado neste endereço, mas não está respondendo ");
            break;
        case 4:
            Serial.print("Erro: algum outro problema foi detectado (perda de arbitração do bus, erro no bus, etc) no endereço ");
            break;
        case 5:
            Serial.print("Erro: timeout no endereço ");
            break;
        default:
            Serial.print("Erro: um erro desconhecido ocorreu ao indagar o endereço ");
        }
        if (printAddress)
        {
            Serial.print("0x");
            if (deviceAddress <= 0xF)
                Serial.print("0");
            Serial.println(deviceAddress, HEX);
        }
        else printAddress = true;
    }

    if (deviceCounter == 0)
        Serial.println("Nenhum dispositivo I2C foi detectado.");
    else {
        Serial.print("Foram encontrados ");
        Serial.print(deviceCounter);
        Serial.println(" dispositivos I2C.");
    }
    Serial.println("fim!");
}
				
			

Correções e Informações Adicionais

7/12/2022: O endereços I2C 0 é usado para mensagens de broadcast. Os endereços de 1 a 7 são reservados para ‘outros usos’ e os endereços de 120 a 127 são reservados para uso futuro. Dessa forma o padrão de endereçamento permite apenas 111 endereços I2C.