ROBÔ CONTROLADO POR VOZ VIA WIFI
Como desenvolver um robô controlado por voz via WiFi e utilizando como microcontrolador nosso velho amigo, o NodeMCU!
Em meu último tutorial: Controle ativado por voz com Android e NodeMCU, exploramos como desenvolver nossa própria App em um smartphone Android para controlar localmente (usando botões ou voz) dispositivos domésticos inteligentes. Que tal agora, em vez de dispositivos domésticos controlarmos motores? E melhor ainda, que tal ter esses motores movendo um robô? Pois isso, é exatamente o que desenvolveremos aqui, um robô controlado por voz via WiFi e utilizando como microcontrolador nosso velho amigo, o NodeMCU!
O diagrama de blocos abaixo nos dá uma geral sobre o projeto que desenvolveremos aqui:
E o filme nos mostra como ficará o projeto:
Por favor, considere que um de meus motores estava com muito pouco torque. Apesar de o resultado parecer estranho, o projeto funciona a contento. Assim que mudar o motor, atualizarei o vídeo. Obrigado.
https://www.youtube.com/watch?v=UQeTC4ZxvhA.
1: Lista de materiais (BoM)
Valores em USD, apenas para referência.
- NodeMCU ESP8266-12E ($8.79)
- 400-point Experiment Breadboard Breadboard ($ 4.97)
- H-Bridge L293D ($2.17)
- Motor Robot Car Chassis Kit ($13.00)
- Male-Female Dupont Cables ($1.00)
- Portable Charger 5VDC 3000mAh Power Bank ($10.00)
- Bateria 9V DC
….. e qualquer telefone ou tablet Android!
2: A Ponte-H L293D
Para “drivar” os motores usaremos o L293D.
De acordo com seu Datasheet, o L293D é um controlador do tipo half-H quádruplo de alta corrente. O L293D foi projetado para fornecer correntes bidirecionais de até 600 mA em tensões variando de 4,5 V a 36 V.
Usaremos o CI L293D diretamente com o NodeMCU, em vez de um Shield, como se vê mais comumente em projetos no mercado.
Pois bem, nosso robô terá duas rodas, acionadas por 2 motores DC:
- Motor esquerdo (LEFT)
- Motor direito (RIGHT)
Ligaremos os motores ao L293D e este ao NodeMCU, como mostra o diagrama em blocos anterior, onde 6 de seus GPIOs comandarão esses motores.
int rightMotor2 = 13; // D7 – right Motor –
int rightMotor1 = 15; // D8 – right Motor +
int leftMotor2 = 0; // D3 – left Motor –
int leftMotor1 = 2; // D4 – left Motor +
int eneLeftMotor = 12; // D6 – enable Motor Left
int eneRightMotor = 14; // D5 – enable Motor Right
Por exemplo, para mover o robô para a frente, girando o motor esquerdo (LEFT) no sentido apropriado, você deve colocar:
- HIGH no pino D4 (motor esquerdo +) e
- LOW no pino D3 (motor esquerdo -)
Para o motor direito (RIGHT) você deve fazer o oposto:
- HIGH no pino D8 (motor direito +) e
- LOW no pino D7 (motor esquerdo -)
Devido à maneira como meus motores são montados, a combinação acima irá girar ambos motores no mesmo sentido, “empurrando” o robô para a frente.
O diagrama acima define (ou melhor, convenciona) como o robô deverá se mover. Isso é importante para a definição adequada de suas variáveis.
Para controlar a H-Bridge corretamente, você também deverá trabalhar com os pinos de habilitação (“enable”). No exemplo anterior onde o robô se move para a frente, além dos 4 GPIOs discutidos, o robô só funcionaia se os pinos de habilitação (eneLeftMotor e eneRightMotor) estiverem ambos em HIGH. Você poderá conectar esses pinos do L293D (1 e 9) diretamente a + VCC e esquecê-los. Eu não gosto disso, porque se voce necessita por exemplo, parar rapidamente seu robô, esses pinos deveriam estar em necessariamente em LOW. Além disso, associar os pinos de habilitação a saídas PWM do NodeMCU, permitirá também controlar a velocidade do motor. Em nosso exemplo, usaremos estes pinos apenas com valores digitais tais como HIGH e LOW, o que ajustará a velocidade para MAX e ZERO, respectivamente.
Asim, para se criar uma função que conduza nosso robô para a frente, devemos escrever um código como este:
void forwardMotor(void) { digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH);   digitalWrite(leftMotor1,HIGH); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,HIGH); digitalWrite(rightMotor2,LOW); }
3: Movendo o robô em outras direções
Na etapa anterior, aprendemos como podemos mover o robô para a frente, pensemos agora como movê-lo em outras direções.
Definamos 5 possíveis comandos:
- STOP: Pare
- LEFT: Vire à esquerda
- RIGHT: Vire à direita
- REVERSE: Dê marcha à ré
- FORWARD: Vá para a frente
O primeiro comando “STOP” é simples de realizar. Todas as entradas da H-Bridge devem estar em LOW, desta forma os motores não se moverão:
void stopMotor(void) { digitalWrite(eneLeftMotor,LOW); digitalWrite(eneRightMotor,LOW); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,LOW); }
Da mesma forma pensemos em fazer o robô tomar outra direção, digamos que “virar à ESQUERDA”. Considere que o robô está indo para a frente, se queremos virar à esquerda podemos pensar em duas situações:
- Parar o motor ESQUERDO, mantendo o motor DIREITO no sentido a frente (Forward): o robô executará uma trajetória de arco para a esquerda
- Inverter o sentido do motor ESQUERDO, mantendo o motor DIREITO no sentido a frente (Forward): o robô irá girar sobre seu eixo para a esquerda.
Implementemos o caso 2 acima:
void leftMotor(void)
{ digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH);   digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,HIGH); digitalWrite(rightMotor1,HIGH); digitalWrite(rightMotor2,LOW); }
Os demais comandos seguirão a mesma lógica, como ilustra a tabela abaixo:
4: Completando o HW
Siga o diagrama acima e complete o HW de seu Robô.
Você poderá executar alguns testes simples para verificar que seu código está OK. Para isto, introduziremos um “botão” para iniciar o seu robô. Vamos instalá-lo na porta D0 do NodeMCU como mostrado no diagrama elétrico anterior.
Você poderá utilizar o programa abaixo para testar os movimentos de seu robô:
// Set Motor Control Pins int rightMotor2 = 13; // D7 - right Motor - int rightMotor1 = 15; // D8 - right Motor + int leftMotor2 = 0; // D3 - left Motor - int leftMotor1 = 2; // D4 - left Motor + int eneLeftMotor = 12; // D6 - enable Mortor Left int eneRightMotor = 14; // D5 - enable Mortor Right int buttonPin = 16; // D0 void setup() { Serial.begin(115200); pinMode(buttonPin, INPUT_PULLUP); pinMode(eneLeftMotor, OUTPUT); pinMode(eneRightMotor, OUTPUT); pinMode(leftMotor1, OUTPUT); pinMode(leftMotor2, OUTPUT); pinMode(rightMotor1, OUTPUT); pinMode(rightMotor2, OUTPUT); digitalWrite(eneLeftMotor,LOW); digitalWrite(eneRightMotor,LOW); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,LOW); while (digitalRead(buttonPin)) // Wait for button to be pressed to move on { } } void loop() { forwardMotor(); delay (5000); stopMotor(); digitalWrite(leftMotor1,HIGH); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,HIGH); } /* command motor stop */ void stopMotor(void) { digitalWrite(eneLeftMotor,LOW); digitalWrite(eneRightMotor,LOW); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,LOW); } delay (200); leftMotor(); delay (3000); digitalWrite(leftMotor1,HIGH); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,HIGH); } /* command motor stop */ void stopMotor(void) { digitalWrite(eneLeftMotor,LOW); digitalWrite(eneRightMotor,LOW); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,LOW); } stopMotor(); delay (200); forwardMotor(); delay (5000); stopMotor(); delay (200); rightMotor(); delay (3000); stopMotor(); delay (200); reverseMotor(); delay (5000); stopMotor(); delay (200); } /* command motor forward */ void forwardMotor(void) { digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH); digitalWrite(leftMotor1,HIGH); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,HIGH); digitalWrite(rightMotor2,LOW); } /* command motor backward */ void reverseMotor(void) { digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,HIGH); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,HIGH); } /* command motor turn left */ void leftMotor(void) { digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,HIGH); digitalWrite(rightMotor1,HIGH); digitalWrite(rightMotor2,LOW); } /* command motor turn right */ void rightMotor(void) { digitalWrite(eneLeftMotor,HIGH); digitalWrite(eneRightMotor,HIGH); digitalWrite(leftMotor1,HIGH); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,HIGH); } /* command motor stop */ void stopMotor(void) { digitalWrite(eneLeftMotor,LOW); digitalWrite(eneRightMotor,LOW); digitalWrite(leftMotor1,LOW); digitalWrite(leftMotor2,LOW); digitalWrite(rightMotor1,LOW); digitalWrite(rightMotor2,LOW); }
Quando você pressionar o botão, o robô começará a fazer os movimentos definidos no loop (). Os movimentos continuarão até que você pressione “Reset” no NodeMCU. Pressionando o botão “verde” novamente, o ciclo se repetirá.
O código acima poderá ser baixado de meu GitHub:
WiFi_Robot_NodeMCU_Android_Voice_Motor_tests
5: Montando o corpo do robô
- Use o Kit: Chassis, 2 Rodas, 2 Motores DC, 1 roda solta (coaster)
- Solde 2 fios de 10 cm (Vermelho e Preto) em cada polo do motor
- Fixe os motores ao chassi como mostrado na foto acima
- Monte o coaster
- Conecte os fios do motor à eletrônica (L293D)
- Fixe a bateria de 9V ao chassi
- Fixe a bateria de 5V sob o chassi
O robô deverá ficar com essa cara:
6: Conectando o NodeMCU ao seu WiFi local
Conectemos o NodeMCU ao WiFi local, verificando seu endereço IP. Para isso, podemos utilizar o programa abaixo:
#include WiFiClient client; WiFiServer server(80); const char* ssid = "YOUR SSID"; const char* password = "YOUR PASSWORD"; void setup() { Serial.begin(115200); connectWiFi(); server.begin(); } void loop() { } /* connecting WiFi */ void connectWiFi()
{Se ocorrer um erro, por exemplo, quando você diz um comando de voz não reconhecido pelo NodeMCU, o bloco acima irá gravar os detalhes do erro na última linha (Comm_Status) do App, conforme mostrado abaixo:Se ocorrer um erro, por exemplo, quando você diz um comando de voz não reconhecido pelo NodeMCU, o bloco acima irá gravar os detalhes do erro na última linha (Comm_Status) do App, conforme mostrado abaixo:
Serial.println("Connecting to WIFI"); WiFi.begin(ssid, password); while ((!(WiFi.status() == WL_CONNECTED))) { delay(300); Serial.print(".."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("NodeMCU Local IP is : "); Serial.print((WiFi.localIP())); }
No monitor serial voce poderá observar o endereço IP dinâmico designado pelo Modem ao seu NodeMCU.
Tome nota deste endereço de IP. Precisaremos dele para conectar o App Android.
O código acima poderá ser baixado de meu GitHub:
7: Completando o código fonte para o NodeMCU
Para que nosso robô se mova, o NodeMCU deverá receber comandos provenientes do dispositivo Android. Para tal, definamos uma variável para receber estes comandos:
String command ="";
Por exemplo, se a aplicação Android enviar como um comando: “forward” (a frente), o robô deverá avançar, invocando-se a função forwardMotor(). Veja abaixo:
if (command == "forward") { forwardMotor(); }
O mesmo conceito deverá ser aplicado a todo os demais comandos e funções associadas tal como vimos no item 4:
- STOP: Pare
- LEFT: Vire à esquerda
- RIGHT: Vire à direita
- REVERSE: Dê marcha à ré
- FORWARD: Vá para a frente
Baixe o código completo: WiFi_Robot_NodeMCU_Android_Voice_EXT de meu GitHub.
<strong>Entre com as credenciais de sua rede local:</strong> <strong>const char* ssid = "YOUR SSID";</strong> <strong>const char* password = "YOUR PASSWORD";</strong>
Carregue o código em seu NodeMCU e pronto! Você poderá verificar no Monitor Serial se o programa está em execução. Deixe o programa em execução para podermos testar o o aplicativo a ser desenvolvido na próxima etapa.
8: A App Android : “Designer Tab”
Usaremos o MIT App Inventor para o desenvolvimento de nossa App Android.
Principais componentes da Screen 1 (veja a foto anterior):
- User Interface:
Input of IP Address
TextBox named “IP_Address” - 5 Command Buttons:
forward
reverse
right
left
stop
Voice Input Button
Voice_Input - Non Visible Components:
Web1
SpeachRecognizer1
TextToSpeach1 - Other:
Text Box:
Speach_To_Text
Label:
Comm_Status
9: A App Android: Botões
Na Tab “Blocks”, deveremos criar 5 Botões, um para cada comando. Todos eles seguirão a estrutura dos blocos acima.
Estes blocos serão chamados quando o botão “forward” (“botão com a seta para cima”) é pressionado.
Quando você clicar no botão, 3 ações serão executadas:
- Um comando será enviado no formato: http: / ip_address * / forward
- Um “eco” no mesmo formato é esperado
- Uma “mensagem” audível será executada pela App: no caso: “forward”
* O IP_Address será a que você digitar no seu App. Por exemplo, se o IP address é 10.1.0.3, a mensagem completa seria: http: /10.0.1.3/forward
Você poderá utilizar qualquer mensagem, ou até mesmo deixar a mensagem vazia.
10: A App Android: Reconhecimento de voz
Os blocos acima mostram o código de reconhecimento de voz para o nosso aplicativo.
Note que, para qualquer comando de voz inserido, o resultado será um comando em letras minúsculas. Isso facilitará a descodificação do comando pelo NodeMCU.
Por exemplo, se você deseja mover o robô para a frente, uma palavra ou sentença deverá ser enviada para ser interpretada pelo NodeMCU. No exemplo abaixo, o código reconheceria qualquer uma das palavras: “forward”, “frente” ou “a frente “.
if (command == "forward" || command == "frente" || command == "a frente") { forwardMotor(); }
Assim, por exemplo quando digo “frente”, a App enviará : http: / 10.0.1.10/frente e o NodeMCU tomará o que é recebido após o “/”, a palavra “frente” que será interpretada como um comando do tipo “forward”. Com este comando, a função forwardMotor () será invocada.
11: A App Android: Manipulação de erros
Se ocorrer um erro, por exemplo, quando você diz um comando de voz não reconhecido pelo NodeMCU, o bloco acima irá gravar os detalhes do erro na última linha (Comm_Status) do App, conforme mostrado abaixo:
12: Teste final
Você poderá criar seu aplicativo passo a passo, como mostrado nas etapas anteriores ou fazer o upload do meu projeto completo (.aia) no MIT App Inventor, modificando-o de acordo a suas necessidades. Além disso, caso você não tenha experiência no desenvolvimento de aplicativos para Android, poderá executar o arquivo .apk diretamente em seu dispositivo Android.
Ambos, os arquivos .aia e .apk podem ser baixados de meu GitHub:
O video abaixo mostra alguns testes de motores utilizando-se do App:
Conclusão
Como sempre, espero que este projecto possa ajudar outros a encontrarem o seu caminho no emocionante mundo da electrónica, robótica e do IoT!
Verifique o depositário no GitHub para obter os arquivos atualizados:
Saludos desde el sur del mundo!
Até o próximo tutorial!