Mais um vídeo de Arduino UNO
com o ESP8266, desta vez na versão ESP01. Esse modelo eu considero pequeno e
também mais barato e, não por isso, menos poderoso. Neste projeto, conectamos
nosso ESP01, sem utilizar comando AT, no Arduino Uno, sendo que ambos possuem
seus códigos fontes INO. Montamos isso como alternativa a fazer um link serial,
ou seja, utilizamos os códigos pela possibilidade de incluir o WiFiManager,
Watchdog, entre outros recursos. Resumindo: vamos utilizar um ESP8266 para
conexões enquanto um Arduino Uno responde as requisições através da serial.
Montagem
No nosso esquema de automação,
bastante simples, os resistores de 1.2K e 2.2K na parte de cima são para baixar
a voltagem de 5V para próximo de 3.3V. Destaco que, se você quiser, é possível
ligar vários módulos de relés neste projeto. Neste nosso caso: até 12. No
entanto, neste nosso exemplo eu usei Led.
Como gravar
Exibo aqui o esquemático de
como gravar o ESP01. Se você tiver o adaptador, recomendo utilizá-lo.
Nesta imagem abaixo você vê o
adaptador, que aconselho a aquisição, pelas facilidades que ele possibilita. Mais
informações de como gravar o ESP01 você pode ver neste vídeo: GRAVANDO NO ESP01.
Demonstração
ESP8266.ino
Vamos incluir a lib ESP8266WiFi.h
e trabalhar com os parâmetros de rede. Seguimos definindo o Timeout da conexão
e o objeto do servidor, além de declarar o buffer.
#include <ESP8266WiFi.h> //Troque pelos dados da sua rede const char* ssid = "SSID"; const char* password = "12345678"; const char* ip = "192.168.0.177"; //Timeout da conexão #define TIMEOUT 500 #define MAX_BUFFER 300 //Server na porta 80 (padrão http) WiFiServer server(80); //Buffer onde serão gravados os bytes da comunicação uint8_t buffer[MAX_BUFFER];
ESP8266.ino – Setup
Minha comunicação serial com o
Arduino é 115200, o que considero rápido. Enviamos informações da rede para
conectar e esperamos a conexão com o acess point. Configuramos o IP e inicializamos
o server. Pessoal, nesta parte do código nós utilizamos a função Disconnect, pois
ela consegue zerar todas as minhas variáveis internas de comunicação e, digo
isso porque tem situações que essa ação é bastante necessária.
void setup() {
Serial.begin(115200);
//Envia a informação da rede para conectar
WiFi.disconnect();
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
//Espera a conexão com o access point
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
//Configura o IP
IPAddress ipAddress;
ipAddress.fromString(ip);
WiFi.config(ipAddress, WiFi.gatewayIP(), WiFi.subnetMask());
//Inicializa o server
server.begin();
}
ESP8266.ino – loop 1/4
Verifico se houve conexão do client.
Se ninguém conectou, apenas retornamos sem fazer nada. Já no caso de conexão,
marco o tempo que o cliente se conectou e a quantidade de bytes lidos.
void loop() {
//Verifica se alguem se conectou
WiFiClient client = server.available();
if (!client) {
//Se ninguém conectou apenas retorna sem fazer nada
return;
}
//Marca o tempo que o cliente se conectou e a quantidade
//de bytes lidos
uint32_t connectedTime = millis();
int bytesRead = 0;
ESP8266.ino – loop 2/4
Seguimos colhendo os dados de
conexão do cliente. Se o tempo passar do tempo máximo sem ler nenhum byte, fechamos
a conexão.
//Enquanto o cliente estiver conectado
while(client.connected())
{
//Tempo agora
uint32_t now = millis();
//Quanto tempo passou desde a conexão com o cliente
uint32_t ellapsed = now - connectedTime;
//Se o tempo passou do tempo máximo e não leu nenhum byte
if(ellapsed > TIMEOUT && bytesRead == 0)
{
//Fecha a conexão com o cliente
client.stop();
break;
}
ESP8266.ino – loop 3/4
Verificamos se o cliente
possui bytes a serem lidos. Enviamos os bytes pela serial e aumentamos o
contador de bytes lidos.
int available = client.available();
//Se o cliente possui bytes a serem lidos
if(available)
{
int bufferSize = available < MAX_BUFFER ? available : MAX_BUFFER;
int readCount = client.read(buffer, bufferSize);
//Envia os bytes pela serial e aumenta o contador de bytes lidos
Serial.write(buffer, readCount);
Serial.flush();
bytesRead += readCount;
}
ESP8266.ino – loop 4/4
Verificamos aqui se a serial
possui bytes a serem lidos do UNO para o ESP01. Analisamos ainda se é o byte
que define a finalização da conexão. Enviamos o que ainda não tenha sido
enviado e espera um tempo para o cliente receber. Enviamos os bytes ao cliente e depois fechamos a conexão.
available = Serial.available();
//Se a serial possui bytes a serem lidos
if(available)
{
int bufferSize = available < MAX_BUFFER ? available : MAX_BUFFER;
//Lê os bytes
Serial.readBytes(buffer, bufferSize);
//Se for o byte que define a finalização da conexão
if(buffer[bufferSize-1] == 127)
{
client.write(buffer, bufferSize-1);
//Envia o que ainda não tenha sido enviado
client.flush();
//Espera um tempo para o cliente receber
delay(100);
//Fecha a conexão com o cliente e sai do 'while'
client.stop();
break;
}
//Envia os bytes para o cliente
client.write(buffer, bufferSize);
}
}//while(client.connected())
}//loop
Arduino.ino
Definimos aqui o pino onde
está o primeiro relê e quantos pinos serão utilizados. Por fim, inicializamos
os pinos.
#define FIRST_PIN 2 //Pino onde está o primeiro relê
#define PINS_COUNT 12 //Quantos pinos serão utilizados
//Mantém o estado atual dos pinos (HIGH ou LOW)
int pinsStatus[PINS_COUNT];
void setup()
{
Serial.begin(115200);
//Inicializa os pinos
setupPins();
}
Arduino.ino – setupPins
Apontamos, nesta etapa, os
pinos que estão ligados os relés como saída.
void setupPins()
{
//Coloca os pinos que estão ligados os relês como saída
for(int i=0; i<PINS_COUNT; i++)
{
pinsStatus[i] = LOW;
int pinNumber = FIRST_PIN + i;
pinMode(pinNumber, OUTPUT);
digitalWrite(pinNumber, pinsStatus[i]);
}
}
Arduino.ino – loop
Verificamos aqui se há um novo
cliente. Executamos a leitura da requisição e, se a requisição não for para o
favicon, executamos a ação com o valor passado na requisição. Enviamos, então,
a resposta ao cliente.
void loop()
{
//Verifica se há um novo cliente
if (Serial.available())
{
//Faz a leitura da requisição
char* request = readRequest();
//Se a requisição não for para o favicon
if(strstr(request, "favicon") == NULL)
{
//Executamos a ação com o valor passado na requisição
execute(getAction(request), getValue(request));
//Envia a resposta ao cliente
sendResponse();
}
else
{
Serial.print(
"HTTP/1.1 404 Not Found\r\n"
"Content-Length: 0\r\n"
"Connection: close\r\n"
"\r\n");
}
Serial.write(127);
}
}
Arduino.ino – readRequest 1/2
Partimos para a leitura da
primeira linha da requisição. Lembrando que nesta etapa, apenas a primeira
linha da requisição nos interessa.
//Faz a leitura da primeira linha da requisição
char* readRequest()
{
bool currentLineIsBlank = true;
char request[50];
int i = 0;
bool firstLine = true;
while (true){
while(!Serial.available());
char c = Serial.read();
//Apenas a primeira linha da requisição nos interessa
if(firstLine){
request[i] = c;
i++;
}
Arduino.ino – readRequest 2/2
Expomos aqui que a última
linha da requisição é um \r\n sozinho, após o \r\n da linha anterior.
Se foi feita leitura de
qualquer caracter que não for \n e \r, significa que a linha não está em branco.
if (c == '\n'){
//A última linha da requisição é um \r\n sozinho, após o \r\n da linha anterior
if(currentLineIsBlank){
//Se chegou aqui é porque a requisição foi lida por completo
break;
}
currentLineIsBlank = true;
firstLine = false;
}
else if (c != '\r'){
//Se leu qualquer caracter que não for \n e \r significa que a linha não está em branco
currentLineIsBlank = false;
}
}
return request;
}
Arduino.ino – sendResponse
Veja nesta parte a função que
envia o HTML para o cliente.
//Envia o HTML para o cliente
void sendResponse()
{
//Envia o cabeçalho HTTP
Serial.print(
"HTTP/1.0 200 OK\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Connection: close\r\n"
"\r\n");
Serial.println("<!DOCTYPE HTML>");
Serial.println("<html>");
head();//Envia o cabeçalho do HTML
body();//Envia o corpo do HTML
Serial.println("</html>");
}
Arduino.ino – head
Já, nesta parte, enviamos o CSS
para modificar a aparência da página.
//Envia o CSS para modificar a aparência da página
void head()
{
Serial.println(F("<head>"
"<meta name='viewport' content='width=device-width, initial-scale=1.0'>"
"<style>"
"body{"
"text-align: center;"
"font-family: sans-serif;"
"font-size: 14px;"
"}"
"p{"
"color:#555;"
"font-size: 12px;"
"}"
".button{"
"outline: none;"
"display: block;"
"border: 1px solid #555;"
"border-radius:18px;"
"width: 150px;"
"height: 30px;"
"margin: 10px;"
"margin-left: auto;"
"margin-right: auto;"
"cursor: pointer;"
"}"
".button_off{"
"background-color:#FFF;"
"color: #555;"
"}"
".button_on{"
"background-color:#2C5;"
"color: #fff;"
"}"
"</style>"
"</head>"));
}
Arduino.ino – body
Criamos o body e os botões, criando um botão para cada pino que possui um relé.
//Cria o body e os botões
void body()
{
Serial.println("<body>");
String buttons = "";
//Cria um botão para cada pino que possui um relê
for(int i=0; i<PINS_COUNT; i++)
{
buttons.concat(button(i));
}
Serial.println(buttons);
Serial.println("</body>");
}
Arduino.ino – button
Criamos um botão com a
aparência e ação correspondente ao estado atual do relé.
//Cria um botão com a aparência e ação correspondente ao estado atual do relê
String button(int number)
{
String label = String(number + 1);
String className = "button ";
className += pinsStatus[number] == HIGH ? "button_on" : "button_off";
String action = pinsStatus[number] == HIGH ? "off" : "on";
return "<button class=\"" + className + "\"onclick=\"location.href='?" + action + "=" + label + "'\">" + label + "</button>";
}
Arduino.ino – getAction getValue
Retornamos a ação que o
cliente deseja executar (on off), bem como o valor (número do relé) que a ação
será executada.
//Retorna a ação que o cliente deseja executar (on off)
String getAction(char *request)
{
return getStringBetween(request, '?', '=');
}
//Retorna o valor (numero do relê) que a ação será executada
String getValue(char *request)
{
return getStringBetween(request, '=', ' ');
}
Arduino.ino – getStringBetween
Retornamos a string que fica
entre o primeiro caractere “start” e “end”. Também retornamos
o endereço de memória do caractere “start”.
//Retorna a string que fica entre o primeiro caractere 'start' e o primeiro caractere 'end'
String getStringBetween(char* input, char start, char end)
{
String str = "";
//retorna o endereço de memória do caractere 'start'
char* c = strchr(input, start);
//Se não achou o caractere
if(c == NULL)
{
return "";
}
//Vai para o próximo caractere
c++;
//Enquanto não chegar ao caractere 'end' ou ao final da string
while(*c != end && *c!='\0')
{
str += *c;
c++;
}
return str;
}
Arduino.ino – execute
Para finalizar, executamos a
ação junto ao valor (número do relé). Verificamos se é uma das duas ações que
esperamos (On Off) e numeramos a partir do 1, mas o array começa do 0. Então,
tiramos 1. O número do pino, portanto, será o índice mais o número do pino onde
os relés começam. Lembrando que os relés devem estar em sequência a partir do
pino inicial (FIRST_PIN).
//Executada a ação junto ao valor (número do relê)
void execute(String action, String value)
{
//Se é uma das duas ações que esperamos
if(action == "on" || action == "off")
{
//Os relês são numerados a partir do 1, max o array começa do 0
//então tiramos 1
int index = value.toInt() - 1;
//O número do pino será o índice mais o número do pino onde os relês
//começam. Os relês devem estar em sequência a partir do pino inicial (FIRST_PIN)
int pinNumber = FIRST_PIN + index;
int status = action == "on" ? HIGH : LOW;
digitalWrite(pinNumber, status);
pinsStatus[index] = status;
}
}
Faça o download dos arquivos






