Do “blink” ao BLYNK, uma viagem pela “Internet das coisas” nas asas do NodeMCU ESP-12E

Neste tutorial, aprenderemos a lidar com este fantástico dispositivo, o NodeMCU ESP-12E Development Kit V. 1.0, onde aprenderemos como:

  • Fazer piscar um LED pela internet
  • Ligar o ESP a um display LCD do tipo “OLED”
  • Capturar dados gerados por sensores tanto analógicos como digitais
  • Subir dados a um serviço de Internet das coisas tal como o Thinkspeak
  • Controlar “coisas” pela internet, utilisando-se aplicativos para smartphones como o BLINK

2: O NodeMCU

nodeMCU.jpg

O NodeMCU ESP-12E é a versão integrada do popular ESP8266, um Serial to Wi-Fi System On a Chip (SoC), que apareceu pela primeira vez em 2013 e lançado no mercado já no ano seguinte. O ESP8266 foi desenvolvido pela empresa chinesa com sede em Shangai, Espressif Systems, uma fabricante de circuitos integrados focada no desenvolvimento de chips de RF, particularmente Wi-Fi.

Existem vários módulos no mercado que se utilizam do chip ESP8266, Eles são nomeados ESP-NN, onde NN é um número 01, 02, … .. 12, etc. e as vezes seguido de uma letra. Estes módulos tipicamente possuem: o ESP8266 SoC, memória flash, um cristal e na maioria dos casos, uma antena. No link você pode encontrar a lista completa de dispositivos baseados  no ESP8266 encontradas no mercado: Família ESP8266 .

Os 2 módulos mais importantes são sem dúvida, a ESP-01 e o ESP-12E.

O ESP-01 têm sido amplamente utilizado em projetos da Internet das coisas onde tamanho e custo, mas não número de GPIOs (há apenas 2 do tipo digital disponíveis) são importantes. Exploramos este módulo em vários outros tutoriais aqui no Blog:

O ESP8266 – Parte 1Parte 2Parte 3

O ESP-12E Development Board (NodeMCU DevKit 1.0)

Para aumentar ainda mais a capacidade de utilização do módulo ESP-12E, foram adicionados regulação de potência e conectividade USB. O ESP-12E inclui:

  • Adaptador USB para UART: Silicon Labs CP2102,
  • NCP1117 3,3VDC Voltage Regulator,
  • Conector micro-USB,
  • Pinos adicionais com GND, Vin, 3,3VDC para facilitar o acesso durante o desenvolvimento.

Em suma, o NodeMCU ESP-12E é um dispositivo pronto para ser usado, bastando que você instale os drivers USB ao seu computador e comece a escrever programas que se conectam à sua rede Wi-Fi !

Especificações Técnicas:

  • Support STA/AP/STA+AP 3 working modes;
  • Built-in TCP/IP protocol stack, support multiple-channel TCP Client connection (max 5);
  • 0~D8, SD1~SD3: used for GPIO, PWM (D1-D8), IIC, ect; the driven ability can be arrived at 15mA;
  • AD0: one-way 10 bits ADC;
  • Power input: 4.5V~9V(10VMAX), support USB powered and USB debug;
  • Working current: ≈70mA(200mA MAX, continue), standby<200uA;
  • Transmission data rate: 110-460800bps;
  • Support UART/GPIO data communication interface;
  • Support update firmware remotely (OTA);
  • Support Smart Link;
  • Working temperature:-40℃~+125℃;
  • Driven mode: double large-power H bridge driven
  • Weight: 7g.

Picture of The NodeMCU

Um excelente site para se aprender mais sobre a família do ESP 8266 é: What Is The ESP8266 And Why Is It So Popular?

3: Instalando o NodeMCU ao IDE do Arduino

Se você deseja programar e usar o NodeMCU como se fosse um Arduino, a boa notícia é que é possível escrever-se firmwares personalizados e carregá-los no chip (“flash-it”). É importante lembrar que qualquer novo “firmware personalizado” irá substituir qualquer coisa previamente armazenada na memória flash do chip, incluindo o firmware original carregado em fábrica (aquele que aceita os comandos AT). Embora possamos usar o SDK do fabricante para o desenvolvimento de firmwares personalizados, é muito mais fácil usar o bom e velho Arduino IDE.

Comecemos:

No Arduino IDE,  abra a janela de preferências e digite a URL (marcado em vermelho na foto abaixo) no campo Additional Boards Manager URLs e selecione OK.

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Picture of Installing the NodeMCU board at Arduino IDE

  • Selecione MENU  Tools → Board → Boards Manager…e vá rolando até encontrar a opção: esp8266 by ESP8266 Community , a qual deverá ser o último item da lista e clique INSTALL

arduino_ide_boards_esp8266

  • Instalando USB Drivers: O USB to Serial UART module incluído no dispositivo, é o  Silicon Labs’ CP2102, para o qual deveremos instalar o driver Virtual COM Port (VCP). No caso de meu MAC, o arquivo criado para comunicar com o CP2102 foi:  /dev/cu.SLAB_USBtoUART. Você pode encontrar o driver apropriado ao seu computador no seguinte link: CP210x USB to UART Bridge VCP Drivers
  • Depois de restartar o Arduino IDE , poderemos selecionar a placa no menu: Option Tools → Board → NodeMCU 1.0 (ESP-12E Module). Em seguida, especificar a correta frequência de operação da CPU: (Tools → CPU Frequency: “” → 80MHz) e velocidade de comunicação (Tools → Upload Speed: “” → 115,200). Finalmente, selecionar o port apropriado ao seu computador: (Tools → Port → /dev/cu.SLAB_USBtoUART).

nodemcu-ide-setup-3

Neste ponto estamos prontos para escrever nosso próprio firmware e enviá-lo ao dispositivo, mas vamos primeiramente tentar um dos exemplos incluídos com a biblioteca: File → Examples → ESP8266WiFi → WiFiScan. Após o upload, podemos abrir a janela do Serial Monitor e observar os resultados. Verifique que 115,200 baud é a velocidade selecionada no menu do canto inferior direito do Serial Monitor.

nodemcu-ide-setup-4

4: Piscando (“Blinking”) o LED

FullSizeRender 24.jpg

O “Olá Mundo” de qualquer novo projeto de HW é sem dúvida alguma, um LED piscando. Para conectar-se um LED em seu ESP-12E, você poderá usar qualquer um dos seus GPIO digitais.

Picture of Blinking a LED

O diagrama de pinos acima mostra o layout da 2ª geração do NodeMCU ESP8266 . Em nosso caso, usaremos o pino D7 ou seu equivalente para Arduino: GPIO13.

blink.png

Você poderá testar o código usando tanto “D7” quanto “13”. ambas formas funcionarão. O pino D7 não precisa de um resistor externo para alimentar o LED, pois possui um internamente. Abaixo um código simples para piscar o LED:

/**********************************************
  Blink
  Connected to pin D7 (GPIO13) ESP8266 NODEMCU
 **********************************************/

#define ledPin 13
// #define ledPin D7

void setup() 
{
  pinMode(ledPin, OUTPUT);
}

void loop() 
{
  digitalWrite(ledPin, HIGH);   
  delay(1000);              
  digitalWrite(ledPin, LOW);    
  delay(1000);             
}

Há uma relação entre alguns dos pinos do NodeMCU e do Arduino, automaticamante identificados pelo IDE, tal como descrito abaixo:

  • ESP ==> Arduino
  • D0 ==> 16
  • D1 ==> 5
  • D2 ==> 4
  • D3 ==> 0
  • D4 ==> 2
  • D5 ==> 14
  • D6 ==> 12
  • D7 ==> 13
  • D8 ==> 15
  • D9 ==> 3
  • D10 ==> 1

Abaixo o código pronto para ser executado no IDE do Arduino:

5: Instalando o display de 0.96″ (OLED)

FullSizeRender 25.jpg

Um grande companheiro para o nosso ESP-12E é o pequeno display do tipo OLED: SSD 1306. Ele pode ser muito útil em projetos tanto para mostrar seus dados ou mensagens, quanto para depurar seus programas em campo. O modelo que usei aqui é um display de 128 x 64 pixels que se comunica via I2C, o SSD 1306, cujas principais características são:

  • 128 pixels na horizontal por 64 pixels na vertical. Assim se você usar por exemplo, caracteres de  8×8 pixels, obteremos um display de “16X8” (8 linhas de 16 caracteres cada).
  • Comunicação via I2C : se deve conectar ao NodeMCU I2C pins, usando:
    • SCL ==> D1 (equ. Arduino 5)
    • SDA ==> D2 (equ. Arduino 4)

Outra característica importante do SSD1306 é que você deve alimentar-lo com 3.3V, pelo que é possível conectar-lo diretamente ao módulo nodeMCU, como mostra o diagrama eléctrico abaixo:

Picture of Using the 0.96

Uma vez conectado o display, baixar e instalar a sua biblioteca em nosso Arduino IDE. Nós usaremos a versão ACROBOT abaixo:

SSD1306 Arduino Library

Depois de ter reiniciado o IDE, a biblioteca deverá estar instalada. Carreguemos o sketch abaixo para testar o display OLED:

/***********************************************************************
*  NodeMCU and OLED display "Hello World"
*  Based on original code developed by: Makerbro at  https://acrobotic.com/

*  MJRoBot 12Oct16
************************************************************************/

#include 
#include 

void setup()
{
  Wire.begin();  
  oled.init();                      // Initialze SSD1306 OLED display
  oled.clearDisplay();              // Clear screen
  oled.setTextXY(0,0);              // Set cursor position, start of line 0
  oled.putString("  MJRoBot.org");
  oled.setTextXY(4,0);              // Set cursor position, start of line 4
  oled.putString("  HELLO, WORLD");
}

void loop()
{
}

Observe que quando você não definir um tamanho em pixels para os caracteres de texto, o padrão será 8X8. Para se definir um tamanho diferente, por exemplo,5X7, você poderá utilizar: oled.setFont(font5x7);

Abaixo o código para o “Hello World”:

6: O NodeMCU e o DHT22 como uma estação climática

57fe5c32937ddb5b790007f3.jpegPicture of NodeMCU as a local weather station using DHT22

Um dos sensores mais utilizados normalmente para se captar dados meteorológicos é o DHT22 (ou seu irmão, o  DHT11), um sensor digital de humidade relativa do ar e temperatura. Ele usa internamente um sensor capacitivo de humidade e um termistor para medir o ar circundante, gerando um sinal digital em sua saída de dados. 

De acordo com a sua folha de dados (Datasheet), o sensor deve ser alimentado entre 3.3V e 5V (algumas especificações falam em até 6V max). Ele trabalha a partir de -40  a + 80 graus centígraods (algumas especs falam em + 125 ° C) com uma precisão de +/- 0,5 ° C de temperatura e +/-2% de umidade relativa. É importante ter em mente que o seu (“sencing period”) é em média de dois segundo (tempo mínimo entre leituras).

O site da Adafruit fornece uma série de informações sobre ambos, DHT22 e seu irmão DHT11. Para mais detalhes, visite a página:  Tutorial DHT22 / 11 .

O DHT22 tem 4 pinos (de frente para o sensor, o pino 1 é o mais esquerda):

  1. VCC (3 a 5V)
  2. saída de dados
  3. Não conectado
  4. GND (Terra)

OLED_DHT22.png

Uma vez que normalmente você usará o sensor em distâncias inferiores a 20m, um resistor de 10K deve ser conectado entre os pinos de dados e o VCC. O pino de saída deve ser conectado ao pino D3 do ESP-12E (veja o diagrama acima).

Uma vez que o sensor esteja instalado fisicamente no NodeMCU, baixe a biblioteca DHT a partir do repositório de programas:  Adafruit github e a instale junto as outras  bibliotecas de seu IDE (ambiente de desenvolvimento de programas do Arduino).

Uma vez que você recarregue o IDE, a biblioteca para o  sensor de DHT deverá aparecer como instalada. Execute o código abaixo para verificar se tudo está funcionando OK:

7: Instalando sensores analógicos

57fe81bf50e1b6b8d60002fc.jpeg

O NodeMCU possui um Analog Digital Converter (ADC) de 10 bits integrado. Quando comparado com um Arduino UNO que tem 6 ADCs “portas” ou o Nano que tem 8, parece que o NodeMCU sai perdendo aqui. Mas isto não é necessariamente verdade. Primeiro, os Arduinos UNO ou Nano em realidade, possuem apenas um ADC integrado internamente o qual deve ser multiplexado para se poder ler suas múltiplas entradas analógicas. Então, podemos fazer exatamente o mesmo com o NodeMCU.

Por exemplo, suponha que você necessite ler 2 sensores analógicos:

  • LDR: para se medir a intencidade da luz
  • Higrômetro (Soil Moisture) para se medir a umidade do solo

O que devemos fazer é “alimentar” individualmente cada um desses sensores cada vez que precisemos ler um deles. Para isso você deve definir para cada sensor, uma das GPIOs digitais como saída, colocando-as em nível ALTO em cada leitura. Para saber mais sobre múltiplas entradas analógicas, consulte o site:

Multiple analog inputs using one analoge pin

Higrômetro:

Picture of Using Analog sensors No Projeto ArduFarmBot, exploramos como trabalhar com um higrômetro para medir a umidade do solo. Lá, desenvolvemos um sensor do tipo DIY (“Do it yourself” – “Faça você mesmo”), mas aqui vamos usar um eletrônico, muito comum no mercado: o par sensor YL-69 e o comparador LM393.

