i2c-bus-nanoframework

Bus I2C en nanoFramework: sensores y escáner

  • 5 min

El bus I2C es un protocolo para conectar varios dispositivos con dos líneas.

Con UART necesitaríamos una conexión independiente para cada sensor. El protocolo I2C (Inter-Integrated Circuit) permite compartir el bus.

Al ser un bus, podemos conectar varios dispositivos a las mismas dos líneas. El controlador selecciona con quién quiere hablar mediante una dirección.

En esta entrada aprenderemos a usar la clase I2cDevice para gobernar este bus, escanear qué dispositivos hay conectados y leer datos de ellos.

Para este tutorial necesitas instalar el paquete NuGet: nanoFramework.System.Device.I2c

Conceptos básicos: SDA y SCL

El I2C usa solo dos líneas (más GND, por supuesto):

  • SDA (Serial Data): Por donde viajan los datos.
  • SCL (Serial Clock): El reloj que marca el ritmo de la comunicación.

A diferencia de la UART, aquí hay reloj (es síncrono). Y muy importante: las líneas I2C funcionan con lógica “Open Drain”, lo que significa que necesitan resistencias Pull-Up para funcionar. Afortunadamente, la mayoría de módulos de sensores que compras (breakout boards) ya traen estas resistencias soldadas.

Configuración en nanoFramework

En el ESP32, podemos usar casi cualquier par de pines para I2C, pero es recomendable usar los pines por defecto para evitar dolores de cabeza.

  • ESP32 Estándar (DevKit V1):
  • SDA: GPIO 21
  • SCL: GPIO 22

La clase principal es I2cDevice. Para instanciarla, primero necesitamos definir la configuración de conexión (I2cConnectionSettings), que incluye el ID del bus (normalmente 1 en ESP32) y la Dirección del Esclavo.

using System.Device.I2c;

// Configuración: Bus 1, Dirección del dispositivo (ej. 0x77 para BMP280)
I2cConnectionSettings settings = new I2cConnectionSettings(1, 0x77);

// Crear el dispositivo
I2cDevice sensor = I2cDevice.Create(settings);
Copied!

Crear un escáner I2C

Antes de intentar leer un sensor complejo, lo primero es saber: ¿Está mi sensor conectado y detectado? ¿Y cuál es su dirección? (A veces compramos un chip chino y la dirección no es la que dice el datasheet).

Vamos a escribir un pequeño programa que recorra todas las direcciones posibles (0 a 127) e intente saludar.

using System;
using System.Device.I2c;
using System.Diagnostics;
using System.Threading;

namespace I2cScanner
{
    public class Program
    {
        public static void Main()
        {
            Debug.WriteLine("Iniciando Escáner I2C...");

            // En ESP32, Bus 1 suele ser los pines 21(SDA) y 22(SCL)
            // Si usas otros pines, deberás configurarlos con 'I2cDevice.Create' usando 'I2cConnectionSettings'
            // Nota: En versiones recientes de nanoFramework para ESP32, puedes necesitar configurar los pines
            // usando la librería System.Device.I2c (Configuration)
            
            // Recorremos las direcciones válidas (normalmente de 8 a 119)
            for (int address = 0x08; address < 0x78; address++)
            {
                I2cConnectionSettings settings = new I2cConnectionSettings(1, address);
                
                using (I2cDevice device = I2cDevice.Create(settings))
                {
                    try
                    {
                        // Intentamos leer un byte. 
                        // Si el dispositivo existe, responderá (ACK).
                        // Si no existe, lanzará excepción o devolverá error.
                        byte[] buffer = new byte[1];
                        var result = device.Read(buffer);

                        // Si la lectura es exitosa
                        Debug.WriteLine($"¡Dispositivo encontrado en dirección: 0x{address:X2}!");
                    }
                    catch
                    {
                        // No hay nadie en casa, ignoramos
                    }
                }
            }
            
            Debug.WriteLine("Escaneo finalizado.");
            Thread.Sleep(Timeout.Infinite);
        }
    }
}
Copied!

Si ves en la consola ¡Dispositivo encontrado en dirección: 0x77!, ya sabes a quién tienes que llamar.

Leer un sensor con el patrón WriteRead

La comunicación I2C con sensores suele seguir un patrón estándar:

  1. Escribimos al sensor diciéndole: “Quiero leer el registro X”.
  2. Inmediatamente Leemos la respuesta del sensor.

Esto se hace de forma atómica usando el método WriteRead.

Ejemplo: leer el identificador de un chip

Casi todos los sensores (como el acelerómetro MPU6050 o el barómetro BMP280) tienen un registro llamado WHO_AM_I o CHIP_ID. Es un registro de solo lectura que devuelve un valor fijo, ideal para verificar que estamos hablando con el chip correcto.

Supongamos un sensor BMP280 (Dir 0x76 o 0x77). Su registro de ID suele ser 0xD0 y debe devolver 0x58.

public static void LeerIdentificador()
{
    // 1. Conexión al BMP280 (asumiendo dirección 0x76)
    I2cConnectionSettings settings = new I2cConnectionSettings(1, 0x76);
    using I2cDevice bmp280 = I2cDevice.Create(settings);

    // 2. Preparamos los buffers
    byte[] registroAleer = new byte[] { 0xD0 }; // Dirección del registro ID
    byte[] datoRecibido = new byte[1];          // Donde guardaremos la respuesta

    // 3. Ejecutamos WriteRead (Transacción atómica)
    bmp280.WriteRead(registroAleer, datoRecibido);

    // 4. Comprobamos
    Debug.WriteLine($"ID del Chip: 0x{datoRecibido[0]:X2}");

    if (datoRecibido[0] == 0x58)
    {
        Debug.WriteLine("¡Es un BMP280 auténtico!");
    }
}
Copied!

Usar un driver de IoT.Device

Aquí viene la buena noticia. Para aprender está genial enviar bytes crudos, pero para trabajar… no hace falta reinventar la rueda.

La comunidad de .NET IoT y nanoFramework mantiene un repositorio enorme de drivers ya escritos para cientos de sensores populares (AHT10, BMP280, MPU6050, SSD1306, etc.).

Simplemente busca en NuGet: nanoFramework.IoT.Device.Bmxx80 (para el BMP280).

// Ejemplo usando el driver oficial (Mucho más fácil)
using Iot.Device.Bmxx80;
using Iot.Device.Bmxx80.FilteringMode;

// ...
Bmp280 bmp = new Bmp280(I2cDevice.Create(new I2cConnectionSettings(1, 0x76)));

bmp.TemperatureSampling = Sampling.HighResolution;

var lectura = bmp.Read();
Debug.WriteLine($"Temperatura: {lectura.Temperature.DegreesCelsius} ºC");
Copied!

Siempre revisa si existe un driver NuGet antes de ponerte a leer el datasheet y programar registros hexadecimales a mano. Te ahorrará días de trabajo.