Eletrônica Básica e Primeiros Robôs
Nenhum conhecimento prévio necessário!
Este nível é projetado para iniciantes completos. Você vai aprender desde o básico absoluto de eletrônica até construir seus primeiros robôs funcionais.
💡 Dica: Tenha um caderno para anotações e esteja pronto para experimentar bastante!
8 horas • 3 aulas
Bem-vindo ao início da sua jornada no fascinante mundo da robótica! Um robô é, em sua essência, uma máquina programável capaz de realizar uma série de ações de forma autônoma ou semi-autônoma. A palavra "robô" foi popularizada pelo escritor tcheco Karel Čapek em sua peça de 1920, "R.U.R." (Rossum's Universal Robots), derivando da palavra tcheca robota, que significa "trabalho forçado".
Os robôs modernos são compostos por três pilares fundamentais:
Neste curso, exploraremos todos os três pilares, começando com os blocos de construção da eletrônica.
Figura 1: Diversos componentes eletrônicos que formam a base da robótica.
---
Para construir robôs, é crucial entender três conceitos básicos da eletricidade: Tensão, Corrente e Resistência.
| Conceito | Unidade | Analogia com Água |
|---|---|---|
| Tensão (V) | Volt (V) | A pressão da água em uma mangueira. É a "força" que impulsiona os elétrons. |
| Corrente (I) | Ampere (A) | O fluxo de água que passa pela mangueira. É a quantidade de elétrons em movimento. |
| Resistência (R) | Ohm (Ω) | Um estreitamento na mangueira que limita o fluxo de água. Controla a quantidade de corrente. |
A Lei de Ohm relaciona esses três conceitos: V = I * R. Esta é a lei mais fundamental da eletrônica e nos ajuda a calcular como os componentes se comportarão em um circuito.
---
Vamos conhecer alguns dos componentes mais comuns que você usará.
A protoboard é uma ferramenta que permite montar e testar circuitos eletrônicos sem a necessidade de solda. Suas conexões internas facilitam a prototipagem rápida.
Figura 2: Diagrama de uma protoboard mostrando as conexões internas das fileiras e colunas.
+ e -) são conectadas verticalmente. São usadas para distribuir a tensão (VCC) e o terra (GND) por todo o circuito.O LED é um componente que emite luz quando a corrente elétrica passa por ele. Ele é um diodo, o que significa que a corrente só pode fluir em uma direção. O terminal mais longo é o anodo (+) e o mais curto é o catodo (-).
O resistor é um componente que limita a passagem de corrente. Ele é crucial para proteger componentes sensíveis, como os LEDs, de receberem corrente excessiva e queimarem. O valor de um resistor é medido em Ohms (Ω).
Figura 3: Esquema de um circuito simples para acender um LED, mostrando a necessidade de um resistor para limitar a corrente.
---
Vamos aplicar o que aprendemos montando um circuito físico simples. Este projeto não requer programação, apenas uma fonte de energia.
Materiais Necessários:+) da protoboard e o GND à linha negativa (-). - Use um fio para conectar a outra perna do resistor à linha de alimentação positiva (+).
- Use outro fio para conectar a fileira do terminal catodo (-) do LED à linha de alimentação negativa (-).
Ao ligar a fonte de alimentação, o LED deve acender! Se não acender, verifique as conexões, a polaridade do LED (anodo/catodo) e se a fonte está funcionando.
Parabéns! Você montou seu primeiro circuito eletrônico. No próximo módulo, aprenderemos a controlar componentes como este usando programação.
8 horas • 4 aulas
_# Módulo 1.2: Introdução ao Arduino e Programação
No módulo anterior, montamos um circuito estático. Agora, vamos dar vida aos nossos projetos com o Arduino. O Arduino não é um único componente, mas uma plataforma de prototipagem eletrônica de código aberto que combina hardware e software para criar projetos interativos.
Existem muitas placas na família Arduino, mas a mais icônica é o Arduino UNO. Para este curso, focaremos no ESP32, uma placa mais poderosa com Wi-Fi e Bluetooth integrados, mas que pode ser programada da mesma forma que um Arduino.
Figura 1: Um ESP32 DevKit, uma placa poderosa e versátil que usaremos em nossos projetos.
---
Para programar o ESP32, usaremos o Arduino IDE. Siga estes passos para configurar seu ambiente:
- Abra o Arduino IDE, vá em Arquivo > Preferências.
- No campo "URLs de Gerenciadores de Placas Adicionais", cole o seguinte link:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
- Vá em Ferramentas > Placa > Gerenciador de Placas.
- Pesquise por "esp32" e instale o pacote "esp32 by Espressif Systems".
- Conecte seu ESP32 ao computador via cabo USB.
- Em Ferramentas > Placa, navegue até "ESP32 Arduino" e selecione "ESP32 Dev Module".
- Em Ferramentas > Porta, selecione a porta serial correspondente ao seu ESP32 (ex: COM3 no Windows ou /dev/ttyUSB0 no Linux).
---
Todo programa (chamado de sketch) para Arduino possui duas funções principais:
void setup() {
// Código de configuração, executado uma vez quando a placa liga ou é resetada.
}
void loop() {
// Código principal, executado repetidamente em um loop infinito.
}
setup(): Usada para inicializar configurações, como definir se um pino será de entrada ou saída.loop(): Onde a lógica principal do robô acontece. Ele lê sensores, toma decisões e controla atuadores, repetidamente.Variáveis são "caixas" na memória onde guardamos informações. Cada variável tem um tipo:
int: para números inteiros (ex: int idade = 30;)float: para números com casas decimais (ex: float pi = 3.14;)bool: para valores verdadeiro ou falso (ex: bool ledAceso = true;)String: para texto (ex: String nome = "Robô";)Os pinos de um microcontrolador podem ser configurados como entrada (para ler dados, como um botão) ou saída (para enviar sinais, como acender um LED).
pinMode(pino, MODO): Configura um pino como INPUT ou OUTPUT.digitalWrite(pino, VALOR): Escreve um valor HIGH (ligado, 5V/3.3V) ou LOW (desligado, 0V) em um pino de saída.digitalRead(pino): Lê o valor de um pino de entrada, que será HIGH ou LOW.---
Vamos criar um semáforo simples com três LEDs (vermelho, amarelo e verde) que acendem em sequência.
Materiais Necessários:GND do ESP32 à linha de alimentação negativa (-) da protoboard.-) da protoboard.- LED Verde: GPIO 25
- LED Amarelo: GPIO 26
- LED Vermelho: GPIO 27
Código do Projeto:Copie e cole este código no seu Arduino IDE, e clique no botão "Carregar" (seta para a direita).
// Define os pinos para cada LED
const int pinoLedVerde = 25;
const int pinoLedAmarelo = 26;
const int pinoLedVermelho = 27;
void setup() {
// Configura todos os pinos dos LEDs como saída
pinMode(pinoLedVerde, OUTPUT);
pinMode(pinoLedAmarelo, OUTPUT);
pinMode(pinoLedVermelho, OUTPUT);
}
void loop() {
// Sequência do semáforo
// 1. Verde aceso por 5 segundos
digitalWrite(pinoLedVerde, HIGH);
delay(5000); // Espera 5000 milissegundos (5 segundos)
digitalWrite(pinoLedVerde, LOW);
// 2. Amarelo aceso por 2 segundos
digitalWrite(pinoLedAmarelo, HIGH);
delay(2000);
digitalWrite(pinoLedAmarelo, LOW);
// 3. Vermelho aceso por 5 segundos
digitalWrite(pinoLedVermelho, HIGH);
delay(5000);
digitalWrite(pinoLedVermelho, LOW);
}
Resultado Esperado:
Após carregar o código, seu circuito se comportará como um semáforo, alternando entre os LEDs verde, amarelo e vermelho. A função delay() pausa o programa, permitindo que cada luz fique acesa por um tempo.
Você acaba de dar o primeiro passo na programação de hardware! No próximo módulo, aprenderemos a interagir com o mundo exterior usando sensores.
8 horas • 5 aulas
_# Módulo 1.3: Sensores Básicos
Se os atuadores são as "mãos" de um robô, os sensores são seus "sentidos". Sensores são componentes eletrônicos que permitem a um robô perceber o ambiente ao seu redor. Eles convertem uma propriedade física (como luz, distância ou temperatura) em um sinal elétrico que o microcontrolador pode ler e interpretar.
Neste módulo, vamos explorar um dos sensores mais populares e úteis para robôs iniciantes: o sensor de distância ultrassônico.
---
O HC-SR04 é um sensor que mede distâncias usando ondas sonoras de alta frequência (ultrassom), de forma semelhante a como um morcego ou um sonar de submarino funciona.
Figura 1: O sensor ultrassônico HC-SR04, com seus dois transdutores (emissor e receptor).
Como Funciona:
Conhecendo a velocidade do som no ar (aproximadamente 343 metros por segundo), podemos calcular a distância até o objeto.
Figura 2: Diagrama de pinagem do sensor HC-SR04.
---
Vamos construir um "alarme de ré" que acende um LED e emite um som (usando o LED da própria placa) quando um objeto se aproxima demais do sensor.
Materiais Necessários:
Figura 3: Exemplo de como conectar o sensor HC-SR04 a uma placa Arduino. A lógica para o ESP32 é similar, mas requer o divisor de tensão.
VCC e GND do sensor às saídas 5V e GND do ESP32, respectivamente.Trig do sensor ao GPIO 12 do ESP32. - Conecte o pino Echo do sensor a um ponto na protoboard.
- Nesse mesmo ponto, conecte o resistor de 1kΩ.
- Conecte a outra ponta do resistor de 1kΩ ao GPIO 13 do ESP32.
- No pino GPIO 13, conecte também o resistor de 2kΩ. A outra ponta do resistor de 2kΩ deve ser conectada ao GND.
// Define os pinos para o sensor ultrassônico
const int pinoTrig = 12;
const int pinoEcho = 13;
// Define o pino do LED embutido na placa (geralmente é o 2)
const int ledEmbutido = 2;
// Variáveis para armazenar a duração do pulso e a distância
long duracao;
int distanciaCm;
void setup() {
Serial.begin(115200); // Inicia a comunicação serial para vermos os resultados
pinMode(pinoTrig, OUTPUT);
pinMode(pinoEcho, INPUT);
pinMode(ledEmbutido, OUTPUT);
}
void loop() {
// Limpa o pino Trig
digitalWrite(pinoTrig, LOW);
delayMicroseconds(2);
// Envia um pulso de 10 microssegundos no pino Trig
digitalWrite(pinoTrig, HIGH);
delayMicroseconds(10);
digitalWrite(pinoTrig, LOW);
// Lê o tempo de retorno do pulso no pino Echo
duracao = pulseIn(pinoEcho, HIGH);
// Calcula a distância em centímetros
// Velocidade do som (343 m/s) = 0.0343 cm/µs
// A distância é o tempo / 2 (ida e volta) * velocidade
distanciaCm = duracao * 0.0343 / 2;
// Imprime a distância no Monitor Serial
Serial.print("Distância: ");
Serial.print(distanciaCm);
Serial.println(" cm");
// Lógica do alarme
if (distanciaCm < 10) {
// Se o objeto estiver a menos de 10 cm, acende o LED
digitalWrite(ledEmbutido, HIGH);
} else {
// Caso contrário, apaga o LED
digitalWrite(ledEmbutido, LOW);
}
delay(100); // Pequena pausa antes da próxima leitura
}
Resultado Esperado:
Abra o Monitor Serial (Ferramentas > Monitor Serial) com a velocidade de 115200. Você verá as leituras de distância sendo impressas. Aproxime sua mão do sensor. Quando a distância for menor que 10 cm, o LED azul embutido na sua placa ESP32 deverá acender.
Você acabou de dar "olhos" ao seu projeto! No próximo módulo, vamos aprender a fazer nosso robô se mover usando servomotores.
8 horas • 4 aulas
_# Módulo 1.4: Atuadores - Servomotores
Neste módulo, focaremos nos servomotores, que são essenciais para a robótica de precisão.
---
Um servomotor (ou simplesmente "servo") é um motor especial que permite o controle preciso de sua posição angular ou velocidade. Existem dois tipos principais que usaremos:
Este servo é projetado para girar para uma posição específica dentro de um alcance limitado, geralmente de 0 a 180 graus. Ele é ideal para aplicações que exigem controle de ângulo, como:
Visualmente idêntico ao servo padrão, este tipo é modificado para girar continuamente em 360 graus, sem um limite de posição. Em vez de controlar o ângulo, controlamos a velocidade e a direção da rotação. É a escolha perfeita para as rodas de um robô móvel.
Figura 1: O servo de rotação contínua FS90R, ideal para as rodas do nosso robô.
---
Servos são controlados por um sinal de PWM (Pulse Width Modulation), ou Modulação por Largura de Pulso. Em vez de um sinal digital simples (ligado/desligado), o PWM é um pulso que se repete em uma frequência constante (geralmente 50 Hz para servos), mas cuja largura (duração) pode ser variada.
Figura 2: Um sinal PWM com diferentes larguras de pulso (duty cycles). É essa largura que o servo interpreta.
- Um pulso de ~1000 µs (microssegundos) corresponde a 0 graus.
- Um pulso de ~1500 µs corresponde a 90 graus (centro).
- Um pulso de ~2000 µs corresponde a 180 graus.
- Um pulso de ~1300 µs corresponde à velocidade máxima em um sentido (ex: anti-horário).
- Um pulso de ~1500 µs corresponde a parado.
- Um pulso de ~1700 µs corresponde à velocidade máxima no outro sentido (ex: horário).
O ESP32 possui hardware dedicado (LEDC) para gerar sinais PWM precisos, o que o torna excelente para controlar múltiplos servos.
Figura 3: A pinagem típica de um servo, com fios para alimentação (VCC), terra (GND) e sinal (PWM).
---
Vamos testar um servo FS90R, fazendo-o girar para frente, para trás e parar, usando a biblioteca ESP32Servo.
GND da sua fonte de 5V, o GND do ESP32 e o fio marrom/preto do servo todos juntos na linha negativa (-) da protoboard. Este é o passo mais importante.5V da sua fonte externa.5V do ESP32 à saída de 5V da fonte externa.Primeiro, instale a biblioteca ESP32Servo: vá em Ferramentas > Gerenciar Bibliotecas, procure por "ESP32Servo" e instale-a.
#include <ESP32Servo.h>
// Cria um objeto Servo
Servo meuServo;
// Define o pino onde o servo está conectado
const int pinoServo = 18;
void setup() {
// Associa o objeto Servo ao pino e define os parâmetros de PWM
// (500 µs = pulso mínimo, 2500 µs = pulso máximo)
meuServo.attach(pinoServo, 500, 2500);
}
void loop() {
Serial.println("Girando para frente (velocidade máxima)");
meuServo.writeMicroseconds(1300); // Valor para girar em um sentido
delay(3000); // Gira por 3 segundos
Serial.println("Parando");
meuServo.writeMicroseconds(1500); // Valor para parar
delay(3000); // Fica parado por 3 segundos
Serial.println("Girando para trás (velocidade máxima)");
meuServo.writeMicroseconds(1700); // Valor para girar no outro sentido
delay(3000); // Gira por 3 segundos
Serial.println("Parando");
meuServo.writeMicroseconds(1500); // Valor para parar
delay(3000); // Fica parado por 3 segundos
}
Calibração:
O valor exato para parar um servo de rotação contínua pode variar ligeiramente (ex: 1480, 1510). Se o seu servo não parar completamente com 1500, ajuste este valor no código até encontrar o ponto de repouso perfeito.
O servo irá girar em um sentido por 3 segundos, parar por 3 segundos, girar no sentido oposto por 3 segundos e parar novamente, repetindo o ciclo. Você agora tem o conhecimento para dar movimento ao seu robô!
No próximo módulo, vamos juntar tudo o que aprendemos para construir nosso primeiro robô completo: o Rover de Papel!
6 horas • 3 aulas
Parabéns por chegar ao projeto final do Nível 1! Neste módulo, vamos integrar tudo o que aprendemos sobre eletrônica, programação e atuadores para construir nosso primeiro robô funcional: um Rover de Papel controlado por Wi-Fi.
Este projeto é fantástico para iniciantes porque utiliza materiais simples e acessíveis (como papelão) para o chassi, combinado com a potência do ESP32 para criar uma interface de controle web que funciona em qualquer smartphone. Vamos colocar a mão na massa!
Figura 1: Exemplo de um robô rover similar ao que vamos construir, controlado por um aplicativo web.
---
| Quantidade | Componente | Descrição | Módulo Relacionado |
|---|---|---|---|
| 1x | ESP32 DevKit | O cérebro do nosso robô. | 1.2 |
| 2x | Servo de Rotação Contínua FS90R | Os motores que moverão as rodas. | 1.4 |
| 2x | Rodas para Servo | Podem ser compradas ou feitas de papelão. | 1.4 |
| 1x | Rodízio ou Roda Boba | Para dar um terceiro ponto de apoio. | 1.1 |
| 1x | Power Bank 5V (≥ 2A) | Fonte de alimentação para o robô. | 1.1 |
| 1x | Protoboard Mini | Para organizar as conexões. | 1.1 |
| - | Fios Jumper Macho-Fêmea | Para conectar os componentes. | 1.1 |
| - | Papelão ou Cartolina Rígida | Para construir o chassi. | - |
| - | Ferramentas | Fita dupla-face, cola quente, tesoura, régua. | - |
---
Vamos criar o corpo do nosso robô. A simplicidade é a chave aqui.
Figura 2: Um gabarito técnico para o design do chassi, mostrando a posição dos servos e rodas.
---
Agora, vamos conectar os componentes eletrônicos. Preste muita atenção ao GND comum, que é essencial para o funcionamento do circuito.
Figura 3: A arquitetura do nosso robô, mostrando como a energia e os sinais fluem entre os componentes.
- Conecte o fio de sinal (amarelo/laranja) do servo esquerdo ao GPIO 18 do ESP32.
- Conecte o fio de sinal do servo direito ao GPIO 19 do ESP32.
Figura 4: Exemplo de ligação de um servo. Lembre-se de que o GND deve ser comum a todos os componentes.
---
Este código transformará seu ESP32 em um ponto de acesso Wi-Fi (Access Point). Ao se conectar a ele com seu celular, você poderá acessar uma página web com botões para controlar o robô.
Copie o código abaixo, cole no seu Arduino IDE e carregue-o para o ESP32.
#include <WiFi.h>
#include <ESP32Servo.h>
// ===== Configuração do Wi-Fi AP =====
const char* ssid = "ROBO_PAPEL";
const char* password = "12345678";
WiFiServer server(80);
// ===== Configuração dos Servos =====
Servo servoEsquerdo;
Servo servoDireito;
const int pinoServoEsquerdo = 18;
const int pinoServoDireito = 19;
// ===== Calibração e Velocidade =====
// Ajuste estes valores se os servos não pararem completamente
int paradoEsquerdo = 1500;
int paradoDireito = 1500;
int velocidade = 200; // Quão rápido o robô se move (100-400)
// ===== Funções de Movimento =====
void parar() {
servoEsquerdo.writeMicroseconds(paradoEsquerdo);
servoDireito.writeMicroseconds(paradoDireito);
}
void frente() {
servoEsquerdo.writeMicroseconds(paradoEsquerdo + velocidade);
servoDireito.writeMicroseconds(paradoDireito - velocidade);
}
void tras() {
servoEsquerdo.writeMicroseconds(paradoEsquerdo - velocidade);
servoDireito.writeMicroseconds(paradoDireito + velocidade);
}
void esquerda() {
servoEsquerdo.writeMicroseconds(paradoEsquerdo - velocidade);
servoDireito.writeMicroseconds(paradoDireito - velocidade);
}
void direita() {
servoEsquerdo.writeMicroseconds(paradoEsquerdo + velocidade);
servoDireito.writeMicroseconds(paradoDireito + velocidade);
}
// ===== Página HTML de Controle =====
String html = "<!DOCTYPE html><html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><style>" \
"body{text-align:center; font-family:sans-serif;} button{font-size:24px; padding:20px; margin:10px;}" \
".dir{display:flex; justify-content:center;} #stop{background-color:red; color:white;}" \
"</style></head><body><h1>Controle do Robô</h1>" \
"<div class=\"dir\"><button onmousedown=\"fetch(\"/frente\")\" ontouchstart=\"fetch(\"/frente\")\">▲</button></div>" \
"<div class=\"dir\"><button onmousedown=\"fetch(\"/esquerda\")\" ontouchstart=\"fetch(\"/esquerda\")\">◄</button>" \
"<button id=\"stop\" onmousedown=\"fetch(\"/parar\")\" ontouchstart=\"fetch(\"/parar\")\">■</button>" \
"<button onmousedown=\"fetch(\"/direita\")\" ontouchstart=\"fetch(\"/direita\")\">►</button></div>" \
"<div class=\"dir\"><button onmousedown=\"fetch(\"/tras\")\" ontouchstart=\"fetch(\"/tras\")\">▼</button></div>" \
"</body></html>";
void setup() {
servoEsquerdo.attach(pinoServoEsquerdo);
servoDireito.attach(pinoServoDireito);
parar();
WiFi.softAP(ssid, password);
server.begin();
}
void loop() {
WiFiClient client = server.available();
if (client) {
String req = client.readStringUntil(\'\r\');
if (req.indexOf("/frente") != -1) frente();
else if (req.indexOf("/tras") != -1) tras();
else if (req.indexOf("/esquerda") != -1) esquerda();
else if (req.indexOf("/direita") != -1) direita();
else if (req.indexOf("/parar") != -1) parar();
client.print(html);
delay(1);
}
}
---
ROBO_PAPEL e conecte-se a ela usando a senha 12345678.192.168.4.1.paradoEsquerdo e paradoDireito no código. Aumente ou diminua os valores em pequenos incrementos (ex: de 1500 para 1505, ou 1495) até que as rodas fiquem perfeitamente imóveis ao pressionar o botão de parar.
---
Você construiu seu primeiro robô! Agora, as possibilidades são infinitas. Aqui estão algumas ideias para evoluir seu projeto, que serão a base para o Nível 2 do nosso curso:
6 horas • 3 aulas
Até agora, nossos robôs funcionavam de forma independente, sem nos informar o que estava acontecendo "dentro" deles. A comunicação serial é a ponte que permite que microcontroladores como o ESP32 conversem com computadores, outros dispositivos eletrônicos e até mesmo entre si.
A palavra "serial" significa que os dados são enviados um bit por vez, em sequência, através de um ou mais fios. Isso contrasta com a comunicação "paralela", onde múltiplos bits são enviados simultaneamente através de múltiplos fios.
Imagine a comunicação serial como um túnel estreito onde os carros (bits de dados) passam um de cada vez, enquanto a comunicação paralela seria como uma rodovia de várias pistas onde os carros podem passar lado a lado.
---
A comunicação serial é fundamental para:
---
UART (Universal Asynchronous Receiver-Transmitter) é o protocolo de comunicação serial mais básico e amplamente usado. "Asynchronous" significa que não há um sinal de clock compartilhado entre os dispositivos - eles devem concordar previamente sobre a velocidade de transmissão.
Uma comunicação UART usa, no mínimo, três fios:
| Fio | Nome | Função |
|---|---|---|
| TX | Transmit (Transmitir) | Envia dados do dispositivo |
| RX | Receive (Receber) | Recebe dados para o dispositivo |
| GND | Ground (Terra) | Referência comum de tensão |
Regra de Ouro: O TX de um dispositivo deve ser conectado ao RX do outro, e vice-versa. Pense nisso como uma conversa: quando você fala (TX), a outra pessoa escuta (RX).
O Baud Rate é a velocidade de comunicação, medida em bits por segundo (bps). Ambos os dispositivos devem usar o mesmo Baud Rate. Os valores mais comuns são:
O Arduino IDE possui uma ferramenta chamada Serial Monitor que permite visualizar e enviar dados pela porta serial USB. Ela é acessada através do ícone de lupa no canto superior direito ou pelo atalho Ctrl+Shift+M.
---
Vamos começar com o exemplo mais básico: fazer o ESP32 enviar uma mensagem para o computador.
Código:void setup() {
// Inicializa a comunicação serial a 115200 bps
Serial.begin(115200);
// Aguarda a conexão serial (útil para algumas placas)
delay(1000);
Serial.println("=================================");
Serial.println(" Bem-vindo ao ESP32! ");
Serial.println(" Sistema Iniciado com Sucesso ");
Serial.println("=================================");
}
void loop() {
Serial.print("Tempo desde o início (ms): ");
Serial.println(millis()); // millis() retorna o tempo desde que a placa ligou
delay(1000); // Aguarda 1 segundo entre as mensagens
}
Como Testar:
Ferramentas > Monitor Serial)print() e println():
Serial.print(): Imprime o texto na mesma linhaSerial.println(): Imprime o texto e pula para a próxima linha (adiciona \n)---
Agora vamos fazer o inverso: enviar comandos do computador para o ESP32 controlar um LED.
Materiais:const int pinoLED = 25;
void setup() {
Serial.begin(115200);
pinMode(pinoLED, OUTPUT);
Serial.println("\n=== Sistema de Controle por Serial ===");
Serial.println("Comandos disponíveis:");
Serial.println(" ON - Liga o LED");
Serial.println(" OFF - Desliga o LED");
Serial.println(" STATUS - Mostra o estado atual");
Serial.println("=====================================\n");
}
void loop() {
// Verifica se há dados disponíveis para leitura
if (Serial.available() > 0) {
// Lê a string enviada até encontrar '\n'
String comando = Serial.readStringUntil('\n');
// Remove espaços em branco no início e fim
comando.trim();
// Converte para maiúsculas para facilitar a comparação
comando.toUpperCase();
// Processa o comando
if (comando == "ON") {
digitalWrite(pinoLED, HIGH);
Serial.println("✓ LED ligado!");
}
else if (comando == "OFF") {
digitalWrite(pinoLED, LOW);
Serial.println("✓ LED desligado!");
}
else if (comando == "STATUS") {
if (digitalRead(pinoLED) == HIGH) {
Serial.println("Estado: LED está LIGADO");
} else {
Serial.println("Estado: LED está DESLIGADO");
}
}
else {
Serial.println("✗ Comando não reconhecido: " + comando);
Serial.println("Use: ON, OFF ou STATUS");
}
}
}
Como Usar:
ON e pressione EnterOFF e STATUSSerial.available(): Retorna o número de bytes disponíveis para leituraSerial.readStringUntil('\n'): Lê caracteres até encontrar uma nova linhaString.trim(): Remove espaços em brancoString.toUpperCase(): Converte para maiúsculas---
I2C (Inter-Integrated Circuit), pronunciado "I-squared-C" ou "I-two-C", é um protocolo de comunicação que permite conectar múltiplos dispositivos usando apenas dois fios para dados.
O I2C usa um sistema de mestre-escravo (master-slave):
| Fio | Nome Completo | Função |
|---|---|---|
| SDA | Serial Data | Linha de dados bidirecional |
| SCL | Serial Clock | Linha de clock gerada pelo mestre |
| GND | Ground | Terra comum |
| VCC | Power | Alimentação (3.3V ou 5V) |
Cada dispositivo I2C tem um endereço único (geralmente de 7 bits), permitindo que o mestre especifique com qual dispositivo deseja falar. É como um sistema de apartamentos: você precisa saber o número do apartamento para tocar a campainha certa.
Pinos I2C no ESP32:---
Antes de usar um dispositivo I2C, é útil descobrir seu endereço. Vamos criar um scanner que detecta todos os dispositivos I2C conectados.
Código:#include <Wire.h>
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA = GPIO 21, SCL = GPIO 22
Serial.println("\n=== Scanner I2C ===");
Serial.println("Procurando dispositivos I2C...\n");
}
void loop() {
byte erro, endereco;
int dispositivosEncontrados = 0;
Serial.println("Escaneando...");
// Testa endereços de 1 a 127
for(endereco = 1; endereco < 127; endereco++) {
Wire.beginTransmission(endereco);
erro = Wire.endTransmission();
if (erro == 0) {
Serial.print("Dispositivo I2C encontrado no endereço 0x");
if (endereco < 16) Serial.print("0");
Serial.print(endereco, HEX);
Serial.println("!");
dispositivosEncontrados++;
}
else if (erro == 4) {
Serial.print("Erro desconhecido no endereço 0x");
if (endereco < 16) Serial.print("0");
Serial.println(endereco, HEX);
}
}
if (dispositivosEncontrados == 0) {
Serial.println("Nenhum dispositivo I2C encontrado.");
Serial.println("Verifique as conexões!");
}
else {
Serial.print("\nTotal: ");
Serial.print(dispositivosEncontrados);
Serial.println(" dispositivo(s) encontrado(s).\n");
}
delay(5000); // Aguarda 5 segundos antes de escanear novamente
}
Como Usar:
---
Vamos usar o que aprendemos para controlar um display OLED usando I2C.
Materiais:Ferramentas > Gerenciar Bibliotecas#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Configuração do display
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1 // Reset pin (ou -1 se compartilhado com ESP32)
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void setup() {
Serial.begin(115200);
// Inicializa o display
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("Falha ao inicializar o display OLED!");
while(1); // Para o programa
}
// Limpa o buffer
display.clearDisplay();
// Configurações de texto
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
// Escreve no display
display.println("Robo");
display.println("Expert");
display.setTextSize(1);
display.println("");
display.println("I2C Funcionando!");
// Envia o buffer para o display
display.display();
}
void loop() {
// Exibe um contador
static int contador = 0;
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Sistema Ativo");
display.println("");
display.setTextSize(2);
display.print("Count: ");
display.println(contador);
display.display();
contador++;
delay(1000);
}
Resultado Esperado:
O display OLED mostrará o texto "Robo Expert" e um contador que aumenta a cada segundo. Você acabou de dominar a comunicação I2C!
---
SPI (Serial Peripheral Interface) é um protocolo de comunicação serial síncrono de alta velocidade. Ele é mais rápido que o I2C, mas requer mais fios.
O SPI também usa um sistema mestre-escravo, mas com mais linhas de comunicação:
| Fio | Nome Completo | Função |
|---|---|---|
| MOSI | Master Out Slave In | Dados do mestre para o escravo |
| MISO | Master In Slave Out | Dados do escravo para o mestre |
| SCK | Serial Clock | Clock gerado pelo mestre |
| CS/SS | Chip Select / Slave Select | Seleciona qual escravo está ativo |
| Característica | I2C | SPI |
|---|---|---|
| Velocidade | Até 400 kHz (padrão) | Até 80 MHz no ESP32 |
| Fios Necessários | 2 (SDA, SCL) | 4+ (MOSI, MISO, SCK, CS) |
| Dispositivos | Muitos no mesmo barramento | 1 CS por dispositivo |
| Melhor Para | Sensores, displays simples | Cartões SD, displays TFT, módulos RF |
---
Vamos criar um sistema que grava dados de sensores em um arquivo de texto no cartão SD.
Materiais:#include <SPI.h>
#include <SD.h>
// Pino CS do módulo SD
const int CS_PIN = 5;
File arquivoDados;
void setup() {
Serial.begin(115200);
Serial.println("=== Sistema de Data Logger ===");
// Inicializa o cartão SD
if (!SD.begin(CS_PIN)) {
Serial.println("ERRO: Falha ao inicializar o cartão SD!");
Serial.println("Verifique se o cartão está inserido.");
while (1);
}
Serial.println("✓ Cartão SD inicializado com sucesso!");
// Verifica o tipo do cartão
uint8_t tipoCartao = SD.cardType();
if (tipoCartao == CARD_NONE) {
Serial.println("Nenhum cartão SD detectado!");
return;
}
Serial.print("Tipo do Cartão: ");
if (tipoCartao == CARD_MMC) Serial.println("MMC");
else if (tipoCartao == CARD_SD) Serial.println("SD");
else if (tipoCartao == CARD_SDHC) Serial.println("SDHC");
// Mostra o tamanho do cartão
uint64_t tamanhoCartao = SD.cardSize() / (1024 * 1024);
Serial.print("Tamanho do Cartão: ");
Serial.print(tamanhoCartao);
Serial.println(" MB");
Serial.println("\nIniciando gravação de dados...\n");
}
void loop() {
// Simula leituras de sensores
float temperatura = 20.0 + random(-50, 50) / 10.0; // Temperatura entre 15°C e 25°C
float umidade = 60.0 + random(-100, 100) / 10.0; // Umidade entre 50% e 70%
// Obtém o timestamp
unsigned long timestamp = millis();
// Cria string com os dados
String linha = String(timestamp) + "," +
String(temperatura, 2) + "," +
String(umidade, 2);
// Abre o arquivo para adicionar dados (append)
arquivoDados = SD.open("/dados.txt", FILE_APPEND);
if (arquivoDados) {
arquivoDados.println(linha);
arquivoDados.close();
Serial.println("✓ Dados gravados: " + linha);
} else {
Serial.println("✗ Erro ao abrir o arquivo!");
}
delay(5000); // Grava a cada 5 segundos
}
Como Usar:
dados.txt - você verá um log CSV dos dados!1234,22.34,65.12
6234,21.98,64.87
11234,22.15,65.34
Você pode abrir este arquivo no Excel ou Google Sheets para análise!
---
| Protocolo | UART | I2C | SPI |
|---|---|---|---|
| Fios | 2 (TX, RX) | 2 (SDA, SCL) | 4+ (MOSI, MISO, SCK, CS) |
| Velocidade | 9600 - 115200 bps | 100 - 400 kHz | Até 80 MHz |
| Distância | Longa (até 15m) | Curta (< 1m) | Muito curta (< 50cm) |
| Dispositivos | 1-para-1 | Múltiplos (127 max) | Múltiplos (1 CS cada) |
| Uso Típico | Debug, GPS, Bluetooth | Sensores, OLED, RTC | SD, TFT, RF, ADC rápidos |
---
Vamos integrar tudo em um projeto final que combina UART, I2C e conceitos de data logging.
Objetivo: Criar um sistema que lê dados de sensores, exibe em um display OLED (I2C) e envia telemetria para o Serial Monitor (UART).
Materiais:#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// Configuração do OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Configuração do sensor ultrassônico
const int trigPin = 26;
const int echoPin = 25;
// LED de alerta
const int ledPin = 27;
// Variáveis
float distancia = 0;
unsigned long ultimaLeitura = 0;
const int intervaloLeitura = 500; // 500ms entre leituras
void setup() {
// Inicializa Serial
Serial.begin(115200);
Serial.println("\n=== Sistema de Telemetria ===");
Serial.println("Timestamp(ms), Distancia(cm), Status");
// Inicializa pinos
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
pinMode(ledPin, OUTPUT);
// Inicializa OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println("ERRO: Display OLED não encontrado!");
while(1);
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("Sistema Ativo");
display.display();
}
void loop() {
unsigned long tempoAtual = millis();
// Realiza leitura periódica
if (tempoAtual - ultimaLeitura >= intervaloLeitura) {
ultimaLeitura = tempoAtual;
// Lê o sensor ultrassônico
distancia = lerDistancia();
// Determina o status
String status;
if (distancia < 10) {
status = "CRITICO";
digitalWrite(ledPin, HIGH);
} else if (distancia < 30) {
status = "ALERTA";
digitalWrite(ledPin, HIGH);
delay(100);
digitalWrite(ledPin, LOW);
} else {
status = "NORMAL";
digitalWrite(ledPin, LOW);
}
// Envia telemetria via Serial (UART)
Serial.print(tempoAtual);
Serial.print(", ");
Serial.print(distancia, 2);
Serial.print(", ");
Serial.println(status);
// Atualiza o display (I2C)
atualizarDisplay(distancia, status);
}
}
float lerDistancia() {
// Envia pulso
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
// Lê o eco
long duracao = pulseIn(echoPin, HIGH, 30000); // Timeout de 30ms
// Calcula a distância em cm
float dist = duracao * 0.034 / 2;
// Se o timeout foi atingido
if (duracao == 0) {
return 999; // Indica erro ou distância muito grande
}
return dist;
}
void atualizarDisplay(float dist, String status) {
display.clearDisplay();
// Título
display.setTextSize(1);
display.setCursor(0, 0);
display.println("Telemetria v1.0");
display.drawLine(0, 10, 127, 10, SSD1306_WHITE);
// Distância
display.setCursor(0, 16);
display.print("Distancia:");
display.setTextSize(2);
display.setCursor(0, 28);
if (dist < 999) {
display.print(dist, 1);
display.println(" cm");
} else {
display.println("---");
}
// Status
display.setTextSize(1);
display.setCursor(0, 50);
display.print("Status: ");
display.println(status);
display.display();
}
Resultado Esperado:
---
Parabéns! Você dominou os três principais protocolos de comunicação serial:
Estes protocolos são a base de praticamente todos os projetos de robótica e IoT. No próximo módulo, usaremos todas essas habilidades para construir nosso primeiro robô autônomo completo: o Robô Seguidor de Linha!
8 horas • Projeto Final Parte 1
Este é um dos momentos mais emocionantes do curso! Você irá construir um robô seguidor de linha, um dos projetos mais clássicos e educacionais da robótica. Este robô consegue seguir de forma autônoma uma linha preta (ou branca) desenhada no chão, usando sensores infravermelhos para "ver" o caminho.
Os robôs seguidores de linha são usados em:
---
---
O robô seguidor de linha usa um array de sensores infravermelhos posicionados na parte frontal inferior do chassi. Esses sensores detectam o contraste entre a linha e o fundo, permitindo que o robô determine sua posição relativa à linha.
Princípio de Funcionamento:| Quantidade | Componente | Especificações | Custo Aprox. |
|---|---|---|---|
| 1x | ESP32 DevKit | Microcontrolador principal | R$ 35 |
| 1x | Módulo Sensor IR 5 canais | TCRT5000 ou similar | R$ 15 |
| 1x | Ponte H L298N | Driver de motor duplo | R$ 12 |
| 2x | Motor DC com Caixa de Redução | 3-6V, 100-200 RPM | R$ 20 |
| 2x | Rodas | 65-70mm de diâmetro | R$ 10 |
| 1x | Rodízio ou Roda Boba | Para suporte dianteiro/traseiro | R$ 8 |
| 1x | Suporte de Baterias | 4x pilhas AA (6V) | R$ 5 |
| 1x | Chassi Acrílico ou MDF | 15x12cm, 3mm espessura | R$ 8 |
| - | Fios Jumper e Parafusos | Para montagem | R$ 15 |
Custo Total Aproximado: R$ 128
---
O chassi é a base estrutural do robô. Para um seguidor de linha eficiente, considere:
Dimensões Recomendadas:---
O TCRT5000 é um sensor óptico reflexivo que consiste em:
Superfície Clara (Branco): Superfície Escura (Preto):
LED IR → Alta Reflexão → Sensor LED IR → Baixa Reflexão → Sensor
Saída: HIGH (1) Saída: LOW (0)
Um módulo típico possui 5 sensores TCRT5000 em linha:
[S1] [S2] [S3] [S4] [S5]
← ↑ →
Esq Centro Dir
Saídas Digitais:
| Pino | Função | Conexão ESP32 |
|---|---|---|
| VCC | Alimentação (+5V) | 5V ou 3.3V |
| GND | Terra | GND |
| OUT1 | Sensor 1 (Esquerda) | GPIO 32 |
| OUT2 | Sensor 2 | GPIO 33 |
| OUT3 | Sensor 3 (Centro) | GPIO 25 |
| OUT4 | Sensor 4 | GPIO 26 |
| OUT5 | Sensor 5 (Direita) | GPIO 27 |
---
Uma Ponte H (H-Bridge) é um circuito eletrônico que permite controlar a direção e velocidade de motores DC. O nome vem da configuração em forma de "H" dos transistores internos.
O L298N é um driver de motor duplo muito popular que pode controlar:
| Pino | Função | Conexão |
|---|---|---|
| IN1, IN2 | Controle do Motor A | GPIOs do ESP32 |
| IN3, IN4 | Controle do Motor B | GPIOs do ESP32 |
| ENA, ENB | Enable (PWM para velocidade) | GPIOs do ESP32 ou jumper |
| OUT1, OUT2 | Saída para Motor A | Motor Esquerdo |
| OUT3, OUT4 | Saída para Motor B | Motor Direito |
| +12V | Alimentação dos motores | Bateria 6V (4xAA) |
| GND | Terra | GND comum |
| +5V | Saída regulada 5V | Pode alimentar ESP32 |
A direção do motor é controlada pelos pinos IN1 e IN2:
| IN1 | IN2 | Resultado |
|---|---|---|
| LOW | LOW | Motor PARADO |
| HIGH | LOW | Motor FRENTE |
| LOW | HIGH | Motor TRÁS |
| HIGH | HIGH | Motor FREIO |
---
Conecte o GND do ESP32, L298N e sensores juntos. Isto é crítico!
---
Antes de implementar o seguidor de linha, vamos testar os motores.
// ===== Definição dos Pinos =====
// Motor Esquerdo
const int MOTOR_ESQ_IN1 = 18;
const int MOTOR_ESQ_IN2 = 19;
const int MOTOR_ESQ_EN = 21;
// Motor Direito
const int MOTOR_DIR_IN3 = 22;
const int MOTOR_DIR_IN4 = 23;
const int MOTOR_DIR_EN = 12;
// Configuração PWM
const int PWM_FREQ = 1000; // 1 kHz
const int PWM_RESOLUTION = 8; // 8 bits (0-255)
const int PWM_CHANNEL_ESQ = 0;
const int PWM_CHANNEL_DIR = 1;
void setup() {
Serial.begin(115200);
Serial.println("=== Teste de Motores ===");
// Configura pinos como saída
pinMode(MOTOR_ESQ_IN1, OUTPUT);
pinMode(MOTOR_ESQ_IN2, OUTPUT);
pinMode(MOTOR_DIR_IN3, OUTPUT);
pinMode(MOTOR_DIR_IN4, OUTPUT);
// Configura PWM
ledcSetup(PWM_CHANNEL_ESQ, PWM_FREQ, PWM_RESOLUTION);
ledcSetup(PWM_CHANNEL_DIR, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(MOTOR_ESQ_EN, PWM_CHANNEL_ESQ);
ledcAttachPin(MOTOR_DIR_EN, PWM_CHANNEL_DIR);
}
void loop() {
Serial.println("Teste: FRENTE");
frente(200);
delay(2000);
Serial.println("Teste: PARAR");
parar();
delay(1000);
Serial.println("Teste: TRÁS");
tras(200);
delay(2000);
Serial.println("Teste: PARAR");
parar();
delay(1000);
Serial.println("Teste: GIRAR DIREITA");
girarDireita(180);
delay(2000);
Serial.println("Teste: PARAR");
parar();
delay(1000);
Serial.println("Teste: GIRAR ESQUERDA");
girarEsquerda(180);
delay(2000);
Serial.println("Teste: PARAR");
parar();
delay(2000);
}
// ===== Funções de Movimento =====
void frente(int velocidade) {
// Motor Esquerdo: Frente
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
// Motor Direito: Frente
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void tras(int velocidade) {
// Motor Esquerdo: Trás
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
// Motor Direito: Trás
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarDireita(int velocidade) {
// Motor Esquerdo: Frente
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
// Motor Direito: Trás
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarEsquerda(int velocidade) {
// Motor Esquerdo: Trás
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
// Motor Direito: Frente
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void parar() {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 0);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 0);
}
Teste este código antes de prosseguir! Certifique-se de que o robô se move corretamente em todas as direções.
---
Agora vamos implementar o algoritmo de seguimento de linha!
// ===== Pinos dos Motores =====
const int MOTOR_ESQ_IN1 = 18;
const int MOTOR_ESQ_IN2 = 19;
const int MOTOR_ESQ_EN = 21;
const int MOTOR_DIR_IN3 = 22;
const int MOTOR_DIR_IN4 = 23;
const int MOTOR_DIR_EN = 12;
// ===== Pinos dos Sensores IR =====
const int SENSOR_1 = 32; // Esquerda extrema
const int SENSOR_2 = 33; // Esquerda
const int SENSOR_3 = 25; // Centro
const int SENSOR_4 = 26; // Direita
const int SENSOR_5 = 27; // Direita extrema
// ===== Configurações PWM =====
const int PWM_FREQ = 1000;
const int PWM_RESOLUTION = 8;
const int PWM_CHANNEL_ESQ = 0;
const int PWM_CHANNEL_DIR = 1;
// ===== Parâmetros de Velocidade =====
const int VELOCIDADE_BASE = 150; // Velocidade quando na linha reta
const int VELOCIDADE_CURVA = 100; // Velocidade ao fazer curvas
const int VELOCIDADE_CORRECAO = 180; // Velocidade para correções rápidas
// ===== Variáveis dos Sensores =====
int s1, s2, s3, s4, s5;
void setup() {
Serial.begin(115200);
Serial.println("=== Robô Seguidor de Linha ===");
// Configura pinos dos motores
pinMode(MOTOR_ESQ_IN1, OUTPUT);
pinMode(MOTOR_ESQ_IN2, OUTPUT);
pinMode(MOTOR_DIR_IN3, OUTPUT);
pinMode(MOTOR_DIR_IN4, OUTPUT);
// Configura PWM
ledcSetup(PWM_CHANNEL_ESQ, PWM_FREQ, PWM_RESOLUTION);
ledcSetup(PWM_CHANNEL_DIR, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(MOTOR_ESQ_EN, PWM_CHANNEL_ESQ);
ledcAttachPin(MOTOR_DIR_EN, PWM_CHANNEL_DIR);
// Configura pinos dos sensores
pinMode(SENSOR_1, INPUT);
pinMode(SENSOR_2, INPUT);
pinMode(SENSOR_3, INPUT);
pinMode(SENSOR_4, INPUT);
pinMode(SENSOR_5, INPUT);
Serial.println("Aguardando 3 segundos...");
delay(3000);
Serial.println("INICIANDO!");
}
void loop() {
// Lê os sensores (0 = preto/linha, 1 = branco/fundo)
s1 = digitalRead(SENSOR_1);
s2 = digitalRead(SENSOR_2);
s3 = digitalRead(SENSOR_3);
s4 = digitalRead(SENSOR_4);
s5 = digitalRead(SENSOR_5);
// Debug (envie para Serial Monitor)
Serial.print(s1); Serial.print(" ");
Serial.print(s2); Serial.print(" ");
Serial.print(s3); Serial.print(" ");
Serial.print(s4); Serial.print(" ");
Serial.println(s5);
// ===== LÓGICA DE DECISÃO =====
// Caso 1: Sensor central na linha - SEGUIR RETO
if (s3 == 0) {
frente(VELOCIDADE_BASE);
Serial.println("Ação: RETO");
}
// Caso 2: Sensores centrais e à direita na linha - LEVE CURVA À DIREITA
else if (s3 == 0 && s4 == 0) {
curvaLeveDireita();
Serial.println("Ação: CURVA LEVE DIREITA");
}
// Caso 3: Sensores centrais e à esquerda na linha - LEVE CURVA À ESQUERDA
else if (s2 == 0 && s3 == 0) {
curvaLeveEsquerda();
Serial.println("Ação: CURVA LEVE ESQUERDA");
}
// Caso 4: Apenas sensor direito na linha - CURVA FORTE DIREITA
else if (s4 == 0 || s5 == 0) {
curvaForteDireita();
Serial.println("Ação: CURVA FORTE DIREITA");
}
// Caso 5: Apenas sensor esquerdo na linha - CURVA FORTE ESQUERDA
else if (s1 == 0 || s2 == 0) {
curvaForteEsquerda();
Serial.println("Ação: CURVA FORTE ESQUERDA");
}
// Caso 6: Todos os sensores no branco - PERDEU A LINHA
else if (s1 == 1 && s2 == 1 && s3 == 1 && s4 == 1 && s5 == 1) {
frente(VELOCIDADE_CURVA);
Serial.println("Ação: PROCURANDO LINHA...");
}
// Caso 7: Todos os sensores no preto - CRUZAMENTO OU LINHA GROSSA
else if (s1 == 0 && s2 == 0 && s3 == 0 && s4 == 0 && s5 == 0) {
frente(VELOCIDADE_BASE);
Serial.println("Ação: CRUZAMENTO - SEGUIR RETO");
}
// Pequeno delay para estabilidade
delay(10);
}
// ===== FUNÇÕES DE MOVIMENTO =====
void frente(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void curvaLeveDireita() {
// Reduz velocidade do motor direito
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, VELOCIDADE_BASE);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, VELOCIDADE_CURVA);
}
void curvaLeveEsquerda() {
// Reduz velocidade do motor esquerdo
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, VELOCIDADE_CURVA);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, VELOCIDADE_BASE);
}
void curvaForteDireita() {
// Motor esquerdo frente, direito parado ou trás
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, VELOCIDADE_CORRECAO);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, VELOCIDADE_CURVA);
}
void curvaForteEsquerda() {
// Motor direito frente, esquerdo parado ou trás
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, VELOCIDADE_CURVA);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, VELOCIDADE_CORRECAO);
}
void parar() {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 0);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 0);
}
---
Se o robô estiver:
Muito Lento:VELOCIDADE_BASE (máximo 255)VELOCIDADE_CORRECAOVELOCIDADE_CURVAVELOCIDADE_BASEVELOCIDADE_CORRECAOCrie uma pista simples usando:
---
Adicione detecção de quando todos os sensores detectam preto (cruzamento) e faça o robô:
Implemente um controlador PID (Proporcional-Integral-Derivativo) para um seguimento mais suave:
float kP = 25; // Ganho proporcional
float kD = 15; // Ganho derivativo
int erroAnterior = 0;
void seguirLinhaPID() {
// Calcula o erro de posição
int erro = calcularErro();
// Termo Proporcional
float P = erro * kP;
// Termo Derivativo
float D = (erro - erroAnterior) * kD;
erroAnterior = erro;
// Ajuste de velocidade
int ajuste = P + D;
int velEsq = constrain(VELOCIDADE_BASE + ajuste, 0, 255);
int velDir = constrain(VELOCIDADE_BASE - ajuste, 0, 255);
moverMotores(velEsq, velDir);
}
int calcularErro() {
// -2: muito à esquerda, 0: centralizado, +2: muito à direita
if (s1 == 0) return -2;
if (s2 == 0) return -1;
if (s3 == 0) return 0;
if (s4 == 0) return 1;
if (s5 == 0) return 2;
return 0;
}
Adicione um botão que permite alternar entre:
Adicione um display OLED que mostra:
---
Parabéns! Você construiu um robô autônomo completo que:
Este projeto ensinou conceitos fundamentais de:
No próximo módulo, vamos elevar o nível construindo um Robô Desviador de Obstáculos que navega livremente evitando colisões!
8 horas • Projeto Final Parte 2
Neste módulo culminante, você construirá um robô desviador de obstáculos - um robô móvel autônomo capaz de navegar em ambientes desconhecidos, detectando e evitando obstáculos em tempo real. Este é um dos projetos mais empolgantes da robótica, pois o robô precisa "pensar" e tomar decisões sozinho!
Robôs desviadores de obstáculos são usados em:
---
---
Você já foi apresentado ao HC-SR04 no Módulo 3. Agora vamos dominar completamente este sensor e usá-lo para navegação real.
| Parâmetro | Valor |
|---|---|
| Tensão de Operação | 5V |
| Corrente | 15 mA |
| Alcance | 2 cm a 400 cm |
| Precisão | ±3 mm |
| Ângulo de Medição | 15° (cone) |
| Frequência do Ultrassom | 40 kHz |
Vamos criar uma função robusta para ler distâncias:
const int TRIG_PIN = 26;
const int ECHO_PIN = 25;
// Constantes físicas
const float VELOCIDADE_SOM = 0.0343; // cm/µs (343 m/s = 0.0343 cm/µs)
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
}
float lerDistanciaUltrassonico() {
// Garante que o pino TRIG está LOW
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
// Envia pulso de 10µs
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Lê o tempo do pulso de eco
// Timeout de 30ms (equivale a aproximadamente 5m)
long duracao = pulseIn(ECHO_PIN, HIGH, 30000);
// Se timeout, retorna valor indicando erro
if (duracao == 0) {
return -1; // Nada detectado ou fora de alcance
}
// Calcula distância em cm
// Distância = (Tempo * Velocidade do Som) / 2
// Dividimos por 2 porque o som vai e volta
float distancia = (duracao * VELOCIDADE_SOM) / 2.0;
return distancia;
}
void loop() {
float dist = lerDistanciaUltrassonico();
if (dist > 0) {
Serial.print("Distância: ");
Serial.print(dist);
Serial.println(" cm");
} else {
Serial.println("Erro na leitura ou sem obstáculo detectável");
}
delay(100); // Aguarda 100ms entre leituras
}
Sensores ultrassônicos podem ter leituras instáveis. Vamos implementar um filtro de média móvel:
const int NUM_LEITURAS = 5;
float leituras[NUM_LEITURAS];
int indiceLeitura = 0;
float lerDistanciaFiltrada() {
// Faz uma nova leitura
float novaLeitura = lerDistanciaUltrassonico();
// Armazena no array circular
leituras[indiceLeitura] = novaLeitura;
indiceLeitura = (indiceLeitura + 1) % NUM_LEITURAS;
// Calcula a média (ignorando valores inválidos)
float soma = 0;
int contadorValidos = 0;
for (int i = 0; i < NUM_LEITURAS; i++) {
if (leituras[i] > 0) { // Ignora leituras com erro
soma += leituras[i];
contadorValidos++;
}
}
if (contadorValidos > 0) {
return soma / contadorValidos;
} else {
return -1; // Todas as leituras inválidas
}
}
---
Vamos organizar o comportamento do robô usando uma Máquina de Estados Finitos (FSM - Finite State Machine). Este é um padrão de design fundamental em robótica.
Estados do Robô:┌─────────────┐
│ AVANÇAR │ ← Estado inicial
└──────┬──────┘
│ Obstáculo detectado
↓
┌─────────────┐
│ PARAR │
└──────┬──────┘
│ Avalia situação
↓
┌─────────────┐
│ DECIDIR │ ← Verifica direita/esquerda
└──────┬──────┘
│ Escolhe direção
↓
┌─────────────┐
│ GIRAR │
└──────┬──────┘
│ Giro completo
↓
(volta para AVANÇAR)
Use os mesmos componentes do Módulo 7 (seguidor de linha), mas:
Substitua:---
O sensor ultrassônico fica fixo apontando para frente.
Posicionamento:O sensor pode girar para "olhar" para os lados.
Montagem:---
// ===== Pinos dos Motores =====
const int MOTOR_ESQ_IN1 = 18;
const int MOTOR_ESQ_IN2 = 19;
const int MOTOR_ESQ_EN = 21;
const int MOTOR_DIR_IN3 = 22;
const int MOTOR_DIR_IN4 = 23;
const int MOTOR_DIR_EN = 12;
// ===== Pinos do Sensor Ultrassônico =====
const int TRIG_PIN = 26;
const int ECHO_PIN = 25;
// ===== Configurações PWM =====
const int PWM_FREQ = 1000;
const int PWM_RESOLUTION = 8;
const int PWM_CHANNEL_ESQ = 0;
const int PWM_CHANNEL_DIR = 1;
// ===== Parâmetros de Navegação =====
const int DISTANCIA_SEGURA = 30; // cm - distância mínima antes de desviar
const int VELOCIDADE_NORMAL = 180; // Velocidade de cruzeiro
const int VELOCIDADE_LENTA = 120; // Velocidade ao se aproximar de obstáculos
const int TEMPO_GIRO_90_GRAUS = 500; // ms - ajuste conforme seu robô
// ===== Estados da Máquina de Estados =====
enum Estado {
AVANCAR,
PARAR,
GIRAR_DIREITA,
GIRAR_ESQUERDA,
RE
};
Estado estadoAtual = AVANCAR;
void setup() {
Serial.begin(115200);
Serial.println("=== Robô Desviador de Obstáculos ===");
// Configura motores
pinMode(MOTOR_ESQ_IN1, OUTPUT);
pinMode(MOTOR_ESQ_IN2, OUTPUT);
pinMode(MOTOR_DIR_IN3, OUTPUT);
pinMode(MOTOR_DIR_IN4, OUTPUT);
ledcSetup(PWM_CHANNEL_ESQ, PWM_FREQ, PWM_RESOLUTION);
ledcSetup(PWM_CHANNEL_DIR, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(MOTOR_ESQ_EN, PWM_CHANNEL_ESQ);
ledcAttachPin(MOTOR_DIR_EN, PWM_CHANNEL_DIR);
// Configura sensor
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
Serial.println("Robô pronto!");
delay(2000);
}
void loop() {
// Lê a distância
float distancia = lerDistancia();
// Exibe no Serial Monitor
Serial.print("Distância: ");
Serial.print(distancia);
Serial.print(" cm | Estado: ");
// ===== MÁQUINA DE ESTADOS =====
switch(estadoAtual) {
case AVANCAR:
Serial.println("AVANÇAR");
if (distancia > 0 && distancia < DISTANCIA_SEGURA) {
// Obstáculo próximo - parar e decidir
estadoAtual = PARAR;
} else if (distancia > DISTANCIA_SEGURA && distancia < DISTANCIA_SEGURA * 2) {
// Obstáculo à distância média - reduzir velocidade
frente(VELOCIDADE_LENTA);
} else {
// Caminho livre - velocidade normal
frente(VELOCIDADE_NORMAL);
}
break;
case PARAR:
Serial.println("PARAR");
parar();
delay(300);
// Decide para qual lado girar (aleatório nesta versão simples)
if (random(0, 2) == 0) {
estadoAtual = GIRAR_DIREITA;
} else {
estadoAtual = GIRAR_ESQUERDA;
}
break;
case GIRAR_DIREITA:
Serial.println("GIRAR DIREITA");
girarDireita(200);
delay(TEMPO_GIRO_90_GRAUS);
estadoAtual = AVANCAR;
break;
case GIRAR_ESQUERDA:
Serial.println("GIRAR ESQUERDA");
girarEsquerda(200);
delay(TEMPO_GIRO_90_GRAUS);
estadoAtual = AVANCAR;
break;
case RE:
Serial.println("RÉ");
tras(180);
delay(500);
estadoAtual = GIRAR_DIREITA; // Depois de dar ré, gira
break;
}
delay(50); // Pequeno delay para estabilidade
}
// ===== FUNÇÃO DE LEITURA DO SENSOR =====
float lerDistancia() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duracao = pulseIn(ECHO_PIN, HIGH, 30000);
if (duracao == 0) {
return 400; // Retorna valor alto se não detectar nada
}
float distancia = (duracao * 0.0343) / 2.0;
return distancia;
}
// ===== FUNÇÕES DE MOVIMENTO =====
void frente(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void tras(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarDireita(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarEsquerda(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void parar() {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 0);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 0);
}
---
Agora vamos fazer o robô "olhar" para os lados antes de decidir para onde girar!
#include <ESP32Servo.h>
// ===== Pinos dos Motores =====
const int MOTOR_ESQ_IN1 = 18;
const int MOTOR_ESQ_IN2 = 19;
const int MOTOR_ESQ_EN = 21;
const int MOTOR_DIR_IN3 = 22;
const int MOTOR_DIR_IN4 = 23;
const int MOTOR_DIR_EN = 12;
// ===== Pinos do Sensor Ultrassônico =====
const int TRIG_PIN = 26;
const int ECHO_PIN = 25;
// ===== Pino do Servo =====
const int SERVO_PIN = 13;
Servo servoSensor;
// ===== Ângulos do Servo =====
const int ANGULO_CENTRO = 90;
const int ANGULO_DIREITA = 30;
const int ANGULO_ESQUERDA = 150;
// ===== Configurações PWM =====
const int PWM_FREQ = 1000;
const int PWM_RESOLUTION = 8;
const int PWM_CHANNEL_ESQ = 0;
const int PWM_CHANNEL_DIR = 1;
// ===== Parâmetros de Navegação =====
const int DISTANCIA_SEGURA = 35;
const int VELOCIDADE_NORMAL = 180;
const int TEMPO_GIRO_90_GRAUS = 500;
// ===== Estados =====
enum Estado {
AVANCAR,
ESCANEAR,
GIRAR_DIREITA,
GIRAR_ESQUERDA,
RE
};
Estado estadoAtual = AVANCAR;
void setup() {
Serial.begin(115200);
Serial.println("=== Robô Inteligente com Pan-Tilt ===");
// Motores
pinMode(MOTOR_ESQ_IN1, OUTPUT);
pinMode(MOTOR_ESQ_IN2, OUTPUT);
pinMode(MOTOR_DIR_IN3, OUTPUT);
pinMode(MOTOR_DIR_IN4, OUTPUT);
ledcSetup(PWM_CHANNEL_ESQ, PWM_FREQ, PWM_RESOLUTION);
ledcSetup(PWM_CHANNEL_DIR, PWM_FREQ, PWM_RESOLUTION);
ledcAttachPin(MOTOR_ESQ_EN, PWM_CHANNEL_ESQ);
ledcAttachPin(MOTOR_DIR_EN, PWM_CHANNEL_DIR);
// Sensor
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
// Servo
servoSensor.attach(SERVO_PIN);
servoSensor.write(ANGULO_CENTRO);
Serial.println("Sistema pronto!");
delay(2000);
}
void loop() {
// Sensor olha para frente
servoSensor.write(ANGULO_CENTRO);
delay(100);
float distanciaFrente = lerDistancia();
Serial.print("Frente: ");
Serial.print(distanciaFrente);
Serial.print(" cm | Estado: ");
// ===== MÁQUINA DE ESTADOS =====
switch(estadoAtual) {
case AVANCAR:
Serial.println("AVANÇAR");
if (distanciaFrente > 0 && distanciaFrente < DISTANCIA_SEGURA) {
estadoAtual = ESCANEAR;
} else {
frente(VELOCIDADE_NORMAL);
}
break;
case ESCANEAR:
Serial.println("ESCANEAR");
parar();
delay(200);
// Olha para a direita
servoSensor.write(ANGULO_DIREITA);
delay(500); // Aguarda o servo se posicionar
float distanciaDireita = lerDistancia();
Serial.print(" → Direita: ");
Serial.print(distanciaDireita);
Serial.println(" cm");
// Olha para a esquerda
servoSensor.write(ANGULO_ESQUERDA);
delay(500);
float distanciaEsquerda = lerDistancia();
Serial.print(" → Esquerda: ");
Serial.print(distanciaEsquerda);
Serial.println(" cm");
// Volta para o centro
servoSensor.write(ANGULO_CENTRO);
delay(300);
// DECISÃO INTELIGENTE
if (distanciaDireita < 15 && distanciaEsquerda < 15) {
// Ambos os lados bloqueados - dar ré
Serial.println(" → Decisão: DAR RÉ");
estadoAtual = RE;
}
else if (distanciaDireita > distanciaEsquerda) {
// Direita está mais livre
Serial.println(" → Decisão: GIRAR DIREITA");
estadoAtual = GIRAR_DIREITA;
}
else {
// Esquerda está mais livre
Serial.println(" → Decisão: GIRAR ESQUERDA");
estadoAtual = GIRAR_ESQUERDA;
}
break;
case GIRAR_DIREITA:
Serial.println("GIRAR DIREITA");
girarDireita(200);
delay(TEMPO_GIRO_90_GRAUS);
parar();
estadoAtual = AVANCAR;
break;
case GIRAR_ESQUERDA:
Serial.println("GIRAR ESQUERDA");
girarEsquerda(200);
delay(TEMPO_GIRO_90_GRAUS);
parar();
estadoAtual = AVANCAR;
break;
case RE:
Serial.println("RÉ");
tras(180);
delay(800);
parar();
// Após dar ré, gira 180 graus
girarDireita(200);
delay(TEMPO_GIRO_90_GRAUS * 2);
parar();
estadoAtual = AVANCAR;
break;
}
delay(50);
}
// ===== FUNÇÃO DE LEITURA =====
float lerDistancia() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duracao = pulseIn(ECHO_PIN, HIGH, 30000);
if (duracao == 0) {
return 400;
}
return (duracao * 0.0343) / 2.0;
}
// ===== FUNÇÕES DE MOVIMENTO =====
void frente(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void tras(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarDireita(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, HIGH);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void girarEsquerda(int velocidade) {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, HIGH);
ledcWrite(PWM_CHANNEL_ESQ, velocidade);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, velocidade);
}
void parar() {
digitalWrite(MOTOR_ESQ_IN1, LOW);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 0);
digitalWrite(MOTOR_DIR_IN3, LOW);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 0);
}
---
Para que o robô gire exatamente 90°:
void setup() {
// Configurações de motor aqui
}
void loop() {
Serial.println("Girando 90° para direita...");
girarDireita(200);
delay(500); // Ajuste este valor
parar();
delay(5000); // Espera 5 segundos
Serial.println("Girando 90° para esquerda...");
girarEsquerda(200);
delay(500); // Ajuste este valor
parar();
delay(5000);
}
delay() até o giro ser precisoSe o robô estiver:
Batendo em obstáculos:DISTANCIA_SEGURA (ex: de 30 para 40 cm)DISTANCIA_SEGURA (ex: de 30 para 20 cm)Adicione um contador para detectar quando o robô está preso:
int tentativasGiro = 0;
const int MAX_TENTATIVAS = 3;
// No estado ESCANEAR, adicione:
if (estadoAtual == ESCANEAR) {
tentativasGiro++;
if (tentativasGiro >= MAX_TENTATIVAS) {
// Está preso - estratégia de escape!
Serial.println("ROBÔ PRESO - MODO ESCAPE");
tras(200);
delay(1000);
girarDireita(200);
delay(TEMPO_GIRO_90_GRAUS * 3); // Gira 270°
tentativasGiro = 0;
estadoAtual = AVANCAR;
}
}
// No estado AVANCAR, resete o contador:
if (estadoAtual == AVANCAR && distanciaFrente > DISTANCIA_SEGURA) {
tentativasGiro = 0;
}
---
Este é um algoritmo clássico que sempre mantém uma parede à direita (ou esquerda) do robô:
Regra de Ouro: "Sempre mantém a mão direita na parede"
void navegarLabirinto() {
servoSensor.write(ANGULO_DIREITA);
delay(200);
float distDireita = lerDistancia();
servoSensor.write(ANGULO_CENTRO);
delay(200);
float distFrente = lerDistancia();
const int DIST_PAREDE = 25; // Distância ideal da parede
// Lógica de seguir parede direita
if (distFrente < 30) {
// Obstáculo à frente - girar esquerda
parar();
girarEsquerda(180);
delay(TEMPO_GIRO_90_GRAUS);
}
else if (distDireita > DIST_PAREDE + 10) {
// Muito longe da parede - corrigir para direita
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 200);
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 150); // Motor direito mais lento
}
else if (distDireita < DIST_PAREDE - 10) {
// Muito perto da parede - corrigir para esquerda
digitalWrite(MOTOR_ESQ_IN1, HIGH);
digitalWrite(MOTOR_ESQ_IN2, LOW);
ledcWrite(PWM_CHANNEL_ESQ, 150); // Motor esquerdo mais lento
digitalWrite(MOTOR_DIR_IN3, HIGH);
digitalWrite(MOTOR_DIR_IN4, LOW);
ledcWrite(PWM_CHANNEL_DIR, 200);
}
else {
// Distância ideal - seguir reto
frente(180);
}
}
┌─────────────────┐
│ START │
│ │ │
│ │ ┌───────┤
│ │ │ │
│ └────┤ │
│ │ ┌───┤
│ │ │ │
│ └───┤ E │
│ │ N │
│ │ D │
└─────────────┴───┘
---
Adicione um botão para alternar entre modos:
const int BOTAO_PIN = 14;
enum Modo {
EXPLORADOR, // Navegação livre
SEGUIR_PAREDE, // Algoritmo de parede
RETORNAR_BASE // Volta para o ponto inicial
};
Modo modoAtual = EXPLORADOR;
void setup() {
pinMode(BOTAO_PIN, INPUT_PULLUP);
// ... resto do setup
}
void loop() {
// Verifica se o botão foi pressionado
if (digitalRead(BOTAO_PIN) == LOW) {
modoAtual = (Modo)((modoAtual + 1) % 3);
Serial.print("Modo alterado para: ");
Serial.println(modoAtual);
delay(500); // Debounce
}
switch(modoAtual) {
case EXPLORADOR:
navegarLivremente();
break;
case SEGUIR_PAREDE:
navegarLabirinto();
break;
case RETORNAR_BASE:
retornarBase();
break;
}
}
Mantenha registro dos movimentos para criar um "mapa mental":
const int MAX_MOVIMENTOS = 100;
char historico[MAX_MOVIMENTOS];
int indiceHistorico = 0;
void registrarMovimento(char movimento) {
// 'F' = Frente, 'D' = Direita, 'E' = Esquerda, 'R' = Ré
if (indiceHistorico < MAX_MOVIMENTOS) {
historico[indiceHistorico] = movimento;
indiceHistorico++;
}
}
void imprimirMapa() {
Serial.println("=== Mapa de Movimentos ===");
for (int i = 0; i < indiceHistorico; i++) {
Serial.print(historico[i]);
if ((i + 1) % 20 == 0) Serial.println(); // Quebra de linha a cada 20
}
Serial.println();
}
Envie dados do robô para um dashboard web:
#include <WiFi.h>
const char* ssid = "ROBO_EXPLORER";
WiFiServer server(80);
void enviarTelemetria() {
WiFiClient client = server.available();
if (client) {
String dados = "Distancia:" + String(lerDistancia()) +
",Estado:" + String(estadoAtual) +
",Velocidade:" + String(VELOCIDADE_NORMAL);
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println();
client.println("{\"" + dados + "\"}");
client.stop();
}
}
Adicione sensores IR apontando para baixo para detectar quedas:
const int SENSOR_BORDA_ESQ = 34;
const int SENSOR_BORDA_DIR = 35;
void verificarBordas() {
if (digitalRead(SENSOR_BORDA_ESQ) == HIGH ||
digitalRead(SENSOR_BORDA_DIR) == HIGH) {
// Borda detectada!
Serial.println("ALERTA: BORDA DETECTADA!");
parar();
tras(200);
delay(500);
girarDireita(200);
delay(TEMPO_GIRO_90_GRAUS * 2); // 180°
}
}
---
float lerTensaoBateria() {
// ESP32 ADC no pino 36 (VP)
int leitura = analogRead(36);
float tensao = (leitura / 4095.0) * 3.3 * 2; // Divisor de tensão 1:1
return tensao;
}
void verificarBateria() {
float tensao = lerTensaoBateria();
if (tensao < 5.5) { // Bateria fraca (< 5.5V)
Serial.println("BATERIA FRACA!");
VELOCIDADE_NORMAL = 120; // Reduz velocidade
}
}
---
PARABÉNS! Você completou o Nível 1 do curso de Robótica!
✓ Domínio de eletrônica básica (lei de Ohm, componentes)
✓ Programação em C/C++ para microcontroladores
✓ Uso de sensores (ultrassônico, IR, temperatura)
✓ Controle de atuadores (LEDs, motores DC, servos)
✓ Comunicação serial (UART, I2C, SPI)
✓ Construção de dois robôs autônomos completos
✓ Algoritmos de navegação e controle
No Nível 2, você aprenderá:
---
---
Você agora tem as fundações sólidas para construir praticamente qualquer projeto de robótica! Continue experimentando, quebrando coisas (literalmente, às vezes), aprendendo e, acima de tudo, se divertindo.
Bem-vindo à comunidade de criadores de robôs! 🤖
Ao finalizar o Nível 1, você terá construído robôs funcionais e estará pronto para desafios mais avançados!