O módulo LM393 possui 2 saídas, uma digital (D0), que pode ser configurada usando-se o potenciômetro que existe integrado ao módulo e um analógico (A0). Este módulo pode ser alimantado com 3.3V, o que é muito conveniente quando se trabalha com um NodeMCU. O que vamos fazer, é instalar os 4 pinos do LM393  como descrito abaixo:

  • LM393 A0:    to NodeMCU A0 input
  • LM393 VCC: to NodeMCU VCC or to NodeMCU GPIO D3*
  • LM393 GND: to NodeMCU GND
  • LM393 D0:    Não conectado

NodeMCU WS V2.png

Note que no diagrama acima, um sensor de humidade equivalente de 3 pinos  foi usada apenas para referência (não encontrou o módulo apropriado para desenha-lo com o Fritzing). Também é importante notar que o o “VCC do Sensor” deve ser conectado a um pino digital como saída, de modo que o LM393 seja alimentado apenas quando precisemos fazer uma leitura. Isto é importante não só para poupar energia, mas também para proteger as sondas de corrosão.

* Eu deixei aqui as 2 opções para alimentar sensor, porque pelo menos no caso de meu sensor e módulo, percebi que o NodeMCU não carregava o sketch com o D3 conectado. Também tive eventuais erros devido ao consumo de energia. Se você alimentar o LM393 diretamente ao 3.3V, o código não precisa ser alterado.

Uma rotina simples pode ser escrita para se ler a porta analógica:

/***************************************************
 * Get Soil Moister Sensor data
 **************************************************/
void getSoilMoisterData(void)
{
  soilMoister = 0;
  digitalWrite (soilMoisterVcc, HIGH);
  delay (500);
  int N = 3;
  for(int i = 0; i < N; i++) // read sensor "N" times and get the average
  {
    soilMoister += analogRead(soilMoisterPin);   
    delay(150);
  }
  digitalWrite (soilMoisterVcc, LOW);
  soilMoister = soilMoister/N; 
  Serial.println(soilMoister);
  soilMoister = map(soilMoister, 380, 0, 0, 100); 
}

Alguns comentários sobre a rotina acima:

  • Os dados do sensor é capturado 3 vezes, tirando-se uma média das mesmas.
  • Usamos MAP para configurar o intervalo em percentagem. Para definir os valores extremos, procedemos como abaixo:
    • Fazer um “curto-circuito” nas pontas de prova do higrômetro, isso equivalente a “100% de umidade”, o que gerará um valor de cerca de 0 na saída do ADC
    • Colocar o higrômetro”no ar”, se observa que o valor exibido no Serial Monitor é em torno de 380.

Abaixo o código completo para esta fase do projeto:

8: Subindo os dados para a nuvem: ThinkSpeak.com

Até agora, utilizamos o NodeMCU ESP12-E como se fosse uma placa normal de Arduino. É claro que apenas “tocamos” o verdadeiro potencial deste espectacular chip e agora é a hora de decolarmos rumo as estrelas! Quer dizer, para a nuvem! 😉

Picture of Uploading the sensor data to ThinkSpeak.comComecemos!

  • Primeiro você deve abrir uma conta no ThinkSpeak.com
  • Siga as instruções para criar um canal e tome nota do seu Channel ID e Write API Key
  • Atualize o código abaixo com as credenciais de sua rede Wi-Fi e Thinkspeak
  • Execute o programa no IDE

Comentemos as partes mais importantes do código:

Primeiro:

  • Incorporamos a biblioteca do ESP8266,
  • Definimos o cliente WiFi
  • Entramos com as credenciais do roteador WiFi e do Thingspeak :
/* ESP12-E & Thinkspeak*/
#include 
WiFiClient client;
const char* MY_SSID = "YOUR SSD ID HERE";
const char* MY_PWD = "YOUR PASSWORD HERE";
const char* TS_SERVER = "api.thingspeak.com";
String TS_API_KEY ="YOUR CHANNEL WRITE API KEY";

Segundo, incluamos uma nova biblioteca, muito importante em projetos de IoT: SimpleTimer.h:

/* TIMER */
#include 
SimpleTimer timer;

Em terceiro lugar, durante o setup(), iniciar a comunicação serial, connectar o WiFi atarvés da função connectWiFi() e definir os temporizadores. Note-se que a linha de código: timer.setInterval (19000L, sendDataTS);  chamará a função functionsendDataTS () a cada 19 segundos, a fim de transferir dados para o canal do ThinkSpeak.

void setup() 
{
  ...
  Serial.begin(115200);
  delay(10);
  ...
  connectWifi();
  timer.setInterval(19000L, sendDataTS);
  ...
}

Por último mas não menos importante, durante o loop (), a única ação necessária é iniciar o temporizador e pronto!

void loop() 
{
  ...
  timer.run(); // Initiates SimpleTimer
}

Abaixo em detalhes, as duas funções utilizadas para se comunicar com o ThinkSpeak.com:

ESP12-E conectando-se com a rede WiFi:

/***************************************************
 * Connecting WiFi
 **************************************************/
void connectWifi()
{
  Serial.print("Connecting to "+ *MY_SSID);
  WiFi.begin(MY_SSID, MY_PWD);
  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected");
  Serial.println("");  
}

ESP12-E enviando dados ao ThinkSpeak:

/***************************************************
 * Sending Data to Thinkspeak Channel
 **************************************************/
void sendDataTS(void)
{
   if (client.connect(TS_SERVER, 80)) 
   { 
     String postStr = TS_API_KEY;
     postStr += "&field1=";
     postStr += String(temp);
     postStr += "&field2=";
     postStr += String(hum);
     postStr += "&field3=";
     postStr += String(soilMoister);
     postStr += "\r\n\r\n";
   
     client.print("POST /update HTTP/1.1\n");
     client.print("Host: api.thingspeak.com\n");
     client.print("Connection: close\n");
     client.print("X-THINGSPEAKAPIKEY: " + TS_API_KEY + "\n");
     client.print("Content-Type: application/x-www-form-urlencoded\n");
     client.print("Content-Length: ");
     client.print(postStr.length());
     client.print("\n\n");
     client.print(postStr);
     delay(1000); 
   }
   sent++;
   client.stop();
}

9: Introduzindo o BLYNK

 Mas o que é BLYNK? Pelo Blynk website podemos entender que :

Blynk é uma plataforma baseada em Apps para iOS e Android, utilizada para controlar-se Arduino, Raspberry Pi e outros dispositivos através da Internet. É um painel digital (“Dashboard”) onde você pode construir uma interface gráfica para o seu projeto, simplesmente arrastando e soltando “widgets”.

É realmente muito fácil desenvolver projectos de IoT utilizando-se o BLYNK.

Para começar, aprendamos a controlar nosso velho LED conectado a porta D7, utilisando-se de um smartphone. Para isso, sigamos os passos abaixo:

  • Baixar o BLYNK app para o iPhone da Apple ou o Google Android
  • Instale a Biblioteca do BLYNK para Arduino. Note que você vai baixar um arquivo zip (Há 5 arquivos lá que você deverá instalar manualmente em sua Biblioteca do Arduino).
  • Uma vez que o Arduino IDE é recarregado, abra o sketch: Exemples ==> Blynk ==> Boards_WiFi ==> ESP8266_Standalone
#define BLYNK_PRINT Serial    // Comment this out to disable prints and save space
#include 
#include 

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
char auth[] = "YourAuthToken";

// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "YourNetworkName";
char pass[] = "YourPassword";

void setup()
{
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
}

void loop()
{
  Blynk.run();
}

O código acima é basicamente tudo o que você precisa instalar em seu ESP12-E para executar seus projetos de IoT com o BLYNK. Não se esqueça de alterar a velocidade de comunicação para 115.200 bauds e entrar com as credenciais de seu roteador local e de seu projeto no BLYNK.

Agora, vamos abrir o aplicativo “Blynk”  no smartphone:

