Saludos!

Siguiendo el planning del UWS, una de las partes principales es la comunicación del ESP32 con los arduinos a través del I2C. Antes de explicar qué mensajes se van a enviar, tenemos que configurar y probar la comuncación I2C. En nuestro proyecto, el ESP32 solo va enviar mensajes a los Arduinos, así que de momento solo implementaremos los mensajes en un dirección, del ESP32 a los diferentes arduinos.

ESP32

Usaremos la librería de I2C que ya usamos en el post Conectando el ESP al BMX055. Actualmente la librería está pensada para hablar con sensores y módulos comerciales. Generalmente, todos estos dispositivos funcionan mediante accesos a registros que contienen la información que queremos escribir/leer. Por tanto, la librería primero envía la dirección del I2C que se quiere despertar, luego el identificador del registro al que se quiere acceder, y luego los datos.

Para realizar nuestra comunicación tenemos dos opciones:

  1. Implementar los diferentes registros en nuestro Arduino, y poder usar la librería del ESP32 tal cual está
  2. Enviar el identificador del registro, pero no hacerle caso en el Arduino. De esta manera podemos usar la librería como está pero sin hacer caso al valor del registro
  3. Modificar la función de envío de I2C para que permita enviar mensajes con solo datos, sin necesidad de enviar el registro de destino.

La versión que explicaremos aquí, es implementar una nueva función y modificar la librería del I2C, para permitir mandar una ristra de datos sin necesidad de enviar el registro. Si analizamos la función original podemos ver claramente cual es el proceso, y tan solo eliminando una línea, tenemos una función equivalente sin necesidad del registro. La función original es:

1
2
3
4
5
6
7
8
9
10
11
12
13
esp_err_t I2C::writeBytes(uint8_t devAddr, uint8_t regAddr, size_t length, const uint8_t *data, int32_t timeout) {
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (devAddr << 1) | I2C_MASTER_WRITE, I2C_MASTER_ACK_EN); // Avisamos al I2C por dirección de I2C
    i2c_master_write_byte(cmd, regAddr, I2C_MASTER_ACK_EN); // Enviamos el registro que queremos modifical (<< Borramos esta línea)
    i2c_master_write(cmd, (uint8_t*) data, length, I2C_MASTER_ACK_EN); // Enviamos todos los datos del array de entrada
    i2c_master_stop(cmd);
    esp_err_t err = i2c_master_cmd_begin(port, cmd, (timeout < 0 ? ticksToWait : pdMS_TO_TICKS(timeout)));
    i2c_cmd_link_delete(cmd);

    // Comprobación de errores...
    // ...
}

Con un cambio de definición y eliminando una línea tenemos la función que nos permite enviar bytes sin necesidad de un registro. Con esto ya se pueden enviar los mensajes a los diferentes arduinos que tengamos conectados. La función ha quedado:

1
2
3
4
5
6
7
8
9
10
11
12
esp_err_t I2C::writeBytes(uint8_t devAddr, size_t length, const uint8_t *data, int32_t timeout) {
    i2c_cmd_handle_t cmd = i2c_cmd_link_create();
    i2c_master_start(cmd);
    i2c_master_write_byte(cmd, (devAddr << 1) | I2C_MASTER_WRITE, I2C_MASTER_ACK_EN);
    i2c_master_write(cmd, (uint8_t*) data, length, I2C_MASTER_ACK_EN);
    i2c_master_stop(cmd);
    esp_err_t err = i2c_master_cmd_begin(port, cmd, (timeout < 0 ? ticksToWait : pdMS_TO_TICKS(timeout)));
    i2c_cmd_link_delete(cmd);

    // Comprobación de errores...
    // ...
}

Arduinos

Ahora que sabemos como enviar mensajes a los Arduinos, nos queda recibirlos y tratarlos para poder usarlos. Esta parte está más documentada y podemos hacer uso de las funciones que nos permite configurar el Arduino como Slave y leer mensajes enviados por otro.

Lo primero que tenemos que hacer es preparar la función que se ejecutará cada vez que nos llegue un mensaje de I2C. Una implementación muy sencilla podría ser:

1
2
3
4
5
6
7
8
9
10
11
12
char buffer[10];
uint8_t newData; // Marca si hay nuevos datos
void receiveEvent(int howMany) {  

  //Recibimos el mensaje completo
  byte* p = buffer;
  while (Wire.available()) { // Si hay datos en el receptor del I2C
    newData = 1;
    *p++ = Wire.read(); // los ponemos en el buffer
  }

}

Ya tenemos la función que va a leer los datos, y además guarda en una variable que ha recibido algún dato nuevo. En el Setup tenemos que configurar el I2C para que escuche mensajes enviados a él (solo tiene que escuchar a los mensaje con su dirección I2C, configurable). Para ello en el Setup:

1
2
3
Wire.begin(0x99);     // Escuchamos los mensajes hacia 0x99        
Wire.setClock(400000);
Wire.onReceive(receiveEvent);   // Configuramos la función que se llamará cuando llegue un nuevo mensaje

Por último en la parte del bucle tenemos que ver si hay nuevos datos desde la última vez que ejecutamos el bucle, y si los hay hacemos lo que queramos con ellos.

1
2
3
4
5
6
7
8
9
10
void loop(){
  // ...

  if (newData == 1){
    newData = 0;
    tratarDatos(buffer);
  }

  // ...
}

Conclusión

Este post puede servir para comunicar un procesador potente tipo el ESP32 para que realice los cálculo que precise y actúe sobre unos Arduinos que únicamente se encargan de ejecutar las ordenes del ESP32 (es como lo usaremos nosotros en el UWS).

Espero que os haya servido de ayuda y no dudéis en dejar vuestras dudas y sugerencias en los comentarios.

Un saludo!!!


Gluón

Teleco con ganas de aprender más y compartirlo. Viajero empedernido y amante de la fotografía y la tecnología. Espero dejar mi granito de arena y que este pueda servir de ayuda.