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