blynk_led_ctrl

  1. Clique na tela Create New Project
  2. De um nome para seu projeto (Por exemplo: “LED Control”)
  3. Selecione o HW apropriado: NodeMCU
  4. Anote seu código do Authorization Token (você pode enviar-lo por e-mail e copiar-lo no código anterior:
    char auth[] = "YourAuthToken";
  5. Pressione OK. Uma tela vazia cheia de pontos irá aparecer.
  6. Clique na tela para abrir o Widget Box
  7. Adicione o Button widget.
  8. Clique no ícone do botão para abrir o Widget Settings. A principal coisa a definir é o PIN number (Como nosso LED está conectado ao pino D7 – escolha pin D7). É bom dar um nome para o widget: “LED”. Pressione OK
  9. Caso ainda não o tenha feito, volte para o seu código e atualize-o com o código de autorização que você obteve no aplicativo: char auth[] = “YourAuthToken”;

Uma vez que o código seja carregado em seu ESP12-E e o programa seja executado, o Serial Monitor deve apresentar algo semelhante ao meu abaixo:

blynk-serial-monitor

Agora basta executar o projecto Blynk (use o botão “play” no canto superior direito do aplicativo em seu smartphone) e isto é tudo! Você estará controlando seu HW através da Internet !!!!! 😉

Blynk_LED.png

10: Running our Weather Station on Blynk

Podemos também enviar os dados que estamos coletando de sensores para o Blynk, da mesma maneira que fizemos com o ThingSpeak.

Criemos um novo projeto Blink em nosso aplicativo (ou atualizar o que já foi criado):

Blynk 2.png

  1. Clique na tela e crie  3 novos Widgets. Em nosso caso: “Gauge”
  2. Para cada um desses widgets, defina: name, input type, range e frequency para atualização dos dados

Para os nossos sensores, definamos os seguintes “Virtual Ports”:

  • V10: Temperature, range 0-50oC, 5s of pooling frequency
  • V11: Humidity, range 0-100%, 5 s
  • V12: Soil Moisture, range 0-100%, 5 s

Em nosso código para o ESP12-E teremos de introduzir os valores acima. Comecemos a partir do código anterior e adicionemos os novos sensores e timer. A linha abaixo de código incluída no setup (), da mesma maneira que vimos com a versão Thinkspeak, forçará o programa para executar a função sendUptime () a cada 5 segundos.

  timer.setInterval(5000L, sendUptime);

Abaixo a função completa:

/***************************************************
 * Send DHT data to Blynk
 **************************************************/
void sendUptime()
{
  // You can send any value at any time.
  // Please don't send more that 10 values per second.
  Blynk.virtualWrite(10, temp); //virtual pin V10
  Blynk.virtualWrite(11, hum); // virtual pin V11
  Blynk.virtualWrite(12, soilMoister); // virtual pin V12
}

E, Voilá! O seu aplicativo Blynk estará recebendo informações atualizadas a respeito de seus sensores e você também poderá controlar o LED pela internet.

FullSizeRender 27.jpg

Abaixo o código completo para nosso Blynk Weather Station Control app:

11: Utilizando-se o  Blynk e o ThinkSpeak simultaneamente

Picture of Running Blynk and ThinkSpeak simustanealy

É claro que podemos ter nossos dados em ambas plataformas:

  • Blynk app (que é ótimo para o controle e mobilidade) e
  • ThinkSpeak que é muito bom para armazenamento de histórico e análise de dados.

NOTA: O Blynk possui um Widget: Webhook, onde voce pode comandar o envio automático dos dados ao ThinkSpeak sem necessidade de código adicional. O problema é que este widget só está disponível para Android. Assim fica aqui o código para fazer-lo por SW.

O arquivo abaixo mostra o código completo:

Conclusão

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

Robô explorador de labirintos, utilizando Inteligência Artificial com Arduino

Esta é a segunda e última parte de um projeto mais complexo, que explora a potencialidade de um robô seguidor de linha. Nesta etapa, aplicaremos conceitos de inteligência artificial na exploração de labirintos, implementando algoritmos que nos ajudarão a encontrar o caminho da saída mais curto e rápido.

Este projeto foi desenvolvido a partir de meu último tutorial: “Rex”, um robô que nunca perde a linha! Depois de se conseguir desenvolver um robô com capacidade para seguir linhas, o próximo passo natural é sem dúvida, dar-lhe algum grau de inteligência. Assim, nessa etapa, o nosso querido “Rex, o Robô” tentará encontrar uma forma de escapar de um “labirinto” tomando o caminho mais curto e o mais rápido possível.
A propósito, Rex odeia o Minotauro….. 😉

A maioria dos labirintos, por mais complexa sua concepção possa parecer, foram essencialmente formados a partir de uma parede contínua com muitos cruzamentos e bifurcações. Se a parede circundante do objectivo final de um labirinto está ligado ao perímetro do labirinto na entrada, o labirinto sempre poderá ser resolvido mantendo-se uma mão em contacto com a parede, não importando os muitos desvios que possam existir. Estes labirintos “simples” são conhecido como “Simply-connected” ou “perfeitos”, ou em outras palavras, que não contêm loops.

Voltando ao nosso projeto, ele será dividido em duas partes:

1. (Primeira passada): O robô encontrará o seu caminho para sair de um labirinto perfeito desconhecido. Não importa onde você o colocar dentro do labirinto, ele sempre encontrará uma “solução de saída”.

2. (Segunda passada): Uma vez que o robô encontrou uma possível solução para sair do labirinto, ele deve otimizar-la, encontrando o caminho mais curto para ir do início ao fim.

O vídeo abaixo, mostra um exemplo do robô encontrando seu caminho para sair do labirinto (chegar “ao final”). Na primeira vez que o robô explora o labirinto, é claro que vai perder muito tempo “pensando” sobre o que fazer em todo e qualquer cruzamento. Para testar as possibilidades, ele tomará vários caminhos errados e becos sem saída, o que faz com que ele escolha caminhos mais longos e execute várias  “marcha-rés” desnecessárias.

Durante esta”1ª passada”, o robô irá acumulando experiências, “tomando notas” sobre os diferentes cruzamentos e eliminando os atalhos ruins. Em sua “segunda passada”, o robô irá direta e rapidamente ao final do labirinto sem qualquer erro ou dúvida. Ao longo deste tutorial,  exploraremos em detalhes como fazê-lo:

LISTA DE MATERIAIS:

A lista de materiais é basicamente a mesmo que usamos com robô seguidor de linha, exceto que foram incluídos 2 Sensores adicionais para uma melhor precisão na detecção da cruzamentos a esquerda e a direita. O preço final do robô, continua barato (cerca de US$85,00):

  • Corpo (pode ser adaptado para as suas necessidades):
    • 2 quadrados de madeira (80x80mm)
    • 3 Grampos de papel
    • 2 rodas de madeira (diâmetro: 50 mm)
    • 1 roda “solta” (Caster)
    • 9 Elásticos
    • Fita 3M “Command”
    • Articulações plásticas para fixação do sensor
  • Protoboard e fiação
  • 2 conjuntos de baterias (4XNi-metal hidreto) –  5V cada conjunto
  • 2 Servos de Rotação Contínua (SM-S4303R)
  • Arduino Nano
  • Módulo Bluetooth  HC-06
  • 5 sensores x Linha (TCRT5000 4CH Infrared Linha Pista Seguidor Módulo Sensor + 1 sensor de Pista independente)
  • 2 sensores ZX03 (baseado no TCRT5000) Reflective Infrared Sensors (saída analógica)
  • 1 LED
  • 1 Botão

ALTERAÇÕES AO CORPO DO ROBÔ

Retirar o conjunto original dos 5 Sensores e fixar os novos sensores reflectivos aos extremos esquerdo e direito da barra de suporte plástica.

É aconselhável manter os 7 sensores o mais alinhado possível.

Instalação e testes dos novos sensores

A nova matriz de 7 sensores, é montado de forma a que os 5 originais sejam utilizados exclusivamente para o controlo PID e detecção da “linha completa” (explicado mais adiante) e que os novos 2 sensores somente para a detecção dos cruzamentos a esquerda e a direita.

Como uma rápida revisão, vamos lembrar como os 5 sensores originais “digitais” trabalham:

Se um sensor está centrado em relação à linha preta, este irá produzir um sinal HIGH. Por outro lado, o espaço entre os sensores devem ser calculados de tal modo a permitir que dois sensores possam cobrir a largura total da linha preta produzindo assim um sinal HIGH simultaneamente em ambos os sensores.

Como os 2 novos sensores “analógicos” trabalham:

Se um dos sensores está centrado em relação à linha preta, o resultado observado na saída do ADC interno do Arduino será geralmente menor que “100” (lembre-se que o ADC produz uma saída que vai de 0 a 1023). Sobre superfícies mais claras, o valor de saída deverá ser maior (500 a 600 sobre papel branco, por exemplo). Estes valores devem ser testado em diferentes situações de luz de superfície e de materiais para se definir a constante LIMIAR (“THRESHOLD”) correta a ser usada (ver o quadro aqui).

Abaixo, o diagrama mostrando os componentes conectados ao Arduino:

Maze Solver Circuit

Olhando o código do Arduino, cada um dos sensores será definido com um nome específico:

// LFSensor more to the Left is "0"
const int lineFollowSensor0 = 12; //Using Digital input
const int lineFollowSensor1 = 18; //Using Analog Pin A4 as Digital input
const int lineFollowSensor2 = 17; //Using Analog Pin A3 as Digital input
const int lineFollowSensor3 = 16; //Using Analog Pin A2 as Digital input
const int lineFollowSensor4 = 19; //Using Analog Pin A5 as Digital input

const int farRightSensorPin = 0; //Analog Pin A0
const int farLeftSensorPin = 1; //Analog Pin A1

 

Recordando, as possíveis combinações de saída para a matriz de 5 sensores original do seguidor de linha são:

  • 0 0 0 0 1
  • 0 0 0 1 1
  • 0 0 0 1 0
  • 0 0 1 1 0
  • 0 0 1 0 0
  • 0 1 1 0 0
  • 0 1 0 0 0
  • 1 1 0 0 0
  • 1 0 0 0 0

Com a adição dos 2 novos sensores, as suas saídas possíveis são:

  • Sensor Esquerdo: Saída Analógica maior ou menor do que o valor definido de THRESHOLD
  • Sensor Direito: Saída Analógica maior ou menor do queo valor definido de THRESHOLD

A fim de armazenar os valores de cada um dos sensores uma variável tipo matriz  (Array) é criada para os sensores digitais originais 5:

int LFSensor[5]={0, 0, 0, 0, 0};

E duas variáveis do tipo inteiras para os 2 novos sensores analógicos:

int farRightSensor = 0;
int farLeftSensor = 0;

As variáveis serão constantemente actualizadas dependendo do estado de cada um dos sensores:

LFSensor[0] = digitalRead(lineFollowSensor0);
 LFSensor[1] = digitalRead(lineFollowSensor1);
 LFSensor[2] = digitalRead(lineFollowSensor2);
 LFSensor[3] = digitalRead(lineFollowSensor3);
 LFSensor[4] = digitalRead(lineFollowSensor4);
 
 farRightSensor = analogRead(farRightSensorPin);
 farLeftSensor = analogRead(farLeftSensorPin);

Possuindo 5 sensores, como se viu no projeto do Robô seguidor de linha, se permite a geração de uma “variável de erro” que ajudará a controlar a posição do robô sobre a linha. Essa variável de erro será mantida e uma nova denominada “mode” será incluída para saber se o robô está seguindo uma linha, sobre uma linha contínua, uma intersecção ou fora da linha.

Esta variável “mode” será usada também com os novos sensores de esquerda e direita. Consideremos que os novos sensores da esquerda e da direita geraram 3 estados possíveis: H (maior do que THRESHOLD), L (menor do que oTHRESHOLD) e X (irrelevante). Para as saídas digitais, manteremos “0”, “1” e também introduziremos o “X”:

Intersection

X 1  1  1  1  1 X ==> mode = CONT_LINE; error = 0;
H 0 X X X X L ==> mode = RIGHT_TURN; error = 0;  (Veja o exemplo na imagem acima)
L X X X X 0 H ==> mode = LEFT_TURN; error = 0;
X 0 0 0 0 0 X ==> mode = NO_LINE; error = 0;
H 0 0 0 0 1 H ==> mode = FOLLOWING_LINE; error = 4;
H 0 0 0 1 1 H ==> mode = FOLLOWING_LINE; error = 3;
H 0 0 0 1 0 H ==> mode = FOLLOWING_LINE; error = 2;
H 0 0 1 1 0 H ==> mode = FOLLOWING_LINE; error = 1;
H 0 0 1 0 0 H ==> mode = FOLLOWING_LINE; error = 0;
H 0 1 1 0 0 H ==> mode = FOLLOWING_LINE; error = -1;
H 0 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -2
H 1 1 0 0 0 H ==> mode = FOLLOWING_LINE; error = -3;
H 1 0 0 0 0 H ==> mode = FOLLOWING_LINE; error = -4;

Assim, a implementação da lógica acima na função:

void readLFSsensors()

irá retornar as variáveis “mode” e “error” que serão utilizados na lógica do programa.

É importante testar a lógica dos sensores antes de seguir com o projeto. A função de teste abaixo está incluída no código e poderá ser usado para fins de ensaio:

void testSensorLogic(void) 
{
    Serial.print (farLeftSensor);
    Serial.print (" <== LEFT RIGH==> ");
    Serial.print (farRightSensor);
    Serial.print (" mode: ");
    Serial.print (mode);
    Serial.print (" error:");
    Serial.println (error);
}

Resolvendo o labirinto – a regra da mão esquerda

Como discutido na introdução deste tutorial, a maioria dos labirintos são essencialmente formados a partir de uma parede contínua com muitos cruzamentos e desvios.

Pesquisando na Wikipedia, aprendemos que “o seguidor da parede” é o algorítmo mais conhecido para percorrer labirintos. É também conhecido como “regra da mão esquerda” ou a “regra da mão direita”. Se o labirinto é simplesmente conectado, isto é, todos as suas paredes são ligadas entre si, mantendo-se uma mão em contato com uma das paredes do labirinto é garantido que chegará a uma saída. Usaremos aqui a “Regra da mão esquerda”.

Em resumo, a regra da mão esquerda pode ser descrito como:

  • Coloque a mão esquerda na parede.
  • Comece a andar para a frente
  • Em cada cruzamento, e ao longo do labirinto, manter a sua mão esquerda tocando na parede à sua esquerda.
  • Eventualmente, você vai chegar ao final do labirinto. Você provavelmente não vai seguir o caminho mais curto e mais direto, mas você chegará lá.

Portanto, a chave aqui é identificar as intersecções, definindo que medidas tomar com base nas regras acima. Especificamente no nosso tipo de labirinto em 2D , podemos encontrar 8 tipos diferentes de intersecções :

Intersections types

Olhando a imagem acima, podemos perceber que as possíveis ações nos cruzamentos são:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda ou
    • Vá para a direita ou
    • Siga em frente ou
  2. Em um “T”:
    • Vá para a esquerda ou
    • Vá para a direita
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda ou
    • Siga em frente
  6. Em um a frente ou ou a direita:
    • Vá para a direita ou
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Aplicando-se  a “regra da mão esquerda” a lista acima, será reduzida a apenas uma opção para uma cada uma das possibilidades:

  1. Em um “cruzamento tipo cruz”
    • Vá para a esquerda
  2. Em um “T” (Transversal):
    • Vá para a esquerda
  3. Em um “virar somente a direita”:
    • Vá para a direita
  4. Em um “virar somente a esquerda”:
    • Vá para a esquerda
  5. Em um a frente ou esquerda:
    • Vá para a esquerda
  6. Em um a frente ou ou a direita:
    • Siga em frente
  7. Em um beco sem saída:
    • Volte
  8. No final do labirinto:
    • Pare

Estamos quase lá. Quando o robô atinge um beco sem saída é fácil identificá-lo, porque não existem situações ambíguas (já implementamos essa ação com o Robô seguidor de linha). O problema está quando o robô encontra uma “linha” por exemplo, pois a linha pode ser parte de um cruzamento tipo “cruz” (1) ou de um “T” (2) ou mesmo um “Final” (8). Além disso, quando o robô chega a um “virar à esquerda ou à direita”, esses cruzamentos podem ser os do tipo simples (opções 3 ou 4) ou opções que podem ir para a frente (5 ou 6). Para se descobrir exatamente em que tipo de intersecção está o robô, é necessário incorporar um passo adicional: o robô deve “dar um passinho a frente” ou seja rodar o que chamamos de “extra inch” e assim “ver” o que vem por adiante:

Maze4
Em termos de fluxo, todas as acções possíveis podem ser descritas como:

  • Em um beco sem saída:
    • Volte
  • Em uma linha:
    • Executar uma polegada extra
    • Se há uma linha:
      • É uma “cruz” ==> Ir para ESQUERDA
    • Se não houver nenhuma linha:
      • é um “T” ==> Ir para ESQUERDA
    • Se houver outra linha:
      • É o fim de Maze ==> PARAR
  • Em uma curva à direita:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a direita ==> ir direto
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a direita==> Ir para DIREITA
  • Em uma curva à esquerda:
    • Executar uma polegada extra
    • se há uma linha:
      • É ir a frente ou virar a esquerda ==> virar a esquerda
    • Se não houver nenhuma linha:
      • é um virar obrigatoriamente a esquerda==> Ir para ESQUERDA

Note-se que de facto, no caso de um “virar à esquerda”, você poderá pular o teste porque o robô tomará à esquerda de qualquer maneira. Deixei a explicação mais genérica somente para claridade. No código real ignorarei este teste.

IMG_3474

 

A foto ao lado , é de um labirinto bem simples que desenhei no chão do meu laboratório, usando fita isolante de 18mm (3/4) que uso para testes (ainda bem que minha mãe não viu!!!! ;-0:

Aplicando o algorítmo “Left Hand on the Wall” ao código do Arduino

Uma vez que já temos a função  readLFSsensors () modificada, para incluir os 2 sensores adicionais, podemos também re-escrever a função “Loop” introduzindo o algoritmo como descrito anteriormente:

void loop() 
{
     readLFSsensors(); 
     switch (mode)
     { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode == CONT_LINE) mazeEnd();
            else goAndTurn (LEFT, 90); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) goAndTurn (RIGHT, 90); 
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
     }
}

Algumas funções importantes aparecem aqui.

followingLine() é a mesma utilizada com o robô seguidor de linha, que quando se está apenas seguindo uma linha, deve-se: calcular o PID e controlar os motores, dependendo dos valores dos ganhos da malha de controle usando a função: motorPIDcontrol ();

runExtraInch (): vai empurrar o robô para a frente um pouquinho. Quanto o robô se moverá, dependerá do tempo que você usa na função delay(), antes que mande parar os motores.

void runExtraInch(void)
{
    motorPIDcontrol();
    delay(extraInch);
    motorStop();
}

