Já fui questionado inúmeras vezes a respeito de como fazer a comunicação entre o ESP8266 e o Arduíno por serial (UART). Este questionamento é muito comum quando o ESP por algum motivo não consegue atender todos os requisitos de hardware, seja por falta de IO, entrada analógica ou até mesmo um projeto legado.
O que ocorre é que em alguns casos, a comunicação por UART não é viável, como por exemplo quando a UART é utilizada para debug ou outra aplicação.
Tendo em vista que a UART não possa ser utilizada no seu projeto, vou mostrar como usar a comunicação por I2C para enviar comandos entre o ESP8266 e o Arduino.
Comunicação I2C
A interface I2C, ou Inter-Integrated Circuit, é um tipo de comunicação serial, bi-direcional e por barramento, muito utilizada em diversos equipamentos, sensores, displays, memorias e muitos outros periféricos. Usualmente em dispositivos de baixa velocidade e pode operar em vários modos, como master-slave, multi-slave, multi-master entre outros.
O I2C possui duas linhas, uma de dados (SDA) e outra de clock (SCL), . Para o barramento funcionar, é necessário resistores, usualmente de 10k de pull-up na linha de clock e data.
Para saber detalhes de como funciona o I2C, mas recomendo a leitura desse material:
- Em inglês: https://learn.sparkfun.com/tutorials/i2c
- Em português: http://www.arduinobr.com/arduino/i2c-protocolo-de-comunicacao/
Utilização
Essa técnica pode ser muito útil em casos onde já exista um projeto Arduino pronto, ou um projeto com o ESP em que mais portas digitais ou entradas analógicas são necessárias. Isso pode reduzir o custo de um projeto ou simplificá-lo.
Comunicação master-slave ou mestre-escravo
Para entender o papel do master (mestre), ele é quem envia os comandos para o slave (escravo) responder. O slave não pode iniciar a comunicação, e sim esperar o master perguntar.
Nesse modo, apenas um master pode existir e cada slave terá um endereço, não podendo repetir esse endereço no mesmo barramento. O endereço pode variar entre 7 a 10bits, o que significa que em um único barramento, pode existir algo entre 128 a 1024 slaves
Para esse exemplo, vou utilizar um Arduino como slave e um ESP8266 (Wemos) como master, onde serão enviados comandos para o Arduino através do ESP8266. Não será possível utilizar o ESP como slave neste artigo.
Existe uma limitação na utilização do ESP8266 como slave, mas não devido ao hardware e sim o firmware. Vou tentar abordar esse tema mais para frente utilizando o SDK com NONOS (desenvolvimento nativo).
Hardware
É importante lembrar que no ESP8266 o nivel logico é de 3.3V e no Arduino é de 5V. Para resolver isso sem o medo de queimar o ESP é usar um level shifter, que faz a conversão do sinal logico entre as duas tensões de trabalho.
No caso do uso de level shifter, os resistores de 10k não são necessários, já que o circuito já possui, conforme o esquema abaixo:
Abaixo segue o esquema completo de ligação:
Código
Nesse exemplo, vou mostrar como piscar o LED do Arduino Nano e devolver na resposta do comando o estado dele para que o LED da Wemos também pisque. Se o barramento for desconectado, ambos param de piscar.
Vamos utilizar a IDE Arduino versão 1.8.2 (Linux Manjaro).
Para o lado do master, temos o seguinte código:
/*
I2C Master
Author: Pedro Minatel
*/
#include <Wire.h>
//Estado do LED
uint8_t led_status = 0;
void setup() {
Wire.begin();
//LED embarcado na placa
pinMode(LED_BUILTIN, OUTPUT);
//Serial para debug
Serial.begin(115200);
}
void loop() {
//Inicia a transmissão para o endereço 2
Wire.beginTransmission(2);
//Escreve no barramento o estado do LED
Wire.write(led_status);
//Termina a transmissão
Wire.endTransmission(); // stop transmitting
//Espera a resposta do escravo
int data_available = Wire.requestFrom(2, 1);
//Se houver resposta
if(data_available == 1) {
//Lê o byte de resposta
uint8_t slaveResp = Wire.read();
//Controla o LED de acordo com a resposta do escravo
if(slaveResp==0){
digitalWrite(LED_BUILTIN, LOW);
Wire.write(led_status);
} else if(slaveResp==1){
digitalWrite(LED_BUILTIN, HIGH);
Wire.write(led_status);
}
Serial.println(slaveResp);
//Inverte o estado do LED
led_status = !led_status;
} else {
//Se caso a resposta do escravo for diferente
Serial.print("Erro de bytes recebidos: ");
Serial.println(data_available);
}
delay(100);
}
E para o(s) slave(s):
/*
I2C Slave
Author: Pedro Minatel
*/
#include <Wire.h>
//Dado recebido do mastre
uint8_t rec_value = 0;
void setup() {
//Inicia como escravo no endereço 2
Wire.begin(2);
//Callback para dados recebidos do mestre
Wire.onReceive(receiveCallback);
//Callback para requisições recebidas do mestre
Wire.onRequest(requestCallback);
//LED embarcado na placa
pinMode(LED_BUILTIN, OUTPUT);
//Serial para debug
Serial.begin(115200);
}
void loop() {
}
//Callback para dados recebidos do mestre
void receiveCallback(int bytes)
{
//Se tiver dados recebidos
if(Wire.available() != 0)
{
for(int i = 0; i< bytes; i++)
{
//Lê os dados
rec_value = Wire.read();
Serial.print("Recebido: ");
Serial.println(rec_value);
}
//Controla o LED de acordo com o dado recebido
if(rec_value==0){
digitalWrite(LED_BUILTIN, LOW);
} else if(rec_value==1){
digitalWrite(LED_BUILTIN, HIGH);
}
}
}
//Callback para requisições recebidas do mestre
void requestCallback(int bytes) {
//Retorna para o mestre o valor recebido
Wire.write(rec_value);
}
Vale lembrar que esse é um simples exemplo que pode ser customizado de acordo com a necessidade de cada projeto.
Conclusão
A grande vantagem deste modo, é que a serial pode ser utilizada para o debug da placa sem a necessidade de tratamento dos pacotes de comunicação, além do fato de não ter o inconveniente dos dados que são enviados pela serial no processo de boot do ESP.
Esse método ainda pode ser útil em inúmeros casos e situações, ficando assim mais uma solução na manga no momento do aperto!
Happy Hacking!