6 Comentários
show! Parabéns professor!! Teria como html ficar no esp01? ele tem mais memoria que o arduino uno não? assim podia ser um site mais bem elaborado... Obrigado por mais essa aula!! Sucesso!!!
ResponderExcluirFernando, parabéns pelos vídeos. Danifiquei um ESP8266 e acabei caindo de paraquedas nos seus vídeos. A minha dúvida é o seguinte: lí que a conexão 3.3V do arduíno pode fornecer no máximo 50 mA e o módulo ESP8266 pode consomir mais de 200 mA. Procede isso? É seguro usar dessa forma? Obrigado.
ResponderExcluirPrezado Fernando.
ResponderExcluirMuito bom seu artigo, porém, gostaria de fazer alguns comentários.
Em momento algum você demonstra a utilização do proposto, apenas faz alguns comentários sobre o código sem demonstrar realmente se funciona.
Na foto em "Demonstração", existe um celular conectado ao sistema, porém, o endereço lá é 192.168.3.177 e, no seu código o endereço é 192.168.0.177, ou seja, são redes diferentes, pois não foi atribuído uma classe.
Acompanho seus artigos, porém, como sugestão, não seria mais interessante para os iniciantes uma melhor explicação, com detalhamento e funcionamento?
Muitos dos meus alunos ficam perdidos quando tentam seguir vossas explicações.
Temos um outro blog na Internet (Arduino e Cia) onde o autor faz comentários ricos de detalhes, explicando todos os passos e, realmente testando suas montagens, portanto, não seria bom que o colega também pudesse seguir esses exemplos?
Nós (eu e meus alunos) gostamos muito da forma descontraída que passa o conhecimento, porém, se puder fazer essa pequena mudança, então, será perfeito.
Muito obrigado.
Prof. Norberto
Estou com o seguinte problema com o código ESP8266.ino. Como posso resolver?
ResponderExcluirIn file included from C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/ESP8266WiFi.h:39:0,
from C:\Users\....\Documents\Arduino\ESP8266SerialArduinoUno\ESP8266\ESP8266.ino:1:
C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h: In instantiation of 'size_t WiFiClient::write(T&, size_t) [with T = unsigned char [300]; size_t = unsigned int]':
C:\Users\....\Documents\Arduino\ESP8266SerialArduinoUno\ESP8266\ESP8266.ino:98:42: required from here
C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h:123:36: error: request for member 'available' in 'source', which is of non-class type 'unsigned char [300]'
size_t left = source.available();
^
C:\Users\....\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.2.0\libraries\ESP8266WiFi\src/WiFiClient.h:127:5: error: request for member 'read' in 'source', which is of non-class type 'unsigned char [300]'
source.read(buffer.get(), will_send);
^
exit status 1
Erro compilando para a placa NodeMCU 0.9 (ESP-12 Module)
não apareceu nd na meu monitor serial, dessa forma n consigo acessar os botões de comando pelo google.
ResponderExcluirusei o código que está no site.
tambem nao consigo
Excluir