goAndTurn (direction, angle): Esta função é importante, porque você na verdade não pode virar o robô, tão logo perceba o tipo de intersecção em que está. Lembre-se que projectamos um robô do tipo diferencial que, quando faz curvas, “gira em torno do seu eixo”. Assim, para sair de um cruzamento, girar 90 graus e continuar movendo-se sobre a linha, o centro das rodas deve obrigatoriamente estar alinhado com o centro da intersecção. Uma vez que a linha dos sensores está à frente do eixo das rodas, o robô deve mover-se para a frente para alinhá-los. Os moteres devem funcionar por um tempo “t” dependendo da distância entre a linha dos sensores e o eixo dos motores ( “d”), velocidade e tamanho das rodas. Esta constante de tempo “t” é  no código: adjGoAndTurn, que deve ser ajustada dependendo de seu projeto:

Turn_Explanation

void goAndTurn(int direction, int degrees)
{
    motorPIDcontrol();
    delay(adjGoAndTurn);
    motorTurn(direction, degrees);
}

Neste ponto, o robô já está “resolvendo um labirinto”!  Você acabou de terminar o “Primeiro Passo”. Não importa onde você começar dentro de um labirinto, você chegará ao final.

Abaixo, um vídeo mostrando um teste para esta fase do projeto:

Tomando nota do Caminho

Consideremos o exemplo abaixo:

Maze1

Partindo do ponto escolhido, o robô encontrará 15 Interseções antes de chegar ao final do labirinto:

  1. Esquerda (L)
  2. Back (B)
  3. Esquerda (L)
  4. Esquerda (L)
  5. Esquerda (L)
  6. Back (B)
  7. Reto (S)
  8. Back (B)
  9. Esquerda (L)
  10. Esquerda (L)
  11. Back (B)
  12. Reto (S)
  13. Esquerda (L)
  14. Esquerda (L)
  15. Fim

O que deve ser feito em qualquer um desses cruzamentos é “salvar a decisão tomada” na mesma sequência em que aconteça. Para isso, vamos criar uma nova variável (matriz) que irá armazenar o caminho que o robô tenha tomado:

A variável path[] e 2 índices variáveis, serão utilizados em conjunto para se gravar os passos:

char path[100] = "";
unsigned char pathLength = 0; // the length of the path
int pathIndex = 0; // used to reach an specific array element.

Voltando ao exemplo, uma vez percorrido todo o circuito, as variáveis ficariam:

path = [LBLLLBSBLLBSLL] e  pathLengh = 14

Simplificando (otimizando) o Caminho

Voltemos ao nosso exemplo. Olhando para o primeiro grupo de cruzamentos, percebemos que o primeiro ramo esquerdo é na verdade um “Dead End”, e assim, se o robô em vez de um “lateral-esquerdo-esquerdo” apenas tivesse seguido reto nesse primeiro cruzamento, uma grande quantidade de energia e tempo seriam salvas! Em outras palavras, uma sequência do tipo “LBL”, de facto, seria o mesmo que “S”.

Maze2

Isso é exatamente como o caminho completo pode ser otimizado. Se você analisar todas as possibilidades onde um “U turn” (back) é utilizado, o conjunto de 3 cruzamentos onde o”U-Turn” ( “B”) aparece  (“xBx”) poderá ser reduzido para apenas um.

A descrição acima é apenas um exemplo, abaixo podemos encontrar a lista completa de possibilidades:

LBR = B
LBS = R
RBL = B
SBL R =
SBS = B
LBL = S

Aplicando-se as substituições acima para o caminho completo de nosso exemplo, podemos reduzi-lo a:

path = [LBLLLBSBLLBSLL] ==> LBL = S

path = [SLLBSBLLBSLL] ==> LBS = R

path = [SLRBLLBSLL] ==> RBL = B

path = [SLBLBSLL] ==> LBL = S

path = [SSBSLL] ==> SBS = B

path = [SBLL] ==> SBL = R

path = [RL]

Olhando para o exemplo, é muito claro que se o robô gira para a DIREITA logo no primeiro cruzamento e depois disso, à esquerda, ele chegará ao final do labirinto pelo caminho mais curto!

Maze3

A primeira passada será consolidada na função mazeSolve (). Esta função é, de facto, a função loop () utilizada anteriormente, onde se incorporaram as etapas de armazenamento e otimização do caminho. Quando a primeira passada termina, a variavel path[] conterá o caminho já optimizado.

Uma nova variável é introduzida para sinalizar o final da “passada”:

unsigned int status = 0; // solving = 0; reach end = 1

Abaixo a função completa para a primeira etapa do programa:

void mazeSolve(void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      { 
         case NO_LINE: 
            motorStop();
            goAndTurn (LEFT, 180);
            recIntersection('B');
            break;
         case CONT_LINE: 
            runExtraInch();
            readLFSsensors();
            if (mode != CONT_LINE) {goAndTurn (LEFT, 90); recIntersection('L');} // or it is a "T" or "Cross"). In both cases, goes to LEFT
            else mazeEnd(); 
            break;
         case RIGHT_TURN: 
            runExtraInch();
            readLFSsensors();
            if (mode == NO_LINE) {goAndTurn (RIGHT, 90); recIntersection('R');}
            else recIntersection('S');
            break; 
         case LEFT_TURN: 
            goAndTurn (LEFT, 90); 
            recIntersection('L');
            break; 
         case FOLLOWING_LINE: 
            followingLine();
            break; 
 
       }
   }
}

Aqui uma nova função foi introduzida: recIntersection (direction)

Esta função será a usada para armazenar as decisões tomadas nos cruzamentos e também para chamar outra importante função: simplifyPath(), que irá otimizando “em tempo real” o grupo de 3 cruzamentos envolvendo “U-Turn”, como vimos anteriormente.

void recIntersection(char direction)
{
    path[pathLength] = direction; // Store the intersection in the path variable.
    pathLength ++;
    simplifyPath(); // Simplify the learned path.
}

O crédito para a criação da função simplifyPath () é de Patrick McCabe. Eu apenas a incluí ao meu código (para mais detalhes, visite patrickmccabemakes.com):

void simplifyPath()
{
   if(pathLength < 3 || path[pathLength-2] != 'B') // only simplify the path if the second-to-last turn was a 'B'
   return;
   int totalAngle = 0;
   int i;
   for(i=1;i<=3;i++)
   {
     switch(path[pathLength-i])
     {
        case 'R':
           totalAngle += 90;
           break;
        case 'L':
           totalAngle += 270;
           break;
        case 'B':
           totalAngle += 180;
           break;
      }
    }
    totalAngle = totalAngle % 360;  // Get the angle as a number between 0 and 360 degrees.
    switch(totalAngle) // Replace all of those turns with a single one.
    {
       case 0:
          path[pathLength - 3] = 'S';
          break;
       case 90:
          path[pathLength - 3] = 'R';
          break;
       case 180:
          path[pathLength - 3] = 'B';
          break;
       case 270:
          path[pathLength - 3] = 'L';
          break;
     }
     pathLength -= 2; // The path is now two steps shorter.
}

A segunda passada: resolvendo o labirinto o mais rápido possível!

O programa principal: loop () é bem simples:

void loop() 
{
    ledBlink(1);
    readLFSsensors(); 
    mazeSolve(); // First pass to solve the maze
    ledBlink(2); // end 1st pass
    
    while (digitalRead(buttonPin) && !mode) { }
    pathIndex = 0;
    status = 0;
    mazeOptimization(); //run the maze as fast as possible
    ledBlink(3); // end of 2nd pass. Maze solved!
}

Assim, quando a primeira passada termina, o que devemos fazer é apenas “alimentar” o robô com o caminho otimizado. Ele vai começar a percorrer o labirinto novamente e quando uma intersecção for encontrado, ele não mais tomará decisões, mas simplesmente seguirá o que está armazenado na variável path [].

Para a segunda passada usamos a função mazeOptimization(), que por sua vez, chama a função mazeTurn(path[]) que comandará os movimentos do robô nessa segunda passada:

void mazeOptimization (void)
{
   while (!status)
   {
      readLFSsensors(); 
      switch (mode)
      {
         case FOLLOWING_LINE:
            followingLine();
            break; 
         case CONT_LINE:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case LEFT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
         case RIGHT_TURN:
            if (pathIndex >= pathLength) mazeEnd (); 
            else {mazeTurn (path[pathIndex]); pathIndex++;}
            break; 
      } 
   } 
}
void mazeTurn (char dir) 
{
   switch(dir)
   {
      case 'L': // Turn Left
         goAndTurn (LEFT, 90); 
         break; 
      case 'R': // Turn Right
         goAndTurn (RIGHT, 90); 
         break; 
      case 'B': // Turn Back
         goAndTurn (RIGHT, 180); 
         break; 
      case 'S': // Go Straight
         runExtraInch(); 
         break;
    }
}

A segunda passada está feita!

O vídeo abaixo mostra o exemplo trabalhado aqui completo, onde Rex encontra seu caminho para livrar-se do Minotauro! 😉

O código Arduino completo para este projeto, poderá ser encontrado no link abaixo:

Codigo para o Arduino

Usando o aplicativo Android para o ajuste

A App Android desenvolvido para o projeto do Robô seguidor de Linha também pode ser usado aqui.  O código Arduino apresentado na última etapa já inclui comunicação com o dispositivo Android, mas se não quiser usar-lo não há problema, porque o código é “transparente”.

Eu usei bastante o dispositivo Android durante o projeto para enviar dados de teste do robô para o dispositivo, utilizando-se o campo de “Mensagem recebida”.

Diversas variáveis devem ser bem definidas, a fim de garantir que o robô gire corretamente. Os mais importantes estão abaixo (os marcados em negrito tive mudar-los várias vezes):

const int power = 250;
const int iniMotorPower = 250;
const int adj = 0;
float adjTurn = 8;
int extraInch = 200;
int adjGoAndTurn = 800;
const int THRESHOLD = 150;
float Kp=50;
float Ki=0;
float Kd=0;

CONCLUSÃO

Esta é a segunda e última parte de um projeto complexo, explorando a potencialidade de um robô seguidor de linha, onde aplicando-se conceitos de inteligência artificial se conseguiu explorar labirintos,  encontrando o caminho da saída mais curto e rápido.

Espero que esse trabalho possa contribuir para que outras pessoas possam aprender mais sobre eletrônica, robôs, Arduino, etc.

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

Saludos desde el sur del mundo! 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

“Rex”, um robô que nunca perde a linha!

O objetivo deste projeto é construir um robô seguidor de linha com controle PID. Também usaremos um dispositivo Android para poder configurar mais facilmente os principais parâmetros (ganhos) da malha de controle.

Introdução:

Esta é a primeira parte de um projeto mais complexo, explorando a potencialidade de um robô seguidor de linha. Construiremos um robô com controle PID e também aprenderemos como usar um dispositivo Android na configuração mais dos principais parâmetros (ganhos) de sua malha de controle. Na segunda parte deste projeto Robô explorador de labirintos, utilizando Inteligência Artificial com Arduino, aplicaremos conceitos de inteligência artificial para explorar labirintos,  encontrando o caminho da saída mais curto e rápido.

Abaixo um vídeo que mostra o robô seguindo um circuito básico:

Lista de Materiais:

A lista de materiais necessários é simples e o preço final do robô, muito barato (cerca de US$ 75,00):

  • Corpo (pode ser adaptado para as suas necessidades):
    • 2 quadrados de madeira (80x80mm)
    • 3 Grampos de papel
    • 2 rodas de madeira (diâmetro: 50 mm)
    • 1 roda “solta” (Caster)
    • 9 Elásticos
    • Fita 3M “Command”
    • Articulações plásticas para fixação do sensor
  • Protoboard e fiação
  • 2 conjuntos de baterias (4XNi-metal hidreto) –  5V cada conjunto
  • 2 Servos de Rotação Contínua (SM-S4303R)
  • Arduino Nano
  • Módulo Bluetooth  HC-06
  • 5 sensores x Linha (TCRT5000 4CH Infrared Linha Pista Seguidor Módulo Sensor + 1 sensor de Pista independente)
  • 1 LED
  • 1 Botão

Ajuste dos motores

Como motores, utilizei 2 servos em modo contínuo (SM-S4303R). Eles devem ser “colados”, formando um único e sólido bloco como você pode ver na foto (use a fita 3M Command, cola ou fita dupla face). Esses servos girarão a uma dada velocidade dependendo da largura do pulso que receba em sua entrada de dados. Para este servo específico, a largura do pulso varia de 1.0 ms a 2.0ms (outros tipos de servos podem trabalhar com larguras de pulso diferentes).

Arduino3

Olhando em detalhes:

  • Um pulso de 1.5ms comandará o servo para que fique em sua posição neutra, ou “parado”.
  • Um pulso de 1.0 ms comandará o servo para que funcione em sua velocidade máxima (cerca de 70 RPM) em uma direção e 2.0ms na direção oposta.
  • Pulsos entre 1.0 e 1.5ms ou 1.5ms e 2.0ms, farão os servos girarem em velocidades intermediárias e proporcionais.

Arduino4

 

A primeira coisa que se deve fazer, é enviar um pulso de 1.5ms (1500us) para verificar se os motores ficam realmente”parados”. Caso isto não ocorra, os servosdevem ser ajustados (procure o parafuso amarelo, na lateral do servo). É claro que se o seu servo não possui esse ajuste, tente alterar o valor “1500ms” até obter o ponto final.

Monte o Arduino e os servos como mostrado abaixo:

Motorflb5wsqin3edek6-large

O código abaixo, para o Arduino deve ser usado para o ajuste:

#include Servo // este código linha deve ser mudado, por favor, olhe o código anexado
Servo leftServo;
Servo rightServo;
void setup ()
{
   leftServo.attach (5);
   rightServo.attach (3);
   leftServo.writeMicroseconds (1500);
   rightServo.writeMicroseconds (1500);
}

void loop ()
{
}

Montagem do Corpo e motores para teste de movimento

fk1e3mein3ef60l-medium

 

1. Com a fita 3M Command, fixar os 2 Servos à uma das peças de madeira quadrado.

 

 

fd75kabin3ef63f-small2. Fixar o segundo quadrado de madeira ao anterior, utilizando os grampos de papel. Ajuste o comprimento da plataforma para suas necessidades.

fb16lqlin3ef65n-small

 

 

3. Fixe o caster utilizando-se o grampo de papel.

 

ftmc0s8in3ee81d-medium4. A alimentação para os motores será fornecida por um dos conjuntos de baterias (5V). Este conjunto de baterias será instalado entre o protoboard e a plataforma do corpo.

 

 

fog5h6ein3ee14w-small
5. Conecte a bateria a ser utilizada com os servos: deixe uma linha de alimentação lateral do protoboard exclusivamente para os servos

6. Conecte o Arduino Nano ao protoboard

7. Conecte os terras do Protoboard ao GND do Arduino.

8. Conecte os Servos ao Arduino: LEFT => Pin 5;   RIGHT => Pin 3

9. Ligue o LED ao Arduino Pino 13

10. Ligue o botão para Arduino Pin 9

 

Note que, devido a maneira com que os servos estão montados (em oposição) as faixa de variação de velocidade em função da largura de pulso são diferentes:

  • RIGHT Servo: velocidade de avanço frontal vai de 1500us (parado) a 2000us (velocidade máxima)
  • LEFT Servo: velocidade de avanço frontal vai de 1500us (parado) a 1000us (velocidade máxima)

Um LED externo é adicionar ao pin13, com finalidade de sinalização e testes (você pode usar o LED interno do Arduino se quiser, mas leve em consideração que será difícil de vê-lo no meio dos cabos).

Além disso, um botão é ligado ao pino 9. Este botão será muito útil para testes e para o comando de início, quando o robô estiver posicionado no início do circuito de linha.
Por exemplo:

while (digitalRead (buttonPin)) {}
    motorTurn (LEFT, 500);
    motorTurn (RIGHT, 500);

Note que as 2 linhas que irão comandar o robô para virar à esquerda, esperar 500ms e virar à direita, só serão executadas depois que você pressionar o botão (buttonPin = 0). Antes disso, o programa ficará executando um “loop infinito”.

O código Motor_Test.ino, disponível no final deste tutorial, poderá ser usado como uma base para um teste de motores mais completo (para frente, para trás, parada completa, virar à esquerda, virar à direita). Se necessário você deverá ajustar os atrasos (“delays”) para obter um ângulo de virada correto.. Também, dependendo de seus motores, os valores definidos para a largura de pulso devem ser um pouco diferentes para compensar qualquer falta de alinhamento entre os mesmos.

Instalando o módulo Bluetooth (opcional)

O módulo Bluetooth HC-06 deverá ser instalado no protoboard. A biblioteca SoftSerial do Arduino será utilizada.

Motor&amp;BTf4fe9iqin3eedct-medium

Abaixo as conexões de pino HC-06:

  • Tx Pin para Arduino pino 10 (Rx)
  • RX Pin para Arduino pino 11 (Tx)
  • VCC / GND para Arduino 5V / GND

 

O robô funcionará com ou sem o módulo Bluetooth. O código será construído de uma maneira que caso você não ative o BT, os parâmetros utilizados serão os padrões. Na última parte deste tutorial, vou explorar como usar um aplicativo Android para enviar dados para um melhor ajuste dos parâmetros do robô e mover-lo em modo manual. O uso da App Android é um opcional no caso que alguém deseje explorar mais o uso de um Robô seguidor de linha em competições, por exemplo.

Adicionando os sensores de linha


fqq0sfhin3ees26-large

1. Fixe os 5 sensores em uma barra de plástico, como mostrado nas fotos

 

 

fw9zenoin3eeqyx-large

2. É aconselhável para rotular os sensores para fins de teste. O nome sensores vai de de “0” (mais à esquerda) para “4” (mais à direita)

 

f9d55psin3eer0w-large

3. Passe os cabos sob o quadro, usando os elásticos para corrigi-los (tome cuidado para não misturar-se com as rodas ou Caster.

 

f5c3vfgin3eersy-large

4. Conecte os cabos de pinos do Arduino, como abaixo:

  • Sensor 0 = 12
  • Sensor 1 = 18
  • Sensor 2 = 17
  • Sensor 3 = 16
  • Sensor 4 = 19

fmhzshlin3ecx2e-large

 

5. Fixe o segundo conjunto de baterias 5V e conecte ao pino Vin do Arduino.

Line Follow full circuit

 

 

 

Em meu caso, estou usando um módulo com 4 sensores integrados + 1 extra. Todos são compatíveis. Para simplificar, no diagrama abaixo, I inclui 5 sensores individuais conectados juntos. O resultado final é o mesmo em ambas as configurações.

 

Implementando a Lógica dos Sensores de linha

IRsensorCada sensor é constituído por um LED e um fotodiodo, ambos infravermelhos. A luz emitida pelo LED atinge a superfície e é reflectida de volta para o fotodiodo. O fotodiodo em seguida, gera uma tensão de saída proporcional à reflectância da superfície.

Track SensorsNo caso dos sensores utilizados, um circuito integrado no módulo gera um sinal de saída digital simples (ALTO: escuro; BAIXO: Luz). Um potenciômetro instalado no módulo (ver foto) irá ajustar o nível correto de luz para ser considerada “dark” ou não. Quando a luz reflectida é conLine Follower Sensorsiderada “escura”, a saída fica ALTA ( “1”) e BAIXA ( “0”) para outra cor mais clara. Eu usei aqui um módulo integrado com 4 sensores e e módulo extra com um sensor único (forma diferente, mas mesma lógica). A combinação é uma matriz de 5 sensores que eu considero apropriado para este tipo de controle, como explicado abaixo.

Sensor1_Cover

A matriz de 5 sensores  é montada de forma que, se apenas um sensor está centrado em relação à linha preta, ele irá produzir um “1”.

 

Sensor1_&amp;2_Cover

Por outro lado, o espaço entre os sensores devem ser calculados para permitir que 2 sensores possam cobrir a largura total da linha preta simultaneamente,também produzindo assim um “1” em ambos os sensores (ver os quadros aqui).
As possíveis combinações de saída do conjunto de sensores são:

  • 0 0 0 0 1
  • 0 0 0 1 1
  • 0 0 0 1 0
  • 0 0 1 1 0
  • 0 0 1 0 0
  • 0 1 1 0 0
  • 0 1 0 0 0
  • 1 1 0 0 0
  • 1 0 0 0 0

Trabalhar com 5 sensores, permite a geração de uma “variável de erro” que ajudará a controlar a posição do robô sobre a linha, como mostrado abaixo:
Vamos considerar que a condição ideal é quando o robô está centrado, tendo a linha apenas abaixo do “sensor do meio” (Sensor 2). A saída da matriz será: 0 0 1 0 0  e nesta situação, o “erro” será “zero”.

Se o robô começa a derivar para a esquerda (a linha “parece mover-se” para a direita) o erro deverá aumentar e com um sinal positivo. Se o robô começar a mover-se para a direita (a linha” parece mover-se “para a esquerda), da mesma maneira, o erro tem de aumentar, mas agora com um sinal negativo.
A variável de erro, relacionada com o estado do sensor será:

  • 0 0 0 0 1 ==> erro = 4
  • 0 0 0 1 1 ==> erro = 3
  • 0 0 0 1 0 ==> erro = 2
  • 0 0 1 1 0 ==> erro = 1
  • 0 0 1 0 0 ==> erro = 0
  • 0 1 1 0 0 ==> erro = -1
  • 0 1 0 0 0 ==> erro = -2
  • 1 1 0 0 0 ==> erro = -3
  • 1 0 0 0 0 ==> erro = -4

Olhando o código Arduino, cada um dos sensores será definido com um nome específico (considere que o Sensor mais à esquerda deve ser atribuído com uma etiqueta de “0”):

const int lineFollowSensor0 = 12;
const int lineFollowSensor1 = 18;
const int lineFollowSensor2 = 17;
const int lineFollowSensor3 = 16;
const int lineFollowSensor4 = 19;

A fim de armazenar os valores de cada um dos sensores uma variável tipo Array será criada:

int LFSensor [5] = {0, 0, 0, 0, 0};

Cada posição da matriz irá ser constantemente actualizada com a saída de cada um dos sensores:

LFSensor [0] = digitalRead (lineFollowSensor0);
LFSensor [1] = digitalRead (lineFollowSensor1);
LFSensor [2] = digitalRead (lineFollowSensor2);
LFSensor [3] = digitalRead (lineFollowSensor3);
LFSensor [4] = digitalRead (lineFollowSensor4);

Uma vez armazenado o valor de cada um dos sensores, uma lógica deve ser implementada para gerar a variável de erro:

if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 1 )) erro = 4;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 1) && (LFSensor [4] == 1)) erro = 3;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 1) && (LFSensor [4] == 0)) erro = 2;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 1) && (LFSensor [3] == 1) && (LFSensor [4] == 0)) erro = 1;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 1) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = 0;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 1) && (LFSensor [2] == 1) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = - 1;
else if ((LFSensor [0] == 0) && (LFSensor [1] == 1) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -2;
else if ((LFSensor [0] == 1) && (LFSensor [1] == 1) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -3;
else if ((LFSensor [0] == 1) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) erro = -4;

Controlando a direção (Proporcional Control – P)

Perfeito! Neste ponto, o robô está montado e operacional. Você deve aproveitar e executar alguns testes básicos com os motores, ler a saída de sensores e testá-los através de uma linha. O que está faltando ainda, é o verdadeiro “cérebro”, ou seja fazer o Robô executar a tarefa de “andar na linha” por seus próprios meios. Vamos conseguir isso, através da implementação de uma lógica de controle que garantirá ao robô seguir uma linha, seja qual for o desenho.

PD Control
Suponha que o robô esteja “andando” sobre uma linha e a saída do Array de Sensores é: “0 0 1 0 0”.  Neste caso, o erro correspondente é “0” e ambos os motores estarão empurrando o robô para a frente com uma velocidade constante. Um código para essa condição seria:

rightServo.writeMicroseconds (1500 + iniMotorPower);
leftServo.writeMicroseconds (1500 - iniMotorPower);

Por exemplo, com iniMotorSpeed = 250,  significa que LEFT Servo receberá pulsos de 1,1250us e o RIGHT Servo 1750us, o que fará com que o robô avance com metade de sua velocidade máxima. Lembre-se que a velocidade de avanço do Servo direito irá variar com larguras de pulso que variam de 1500us (parado) a 2000us (velocidade máxima) e o Servo esquerdo de 1500us (parado) a 1000us (velocidade máxima).

Suponha agora que o robô comece a derivar para a esquerda ( a “linha vai para a direita”), fazendo com que o sensor 3 també, fique sobre a linha. Neste caso, a saída do Array de sensores será: “0 0 1 1 0” e o erro = 1. Nesta situação, o que necessitamos é virar o robô para a direita. Para fazer isso, devemos diminuir a velocidade do Servo direito o que significa diminuir a largura do pulso. Além disso, a velocidade do Servo esquerdo deve aumentar, o que significa diminuir a largura do pulso. Para isso, precisamos alterar a função de controle do motor:

rightServo.writeMicroseconds (1500 + iniMotorPower - erro);  // Erro positivo: velocidade diminue
leftServo.writeMicroseconds (1500 - iniMotorPower - erro);  //Erro positivo: velocidade aumenta

A lógica acima é correcta, mas é facil compreender que a adição ou subtracção de apenas “1” microssegundo na duração dos pulsos não irá gerar a correcção necessária, ao menos em um tempo realista. É intuitivo que o número a se adicionar ou subtrair deve ser maior, por exemplo, 50, 100, etc. Para conseguir isso, o “erro” deve ser multiplicado por uma constante “K”. Uma vez que a influência dessa constante será proporcional ao erro, vamos chamar-la “Constante proporcional: Kp. No meu caso, testei o robô com uma constante Kp=50 e ele funcionou muito bem.

A função final para os motores será:

int Kp = 50;
rightServo.writeMicroseconds (1500 + iniMotorPower - Kp * erro);
leftServo.writeMicroseconds (1500 - iniMotorPower - Kp * erro);

Podemos resumir o que acontecerá com os motores, como mostrado abaixo:

  • Array de Sensores: 0 0 1 0 0
    • erro = 0
    • Servo direito: pulso = 1750us
    • Servo esquerdo: pulso  = 1250us
    • (ambos os motores a mesma velocidade)
  • Array de Sensores: 0 0 1 1 0
    • erro = 1
    • Servo direito: pulso = 1700us (mais lento)
    • Servo esquerdo: pulso = 1200us (mais rápido)

Se a situação é o oposto e o robô vai para a direita, o erro seria “negativo” e a velocidade dos servos deve mudar:

  • Array de Sensores: 0 0 1 0 0
    • erro = 0
    • Servo direito: pulso = 1750us
    • Servo esquerdo: pulso = 1250us
    • (ambos os motores a mesma velocidade)
  • Array de Sensores: 0 1 1 0 0
    • erro = -1
    • Servo direito: pulso = 1800us (mais rápido)
    • Servo esquerdo: pulso = 1300us (mais lento)

Neste ponto fica claro que quanto mais o robô “escorregue” para um lado, maior será o erro (1, 2, 3 ou 4) e mais rápido ele deve retornar ao centro (valores a serem adicionados a largura do pulso: 50, 100, 150, 200). A velocidade com que o robô irá reagir ao erro será proporcional ao mesmo.

Controle PID

Controle PID (Proporcional, Derivativo e Integral) é um dos sistemas de controle mais comuns que existem. A maioria dos sistemas de controle industriais utilizam algum tipo do controle PID. Há muitas maneiras de ajustar uma malha PID, incluindo a técnica “tentativa e erro”, que é a que usaremos nesse projeto.

Pense no PID como uma mola simples. Uma mola tem um comprimento original (setpoint), que quando perturbada, pela expansão ou contração, tende a recuperar o seu comprimento original no menor tempo possível. Controladores PID são utilizados onde quer que haja a necessidade de controlar uma quantidade física e a torná-la igual a um valor pré-especificado. Por exemplo, controlador de velocidade em carros, robôs, reguladores de temperatura, reguladores de tensão, etc.

Como o PID funciona?
O sistema calcula o “ePIDrro” ou “desvio” da quantidade física em relação ao set point, medindo o valor atual dessa quantidade física usando um sensor. Para voltar ao set point, este “erro” deve ser minimizado, idealmente igual a zero. Além disso, esse processo deve ocorrer o mais rapidamente possível (também idealmente esse tempo deveria ser zero).
Mais informações, acesse:  http://en.wikipedia.org/wiki/PID_controller

Implementando PID

  1. erro (e):
    Este valor é igual à diferença entre o ponto de regulação e o valor actual da quantidade a ser controlada.
    error = set_point – CURRENT_VALUE (no nosso caso é a variável de erro começa a partir da posição do robô sobre a linha
  2. Termo Proporcional (P):
    Este termo é proporcional ao erro.
    P = error
    Este valor é responsável pela magnitude da mudança necessária na quantidade física para atingir o ponto de ajuste. O termo proporcional é o que determina o tempo de subida da malha de controle ou o quão rápido ele vai chegar ao set point.
  3. Termo Integral (I):
    Este termo é a soma de todos os valores de erro anterior.
    I = I + error
    Este valor é o responsável pela rapidez de resposta do sistema para a mudança do ponto de ajuste. O termo integral é utilizado para eliminar o erro de estado estacionário exigido pelo termo proporcional. Normalmente, pequenos robôs não usam o termo integral porque não estamos preocupados com erro de estado estacionário e isso pode complicar o ajuste.
  4. Diferencial ou termo derivativo (D):
    Este termo é a diferença entre o erro instantâneo do ponto de ajuste, e o erro a partir do instante anterior.
    D = error – previousError
    Este valor é responsável para diminuir (“slow down”) a taxa de variação da quantidade física quando nos aproximamos do ponto de ajuste. O termo derivativo é utilizado a reduzir o “overshoot” ou o quanto o sistema “super corrige”.

Equação:

PIDvalue = (Kp * P) + (Ki * I) + (Kd * D)

Onde:

  • Kp é a constante usada para fazer variar a magnitude da mudança necessária para atingir o ponto de ajuste.
  • Ki é a constante utilizada para variar a velocidade com que a mudança deve ser apresentado na quantidade física para atingir o ponto de ajuste.
  • Kd é a constante utilizada para variar a estabilidade do sistema.

Uma abordagem para a obtenção das constantes é deixar Ki de fora  e trabalhar com o Controle PD (manter Ki = 0);  definir a variável Kd como 0 e sintonizar o termo Kp sozinho pela primeira vez. Kp de 25 é um bom lugar para começar no nosso caso aqui e ir subindo seu valor. Na última etapa foi utilizado um Kp de 50 que funciona muito bem com meu robô. Se o robô reagir muito lentamente, voce deve aumentar o valor. Se o robô parece reagir muito rapidamente tornando-se instável, diminua o valor. Uma vez que o robô responda razoavelmente, sintonize a parte derivativa da malha de controle. Primeiro defina o valor Kp e Kd cada um para a 1/2 do valor Kp. Por exemplo, se a resposta do robô é razoável com um Kp = 50, definir Kp = 25 e Kd = 25 para iniciar. Aumente o ganho de Kd para diminuir o “overshoot ou o diminua se o robô se tornar instável.

Um outro componente do loop a ser considerado é a taxa real de amostra / loop. Acelerar-la ou para retardar-la pode fazer uma diferença significativa no desempenho do robô. Isso é definido pelas declarações de “Delay” que você tem em seu código. Use o método tentativa-erro para obter o melhor resultado.

Com base na abordagem anterior, implementamos a função abaixo:

void calculatePID () 
{
   P = error;
   I = I + error;
   D = error - previousError;
   PIDvalue = (Kp * P) + (Ki * I) + (Kd * D);
   previousError = error;
}

A constante Kp simples usada na última etapa será substituída agora por PIDvalue, mais completa:

void motorPIDcontrol () 
{
   int leftMotorSpeed = 1500 - iniMotorPower - PIDvalue; 
   int rightMotorSpeed = 1500 + iniMotorPower - PIDvalue;
   leftServo.writeMicroseconds (leftMotorSpeed);
   rightServo.writeMicroseconds (rightMotorSpeed);
}

Mas note que se você tem Kd e Ki = 0, PIDvalue = Kp * error, exatamente como na etapa anterior onde usamos só o controle Proporcional.

Adicionando condições especiais ao código final

 

FullSizeRender 13

Neste ponto do projeto, o robô já pode seguir sem parar um circuito de linha do tipo “loop constante”.

A função Loop do programa de ciclo seria simplesmente:

void loop ()
{
   readLFSsensors (); // Ler sensores, armazenar os valores no Array de sensores e calcular o "erro"
   calculatePID (); 
   motorPIDcontrol ();
}

Mas, para uma operação mais completa e real, é importante acrescentar, pelo menos um par de “comandos básicos de linha”.

Por exemplo, vamos introduzir uma nova variável: "mode", definindo 3 estados para esta variável:

#define STOPPED 0
#define FOLLOWING_LINE 1
#define NO_LINE 2

Se todos os sensores de encontrar uma linha preta, a saída do Array de Sensor seria: 1 1 1 1 1. Nesta condição, podemos definir o modo como “PARADO” e o robô deve realizar um “Full Stop”.

if ((LFSensor [0] == 1) && (LFSensor [1] == 1) && (LFSensor [2] == 1) && (LFSensor [3] == 1) && (LFSensor [4] == 1 )) {mode = STOPPED;}

Outra situação comum com robôs seguidores de linha, é quando o mesmo não encontra “nenhuma linha”, ou a saída do Array de Sensores é: 0 0 0 0 0. Neste caso, podemos programá-lo para girar 180 graus (ou girar em ângulos pequenos até que uma linha é encontrada e a condição normal FOLLOWING_LINE é retomada.

else if ((LFSensor [0] == 0) && (LFSensor [1] == 0) && (LFSensor [2] == 0) && (LFSensor [3] == 0) && (LFSensor [4] == 0)) {mode = NO_LINE;)

A função loop completa seria:

void loop () 
{
   readLFSsensors ();
   switch (mode)
   {
     case STOPPED:
        motorStop();
        break;
     case NO_LINE:
        motorStop ();
        motorTurn (LEFT, 180);
        break;
     case FOLLOWING_LINE:
        calculatePID ();
        motorPIDcontrol ();
        break;
   }
}

O código final incluirá integrar lógica adicional e também algumas variáveis devem ser inicializadas, etc. Durante as explicações , deixei estes detalhes de fora para simplificar a explicação, mas acredito que tudo fique claro dando uma olhada no código final.

Usando o App Android para ajustar os ganhos do controlador PID

No código do Arduino, você poderá encontrar no arquivo “robotDefines.h” as seguintes definições para as constantes “default” serem usadas com o controle PID.

float Kp = 50;
float Ki = 0;
float Kd = 0;

Como explicado anteriormente,  a melhor maneira de definir os ganhos corretos (Constantes Kd, Ki e Kd) é usar a metodologia “Tentativa-e-erro”. O lado ruim disso é que você deve re-compilar o programa a cada vez que você defina uma constante nova. Uma maneira de acelerar o processo é usar o App Android para enviar as constantes na fase de setup do programa.

Picture1

Eu desenvolvi um aplicativo Android exclusivamente para isso:

o MJRoBot Line Follower PID Control

Em resumo o aplicativo possui:

fkhjj4uin699t7j-medium

  • Comandos manuais tradicionais:
    • FW, BW, esquerda, direita e parar onde o aplicativo irá enviar ao módulo BT HC-o6, respectivamente:  ‘f’, ‘b’, ‘l’, ‘r’ e ‘s’.

 

fpjpy8zin699t7l-medium

  •  3sliders , um para cada constantes PID:
    • Kp: “p / XXX”
    • Ki: “i / XXX”
    • Kd: “d / XXX”
      • Em que “XXX” é um número de 0 a 100.

 

 

  • Um botão extra (“Play”) que funciona exatamente como o botão conectado ao pino 9 do Arduino. Você poderá utilizar um ou o outro, não importa.

No final deste tutorial, você encontrará o arquivo .aia que pode ser modificado no MIT AppInventor e o arquivo .apk para ser instalado diretamente no seu dispositivo Android.

  • MJRoBot_Line_Follower_PID_Control.aia
  • MJRoBot_Line_Follower_PID_Control.apk

Alterar o código para o ajuste remoto dos ganhos do PID

Durante a fase de setup do programa, introduziremos um “loop” onde você poderá enviar os parâmetros PID para o robô antes de colocá-lo sobre a linha:

while (digitalRead (buttonPin) && !mode) 
{
   checkBTcmd (); // Verificar se um comando é recebido via controle remoto BT
   manualCmd (); // Executar o comando
   command = "";
}
checkPIDvalues (); // Enviar as constantes PID para o dispositivo Android (apenas para verificação)
mode = STOPPED;

A função de comando manual, será:

void manualCmd () 
{
   switch (command [0])
   {
      case 'g':
        mode = FOLLOWING_LINE;
        break;
      case 's':
        motorStop (); // Desligar ambos motores
        break;
      case 'f':
        motorForward ();
        break;
      case 'r':
        motorTurn (RIGHT, 30);
        motorStop ();
        break;
      case 'l':
        motorTurn (LEFT, 30);
        motorStop ();
        break;
      case 'b':
        motorBackward ();
        break;
      case 'p':
        Kp = command [2];
        break;
      case 'i':
        Ki = command [2];
        break;
      case 'd':
        Kd = command [2];
        break;
    }
}

No vídeo, você poderá ver alguns testes usando o App Android:

Video Robô seguidor de linha

Abaixo o código final para o robô:

Arduino & Android codes

Conclusão

Este é o primeiro tutorial de um projeto mais complexo, explorando a potencialidade de um robô seguidor de linha. No próximo tutorial, desenvolverei um robô com habilidade para resolver um labirinto, ou seja não só encontrar o caminho de saída, mas também qual seria o caminho mais curto e rápido.

Espero que esse trabalho possa contribuir para que outras pessoas possam aprender mais sobre eletrônica, robôs, Arduino, etc.

Um abraço e até o próximo post!
Obrigado

O braço robótico: Desenvolvimento do Projeto

Depois de um longo e tenebroso inverno, vamos ao projeto de verdade! Afinal, já era sem tempo! O circuito como comentado em meu post anterior:

O braço robótico – Introdução

será baseado no Arduino MEGA, alguns potenciômetros, botões e LEDS para o “painel de controle local”.

Robot Arm Project diagram

Um modulo HC-06  conectará o braço, via rede Bluetooth, com um celular Android (controle em modo remoto).

Braços robóticos:

Os braços robóticos podem ser classificados segundo o número de “juntas”, ou “graus de liberdade” (DOF – Degree of Freedom) que possuem. Por exemplo:

  • 4dof-robotBase giratória (de 360 ou 180 graus). A base é conhecida como “Waist” ou simplesmente “Base”.
  • O ombro ou “Shoulder” é o responsável por levantar ou abaixar o braço na vertical
  • O cotovelo ou”Elbow”, fará o braço ir para a frente ou para trás.
  • A garra ou “Gripper” (em alguns casos “Claw”), funciona abrindo ou fechando para “agarrar coisas”.

arms

Observe que nesse diagrama só o braço propriamente dito já possui 3 DOF. A garra que no caso do meArm (4DOF) adiciona o quarto elemento. Nesse projeto ficaremos por aqui (até 4 DOF), mas é obvio que poderíamos ter mais “juntas no corpo”  e principalmente a garra poderia ser mais sofisticada com 2 ou 3 DOF (rotação e elevação).

easyarmdf

O braço ao lado por exemplo, possui 5DOF e foi desenvolvido pela EASYDS, que possui uma linha completa de Kits Robóticos Educacionais em MDF. Vale a pena dar uma olhada no site.

O circuito:

Para o acionamento das juntas serão utilizados servo motores conectados diretamente ao Arduino, mas poderiam também ser utilizados “Stepper Motors” para maior torque e precisão.

A escolha adequada dos servos é muito importante (existem servos baratos chineses que são muito mal construídos como por exemplo o MG995, que infelizmente são os que vieram com meu SanSmart 3DOF aArm) .

A alimentação dos servos deve ser separada do Arduino e demais componentes. Uma fonte externa de 5 a 6V deve funcionar sem problemas (verifique o datasheet de seus servos para verificar a faixa de voltagem apropriada). Uma prática comum também é a utilização de capacitores de 470uF entre VCC e GND para minimizar os ruídos gerados pelos motores internos dos servos.  Não se esqueça de conectar todos os terras (fonte externa com  o Arduino).

Caso os servos tenham problemas e vibrem muito, faça ajustes nos delays de seu codigo. É importante que os servos tenham tempo para chegar a um determinado ponto antes de receber um novo comando. Também vale a pena verificar se os servos são digitais ou analógicos, pois apesar de serem parecidos mecanicamente, os digitais trabalham em uma frequência de 300Hz enquanto que os analógicos com 50Hz. A biblioteca standard do Arduino  foi desenvolvida para servos analógicos e podem ser modificadas caso necessário, para para um melhor funcionamento com servos digitais.

Abaixo o diagrama completo:circuit

O código:

O projeto não é complicado, mas possui muitas variáveis. O mais prudente foi definir-las claramente e deixar suas declarações en um arquivo exclusivo:

ArmDefine.h

No arquivo também foram definidos os valores mínimo, máximo e iniciais para os servos. No codigo incluído nesse tutorial, existem dois conjuntos de parâmetros referentes aos braços robóticos que testei em meu projeto (claro que somente um grupo de constantes deverá ser utilizado):

  1. MeArm 4-DOF
    • #define minGrip 15 
      #define minBase 0 
      #define minShou 60 
      #define minElbw 60
    • #define maxGrip 45 
      #define maxBase 170
      #define maxShou 180 
      #define maxElbw 150
      #define midGrip 30
      #define midBase 87
      #define midShou 138
      #define midElbw 100
  2. SS 3-DOF
    • #define minGrip 75 
      #define minBase 5 
      #define minShou 5 
      #define minElbw 0
      #define maxGrip 125 
      #define maxBase 150
      #define maxShou 155 
      #define maxElbw 0
      #define midGrip 100
      #define midBase 90
      #define midShou 90
      #define midElbw 0

Para cada tipo de braço vão existir parâmetros distintos e é importante que você encontre os correctos para o seu. O que sugiro é que inicialmente os potenciômetros fiquem em seu ponto médio e que o Mapping das saídas PWM sejam definidas com os valores standard: Max = 255, Min = 0 e Mid = 126 (“#defines” acima). Ao se ir variando os potenciômetros (um a um), deve-se observar no Monitor Serial (ou LCD) quais deverão ser os valores mínimos e máximos em que o braço trabalhará corretamente. Estes serão os valores finais a serem utilizados para as definições.

Para a “gravação” dos conjuntos de coordenadas (ou steps) que o robot deverá reproduzir, utilizarei arrays de dados:

int gripPosition[100];
int basePosition[100];
int shouPosition[100];
int elbwPosition[100];
int positionIndex = 0;

Observe que não estou guardando as posições “gravadas” e ao finalizar -se o “programa do robot”, o índice voltará a zero e o robot esperará pela gravação de uma nova sequência. Se poderia guardar esses arrays de dados na EEPROM do Arduino, por exemplo. Isso faria com que o programa pudesse ser executado infinitas vezes, ou até mesmo possuir mais de um programa armazenado. Fica aqui a dica para o desenvolvimento de um projeto mais sofisticado.

A lógica do programa:

O bloco principal (“Loop”) é na verdade bem simples:

  1. Verifica se há mensagens no buffer serial, provenientes do Android
  2. Se há mensagens, verifica se o controle deverá ser Local ou Remoto ( o default é Local).
  3. Verifica se existe um comando para executar o “programa” (sequencia de passos). Se existe, o executa. Do contrário, entende que o “programa” não está completo e ainda se devem gravar novos passos.
  4. Se há uma nova posição é definida, adiciona a mesma ao programa
  5. Volta ao início e executa o passo 1 novamente.
void loop()
{ 
  checkBTcmd();
  defineLocalRemote();
 
  execTaskCmd = digitalRead (execTaskPin);
  if(execTaskCmd == HIGH || command == "runon")
  {
    runProgram();
  }
  else recArmPosition(); 
  command = "";
}

A função checkBTcmd() monta uma string a partir dos caracteres que chegam do modulo BT. Essa string é passada a variável “command”.

A função defineLocalRemote() analisará  a variável “command” verificando se um comando para mudar a função de local a remoto ou vice e versa é recebida. O comando de Alarm também é analisado aqui. Pela lógica do programa, se “Alarm” for acionado no no Android, significará que o braço deverá  passar obrigatoriamente ao modo Remoto.

A função runProgram () executará as preparações, como acender/apagar os LEDS correspondentes, etc. e principalmente invocará a função: executeTask(). Essa última, é a função que contem a lógica de execução da sequencia de steps. A função incrementa o “positionIndex” enviando ao braço uma a uma,  as coordenadas para seu posicionamento usando a função: armPosition(grip, base, shoulder, elbow).

Por último, a função que realmente comandará os servos e gravará os “steps” é a recArmPosition(). Dependendo do comando recebido do Android, esta função deverá definir se o posicionamento dos servos será comandado através dos potenciômetros, ou através dos “sliders” do Android. A cada mudança de posição, esta função enviará as coordenadas aos servos via a função armPosition(grip, base, shoulder, elbow). A leitura da posição dos potenciômetros ou dos sliders e o correspondente acionamento dos servos ocorrerá até o momento em que o comando de “Gravar” ou “PROGRAM” seja acionado. Nesse momento o índice de posição dos Arrays será incrementado e as coordenadas guardadas.

Para simplificação de entendimento, todo o codigo foi baseado em funções específicas. O bloco de Setup, Loop e as funções descritas anteriormente estão praticamente todas no file:

MJRoBot_Arm_Robot_Task_Prgm.ino

As funções mais gerais como leituras de comandos BT: void checkBTcmd(); gerador de som: void beep(int pin, int freq, long ms)  e debouncing : boolean debounce(int pin); estão no file:

General_Functions.ino

Um dado importante. Como o Arduino executará instruções baseadas em um clock de 16Mhz,  é de se esperar que os botões de comando sejam lidos centenas ou até milhares de vezes por segundo, daí ser importantíssimo fazer um “debouncing” do botão que define a gravação do step.

O quarto e último file que compõe o código é:

Arm_Ctrl_and_Display.ino

Nesse file estão principalmente as funções de leitura de potenciômetros: bool readPotenciometers(); leitura dos sliders do Android: bool readSliders(); posicionamento dos servos: void armPosition(int gripp, int basee, int shoulder, int elbow). As demais funções do file são auxiliares para display de dados no LCD, Serial Monitor, alarmes, etc.

O código completo para o projeto pode ser baixado aqui:

Link para o código do Arduino

Abaixo um vídeo do braço robótico 4DOF “meArm”:

No vídeo o braço está sendo programado remotamente através da nova versão da app Android

androidapp

A app pode ser baixada gratuitamente aqui:

MJRoBot Arduino Arm Robot Ctrl.

Toda a documentação para o projeto pode ser baixada de meu GitHub

Agora é só colocar a mão na massa, quer dizer no Arduino e mandar bala!

Conclusão

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, robótica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!” 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

O braço robótico – Introdução

O objetivo desse tutorial é desenvolver passo a passo um projeto para o controle e programação de um braço robótico, simulando as funções básicas de um robô industrial.

Especificações:

  • O robô deverá ter duas funções básicas:
    • Programa: gravar posições do braço em 3 dimensões
    • Run: executar o programa de trabalho (ou seja, executar em sequencia as posições gravadas na etapa de programa.
  • O robô executará o programa até que o comando de “abortar” seja utilizado.
  • O projeto deverá controlar robots de 3 ou 4 DOF (“Degrees of Freedom”).
  • O robô deverá ser controlado em modo “local” e “remoto” (via celular)
  • Durante o programa se poderá mudar o controle de “local” a “remoto” e vice-e-versa.
  • Deverá possuir sinalização tanto visual (LEDS e display) quanto sonora
  • Deverá possuir função de alarme acionado remotamente.

O projeto:

O diagrama abaixo mostra o “Bill of Material” que será utilizado no projeto:

Robot Arm Project diagram

Para o projeto, optarei pelo Arduino MEGA para não ter que me preocupar com o número de portas I/Os. O UNO funcionaria sem problemas, mas se deveria utilizar algum método de expansão de portas. Existem várias opções que podem ser facilmente encontradas na internet, como um decodificador 74138 por exemplo (veja o site: “Expandindo os I/Os do Arduino”).

Para a rede Bluetooth utilizarei o HC-06 (o qual explico em detalhes no tutorial “Conectando “coisas” através do Bluetooth”).

MJRoBot Arm Ctrl iconA app Android utilizado para o “modo  remoto”, foi desenvolvida utilizando-se a ferramenta  MIT appinventor2, uma plataforma tão poderosa quanto simples para esse tipo de app baseado no Android. No futuro pretendo explorar a criação de apps desse tipo aqui no Blog. Por enquanto, a app está disponível para ser baixada gratuitamante na loja da Google:  MJRoBot Arduino Arm Robot Control.

Os vídeos abaixo dão uma idéia de como deverá ficar o projeto final:

Primeiro protótipo sendo programado em “modo local”:

O robô programado de maneira remota via rede Bluetooth:

 

Nos próximos posts, detalharei os passos necessários para o desenvolvimento do projeto.

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, robótica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!” 😉

Um abraço e até o próximo post!

Obrigado

Marcelo

FullSizeRender 23Em tempo: aproveito e deixo aqui um abraço para duas pessoas muito queridas, meu tio Ítalo que já não está aqui entre nós, que além de me ensinar a jogar Xadrez quando garoto, me deixou de presente essas peças que utilizei no vídeo. O outro abraço vai para o meu sogro Mathias, que muito carinhosamente presenteou-me com o tabuleiro e com as peças do jogo que haviam se perdido com o tempo (tudo feito por ele).

 

 

 

 

Conectando “coisas” através do Bluetooth

 

bluetooth-logo

Uma rede sem fio local (e pessoal) porreta é sem dúvida a Bluetooth (BT). Hoje em nosso dia-a-dia é comum encontrar-nos com celulares, aparelhos de som, cameras, etc., entrelaçados com a ajuda da famosa “luzinha azul”

No mundo do IoT e da automação em geral, é muito comum deparar-nos com controles remotos via celulares utilizando tecnologia BT. Isso é devido a 2 componentes básicos mas muito importantes:

  1. Plataforma de desenvolvimento para OS ANDROID
  2. Módulos BT baratos e acessíveis (Como por exemplo o HC-06)

Neste tutorial, vou desenvolver algumas idéias de como controlar as saídas de um Arduíno através de um celular de maneira a mover um Robot, acionar lâmpadas em uma casa, etc.

No mercado é comum encontrar módulos de BT 3.0  “Master-Slave” como o HC-05  e “Slaves” como o HC-06. Já mais recentemente, apareceram os HC-08 e HC-10 que trabalham com tecnologia BT 4.0 ou BLE (“Bluetooth Low Energy”). Os módulos BLE são os únicos que podem ser conectados a um Iphone, pois infelizmente a Apple não fornece suporte a ao BT 3.0.

Para os projetos discutidos aqui, usarei um HC-06 que é bem popular e barato (Bye, bye, Iphone, vamos de Android!). O Módulo é alimentado com 5V o que faz com que ele seja facilmente conectado a um Arduino

HC06 PinsUNO por exemplo, para receber e transmitir informações a outros dispositivos como um PC ou um telefone celular. Seus pinos de transmissão e recepção podem ser conectados diretamente ao UNO, não havendo a necessidade de se utilizar divisores de tensão como vimos no caso do ESP8266.

Na prática, o HC-06 deve ser ligado diretamente aos pinos 0 e 1 do Arduino (Serial):

  • HC06-Tx ao Arduino pin 0 (Rx)
  • HC06-Rx ao Arduino pin 1 (Tx)

Ao se usar a entrada serial por HW do UNO é muito importante lembrar-se que o HC-06 não pode estar fisicamente conectado aos pinos 0 e 1 durante a carga do programa, isso porque o USB também usa essa mesma serial.  Uma maneira simples de se contornar esse probleminha (se seu projeto não utiliza muitos GPIOs do UNO) é usar uma porta serial por SW através da library SoftwareSerial (a mesma que usamos no caso do ESP8266). Em nosso caso aqui, usaremos os pinos 10 e 11 do UNO (Tx, Rx respectivamente).

UNO-HC06

O passo seguinte será escrever um codigo bem simples para se poder testar, programar e inicializar o HC-o6:

Para iniciar, incluir a Library Software Serial, definindo a variável “BT” como a nova porta serial.

#include <SoftwareSerial.h>
SoftwareSerial BT(10, 11); // RX, TX
String command = ""; // Stores response of bluetooth device
 
void setup()
{
 Serial.begin(9600);
 Serial.println("Type AT commands!");
 BT.begin(9600); // HC-06 usually default baud-rate
}

Em seguida vem o corpo principal do código que simplesmente espera por dados vindos do BT e uma vez que eles chegem, os mesmos são escritos no Serial Monitor. Da mesma maneira, se podem enviar comandos AT desde o monitor serial até o módulo HC-06.

void loop()
{
 if (BT.available()) // receive data if available.
 {
  while(BT.available()) // "keep receiving".
  {
   delay(10); //Delay added to make thing stable 
   char c = BT.read(); //Conduct a serial read
   command += c; //build the string.
  } 
  Serial.println(command);
  command = ""; // No repeats
 } 
 if (Serial.available())
 {
  delay(10); 
  BT.write(Serial.read());
 }
}

 

Uma vez carregado o programa, faça alguns testes básicos. Por exemplo, envie “AT“, o módulo deverá responder “OK“. Pergunte a versão do Firmware: “AT+VERSION”, o módulo deverá responser, por exemplo: “linvorV1.8“. Com o HC-06 é possível definir um nome para o módulo por exemplo: “AT+NAMEMJRoBot_BT_HC06“.  Mas diferente de outros módulos, voce não conseguirá saber qual é o nome que está definido para o módulo. Ao se enviar o comando anterior, o HC-06 responderá simplesmente: “OKsetname”

Em geral, o HC-o6 vem de fábrica com o password (ou PIN): 1234. Voce poderá definir um novo com o comando AT: AT+PINxxxx onde ‘xxxx‘ serão 4 números.

OK! Módulo conectado ao UNO e funcionando. Hora de lançar mão do velho e bom celular Android!!!

Existem muitas apps na loja da Google que podem ser utilizadas. Vou sugerir duas delas, por serem as que usarei nos tutoriais deste blog. Essas apps foram desenvolvidas por mim utilizando o MIT Application2 tool (veja projeto aqui) e estão disponíveis sem custo na loja da Google:

  1. MJRoBot II BT Control
  2. MJRoBot BT Digital Analog Voice Control

MJRoBot BT IconO App MJR0Bot II foi desenvolvido para comandar Robots. Ele basicamente envia um caracter para cada comando de direção, modos AUTO/MANUAL, velocidade + e velocidade-. Alem de permitir o envio de mensagens em modo texto.

2015-12-11 20.26.44 copy

 

MJRoBot BT Ctrl app Logo

O App. MJRoBot Digital Analog Voice Control, envia comandos para acionamento digitais (ligar/desligar) tanto por botões como por voz e comandos numéricos para controle de PWMs (0-255).

FullSizeRender 10

 

Faça o Download de qualquer uma das duas Apps, vá ao set-up do celular e procure o modulo BT fazendo a conexão (entre com o PIN 1234 ou algum outro definido por você).  Isso deverá ser feito uma única vez, pois o celular guardará os dados de conexão. Uma vez que o celular e o HC-06 estão conversando, é só usar as APPs.

A partir da segunda vez, ao lançar-se a app, o modulo BT estará desconectado.

FullSizeRender 14

Acione o botão de BT, para que o app informe dos modulos disponíveis:

FullSizeRender 12

Selecione o nome do módulo (No caso é esse com o HC-06 ao final).

O App então mostrará “CONNECTED”, informando que está “pared” com o HC-06

FullSizeRender 13

A partir daí, é só ir acionando os botões do APP e observar no Monitor Serial, o que é que o APP está enviando.

Por exemplo, acionando “ON” e “OFF” sequencialmente para os 4 devices, no caso da APP MJR0Bot BT Digi/Ana/Voice Ctrl, o resultado seria:

dev1on
dev1off
dev2on
dev2off
dev3on
dev3off
dev4on
dev4off 

Agora que já temos um App para o Android e sabemos como funciona um modulo BT, vamos colocar a mão na massa e criar algo de util!

Controlando as saídas do Arduino.

Vamos construir o circuito abaixo:

Picture1

A idéia será usar o App MJRoBot Digi/Ana/Voice Ctrl para acender e apagar os LEDS e também controlar a intensidade dos mesmos.

Estaremos relacionando:

  • Device 1: "dev1on/dev1off" ==> LED Red ==> Pin 3 do UNO
  • Device 2: "dev2on/dev2off" ==> LED Yellow ==> Pin 5 do UNO
  • Device 3: "dev3on/dev3off" ==> LED Green==> Pin 6 do UNO
  • Device 4: "dev4on/dev4off" ==> LED Blue==> Pin 9 do UNO

Ou seja, ao acionar o botão “ON” relacionado ao “Device 1”, a mensagem de texto “dev1on”será enviada ao Arduino. Ao receber essa mensagem, o LED vermelho deverá acender e assim por diante.

Observe que os 4 pinos são pinos com capacidade de gerar PWM. Isso é importante para a utilização dos “sliders” da App, que enviarão valores para controlar a intensidade dos LEDs via PWM

  • Dev A0: "r/ 0-255" ==> LED Red ==> Pin 3 do UNO
  • Dev A1: "y/ 0-255" ==> LED Yellow ==> Pin 5 do UNO
  • Dev A2: "g/ 0-255" ==> LED Green==> Pin 6 do UNO
  • Dev A3: "b/ 0-255" ==> LED Blue==> Pin 9do UNO

No caso dos controles deslizantes (“sliders”), antes dos valores para controle do PWM (0 a 255), um caracter será enviado para que o Arduino saiba de que “slider” estará chegando o comando.

O código:

Setup inicial:

#include <SoftwareSerial.h>
SoftwareSerial BT(10, 11); //TX, RX respectively
String device;
const int dev1 = 3; //PWM
const int dev2 = 5; //PWM
const int dev3 = 6; //PWM
const int dev4 = 9; //PWM
void setup() 
{
 BT.begin(9600);
 Serial.begin(9600);
 
 pinMode(dev1, OUTPUT);
 pinMode(dev2, OUTPUT);
 pinMode(dev3, OUTPUT);
 pinMode(dev4, OUTPUT);
}

Podemos dividir a parte principal do programa em 4 blocos:

  1. Espera dos comandos BT e construção da variável “device”
  2. Acionamento dos LEDS a partir dos botões
  3. Acionamento dos LEDS a partir de comandos de voz
  4. Controle do intensidade dos LEDs via Sliders
void loop() 
{
 while (BT.available()) 
 {
   delay(10); //Delay added to make thing stable 
   char c = BT.read(); //Conduct a serial read
   device += c; //build the string.
 } 
   if (device.length() > 0) 
 {
   Serial.println(device); 
   // Button control:
   if (device == "dev1on") {digitalWrite(dev1, HIGH);}
   else if (device == "dev1off") {digitalWrite(dev1, LOW);}
   else if (device == "dev2on") {digitalWrite(dev2, HIGH);}
   else if (device == "dev2off") {digitalWrite(dev2, LOW);}
   else if (device == "dev3on") {digitalWrite(dev3, HIGH);}
   else if (device == "dev3off") {digitalWrite(dev3, LOW);}
   else if (device == "dev4on") {digitalWrite(dev4, HIGH);}
   else if (device == "dev4off") {digitalWrite(dev4, LOW);}
 
   // Voice control:
   else if (device == "ligar um" || device == "Ligar 1") {digitalWrite(dev1, HIGH);}
   else if (device == "desligar um" || device == "desligar 1") {digitalWrite(dev1, LOW);}
   else if (device == "ligar som" || device == "Ligar som") {digitalWrite(dev2, HIGH);}
   else if (device == "desligar som" || device == "Desligar som") {digitalWrite(dev2, LOW);}
   else if (device == "ligar TV" || device == "Ligar TV") {digitalWrite(dev3, HIGH);}
   else if (device == "desligar TV" || device == "Desligar TV") {digitalWrite(dev3, LOW);}
   else if (device == "ligar quarto" || device == "Ligar quarto") {digitalWrite(dev4, HIGH);}
   else if (device == "desligar quarto" || device == "Desligar quarto") {digitalWrite(dev4, LOW);}
   // Slider control:
   char colour = device[0]; 
   int value = device[2]; 
   Serial.print(" "); 
   Serial.println(value); 
   if ( colour == 'r') 
   { 
     analogWrite(dev1, value); // use value to set PWM for LED brightness 
   } 
     if ( colour == 'y') 
   { 
     analogWrite(dev2, value); // use value to set PWM for LED brightness 
   } 
     if ( colour == 'g') 
  { 
    analogWrite(dev3, value); // use value to set PWM for LED brightness 
  } 
    if ( colour == 'b') 
  { 
    analogWrite(dev4, value); 
  }
  device=""; //Reset the variable
 } 
}

 

Link para o código Arduino

No vídeo abaixo, uma demonstração da porção programa acima (botões & Slider):

O controle de dispositivos IoT por voz é uma tendência nos dias de hoje. Conseguir este tipo de controle com o conjunto Arduino/HC-06/Android é extremamente simples. Os dispositivos Android já possuem essa característica “de fábrica”. No App que desenvolví, apenas acrescentei um botão que faz com que o Android “escute” um comando e o envie em formato texto para o Arduino. O codigo se encarrega de “ler” a string que chega.

Em vermelho, ressalto no código alguns possíveis comandos de voz. Ao receber por  “Ligar TV” por exemplo, o LED verde (correspondente ao device 3) acenderá (em vez do LED podíamos ter uma relé que ligaria a TV).

No vídeo abaixo, uma pequena demonstração do controle por voz:

 

Bom, acredito que já é possível ver o enorme potencial do controle de “coisas” usando BT/Android/Arduino. Em meus próximos posts estarei desenvolvendo Robots que poderão ser controlados remotamente como vimos aqui.

Um abraço e até mais!

 

O ESP8266 parte 3 – Acionando LEDs remotamente

Depois de um longo e tenebroso inverno, chegamos ao ponto de acionar LEDs (ou qualquer coisa) remotamente via WiFi. Para quem entrou direto nesse post, vale a pena dar uma sapeada em meus 2 posts anteriores:

  1. O ESP8266 – Serial WIFI Module
  2. O ESP8266 parte 2 – Web Server

A diferença básica aqui, é que ao invés do Arduino criar uma pagina web para enviar as informações capturadas, agora será uma página criada anteriormente em HTML e hospedada em algum sitio web (no nosso caso aqui estará em emu desktop) que enviará os dados. Esta pagina estará recebendo comandos de um usuário enviando-os via WiFi ao Arduino.

 

LED Ctrl

O Circuito:

O circuito é bem simples. As conexões do ESP8266 com o UNO são as mesmas dos posts anteriores, apenas acrescentamos os 3 LEDs a 3 GPIOs do UNO:

GPIO 11: LED azul
GPIO 12: LED vermelho
GPIO 11: LED verde

ES8266_Arduino_UNO_GPIO_Control

O código HTML:

A Página deve ser gerada em HTML em um editor qualquer e salva no desktop.

HTML Buttons

A parte principal do codigo HTML abaixo, é a geração dos “botões”, no caso “ON” e “OFF”. Observe que para cada botão (são 6 no total, 2 para cada GPIO), é gerado um “id” específico. Por exemplo, se o botão “ON” para o GPIO 11 for pressionado, o id será “111”, ou seja os dois primeiros dígitos para a identificação do GPIO (no caso “11”) e o 3o. dígito para o estado, no caso “1” ou “ON”. Se fosse “OFF”, o Id sería 110. No caso, na saída digital do pino 11, está o LED azul.

Uma vez gerado o “id” uma função na página se encarrega de enviar-la para para o IP address do ESP8266. Observe que o endereço que aparece no set-up do ESP 8266 tem que ser incluído na linha ==> $.get(“http://10.0.1.14:80/&#8221;, {pin:p});

HTML Button click

No monitor serial se pode observar o valor do pin que é gerado pela página.

Serial monitor ongoing

As primeiras vezes que testei o programa, tive vários problemas e o mais importante foi que observei que só era possível enviar 5 comandos aos LEDs. Isso porque  a cada envío de informação o ESP8255 considerava uma conexão aberta (CONNECT 0, 1, 2, 3, 4) e o limite é 5.

Observei também que quando fazia um “refresh” na página, o connect, voltava a zero. A solução foi incluir um novo codigo HTML, que forçava o refresh automático da página (código tipo “META”). No caso optei por fazer um refresh a cada 15s para não ficar muito ruim para o usuário. Funcionou sem problemas:

<META HTTP-EQUIV=”refresh” CONTENT=”15″>

O código do Arduino:

O código é muito parecido com o código anterior:

Inicializa, faz o setup das portas, “reseta” e inicializa o ESP8266, etc., usando as mesmas funções definidas no post anterior.

A diferença básica está na porção em verde do código contido  no “Loop”:

void loop()
{
  if(esp8266.available())
  {
    if(esp8266.find("+IPD,"))
    {
      delay(1000);
      
      int connectionId = esp8266.read()-48; 
      esp8266.find("pin="); 
      int pinNumber = (esp8266.read()-48)*10; 
      pinNumber += (esp8266.read()-48); 
      int statusLed =(esp8266.read()-48);
      digitalWrite(pinNumber, statusLed)
      
      String closeCommand = "AT+CIPCLOSE="; 
      closeCommand+=connectionId; 
      closeCommand+="\r\n";
      sendData(closeCommand,1000,DEBUG);
    }
  }
}

Ao ler o “id”, por exemplo “111”, o código separa os primeiros dois dígitos e constrói a variável “pinNumber”. Com o 3o. dígito, se obtém o “statusLed”. A função digitalWrite enviará o status do LED, no caso do exemplo “1” para o GPIO correspondente (ou “pinNumber”), no exemplo 11.

No vídeo abaixo, pode-se ver um exemplo do acionamento remoto dos LEDs:

Link para o vídeo do acionamento remoto de GPIOs via ESP8266

Para finalizar, no link abaixo deixo o codigo completo para o Arduino e para a pagina em codigo HTML.

Link para os codigos fonte

Conclusão

Como sempre, espero que este projeto ajude outras pessoas a encontrar seu caminho no apaixonante mundo da eletrônica, robótica e do IoT!

Não deixe de visitar e seguir minha página: MJRoBot.org no Facebook

“Saludos desde el sur del mundo!” 😉

Um abraço e até o próximo post!

Obrigado

Marcelo