CodificaLab

Fazendo Funcionar

Fazendo o Acebott girar

Published by

on

Um dos primeiros experimentos interessantes com o robô móvel Acebott é fazê-lo girar sobre o próprio eixo. À primeira vista, o programa parece muito simples: inicializa os motores, manda o robô girar durante um pequeno intervalo de tempo e depois para. Contudo, por trás dessas poucas linhas existe uma organização importante feita pela biblioteca vehicle, especialmente nos arquivos vehicle.h e vehicle.cpp.

O programa utilizado é o seguinte:

#include <vehicle.h>
vehicle myCar;
void setup() {
myCar.Init();
myCar.Move(Clockwise, 255);
delay(750);
myCar.Move(Stop, 0);
}
void loop() {
}

Esse código mostra uma ideia muito comum em robótica móvel: esconder a complexidade do acionamento dos motores dentro de uma biblioteca. Assim, quem está programando não precisa controlar diretamente cada motor, cada pino de direção e cada sinal PWM. Basta dizer ao robô qual movimento deve ser executado e com qual velocidade.

No arquivo vehicle.h, a biblioteca define a classe vehicle e declara as principais funções usadas no programa. A função Init() é responsável pela preparação inicial do robô, enquanto a função Move() recebe o tipo de movimento e o valor de velocidade. Também é nesse arquivo que aparecem os nomes simbólicos dos movimentos, como Clockwise e Stop. Esses nomes tornam o programa mais legível, pois o usuário escreve uma intenção de movimento, e não uma sequência de comandos elétricos para os motores.

Já no arquivo vehicle.cpp, essas funções são efetivamente implementadas. É nele que a biblioteca traduz comandos de alto nível, como:

myCar.Move(Clockwise, 255);

em sinais aplicados aos motores do robô. Essa é a passagem mais importante para compreender o comportamento do Acebott, porque o movimento de giro não acontece por causa de um “motor de rotação” específico. Ele acontece pela combinação coordenada dos quatro motores das rodas.

No caso do movimento Clockwise, a função Move() configura os motores de modo que um lado do robô gire em um sentido e o outro lado gire no sentido oposto. Em termos simples, as rodas do lado esquerdo e as rodas do lado direito recebem comandos contrários. Essa diferença de sentidos cria um momento de rotação em torno do centro do robô, fazendo com que ele gire praticamente sobre o próprio eixo.

A ideia pode ser entendida assim: se as rodas da esquerda empurram o robô para frente e as rodas da direita empurram para trás, o robô não avança em linha reta. As forças se combinam para produzir uma rotação. Se a combinação for invertida, o giro ocorre no sentido contrário. Em robôs com rodas mecanum, como no Acebott, essa lógica também pode envolver a disposição diagonal das rodas, mas o princípio geral continua sendo a aplicação de velocidades e sentidos diferentes para gerar uma rotação controlada.

O valor 255 passado para a função Move() representa a intensidade do movimento. Em placas como o ESP32 e em bibliotecas didáticas de robótica, esse valor normalmente está associado ao controle PWM dos motores. Quanto maior o valor, maior tende a ser a velocidade aplicada. Como 255 é o valor máximo usual em uma escala de 8 bits, o comando faz o robô girar com velocidade elevada:

myCar.Move(Clockwise, 255);

A duração do giro é definida pela instrução:

delay(750);

Isso significa que o robô permanece executando o movimento de giro por 750 milissegundos, ou seja, 0,75 segundo. O ângulo final de rotação não é calculado diretamente pelo programa. Ele depende de fatores práticos, como carga da bateria, atrito do piso, peso do robô, velocidade real dos motores e pequenas diferenças mecânicas entre as rodas. Por isso, esse valor deve ser ajustado experimentalmente. Se o robô girar pouco, aumenta-se o tempo. Se girar demais, diminui-se o tempo.

Depois desse intervalo, o programa executa:

myCar.Move(Stop, 0);

Esse comando envia à biblioteca a instrução de parada. No vehicle.cpp, o movimento Stop é tratado como uma condição especial, na qual os motores deixam de receber comando de movimento. O valor de velocidade é passado como 0, reforçando que não há mais acionamento a ser aplicado. Essa etapa é essencial, pois sem ela o robô continuaria girando depois do delay().

Outro ponto importante está na estrutura do programa. Todo o comportamento foi colocado dentro da função setup():

void setup() { myCar.Init(); myCar.Move(Clockwise, 255); delay(750); myCar.Move(Stop, 0);}

Isso faz com que o movimento aconteça apenas uma vez, logo após ligar ou reiniciar o ESP32. Como a função loop() está vazia, o robô não repete o giro continuamente. Essa organização é útil para testes curtos, principalmente quando se deseja observar um único movimento e ajustar o tempo de execução.

A sequência geral do programa pode ser resumida em quatro etapas: primeiro, a biblioteca é incluída; depois, o objeto myCar é criado; em seguida, os motores são inicializados por Init(); por fim, o comando Move() é usado para girar e parar o robô. A simplicidade do código principal só é possível porque os detalhes de baixo nível foram organizados nos arquivos vehicle.h e vehicle.cpp.

Esse tipo de abstração é muito útil no ensino de sistemas embarcados e robótica. O estudante pode começar compreendendo o comportamento do robô em alto nível, usando comandos como Clockwise e Stop, e depois avançar para a leitura dos arquivos da biblioteca, onde aparecem os detalhes de controle dos motores, definição dos pinos e aplicação dos sinais PWM.

Em aplicações mais avançadas, esse mesmo conceito pode ser usado em estratégias de navegação. Um giro curto pode servir para corrigir a orientação do robô, procurar uma passagem livre, alinhar-se com um marcador visual ou realizar uma varredura do ambiente com sensores. Assim, mesmo um exemplo simples como esse já introduz uma ideia central da robótica móvel: movimentos complexos surgem da coordenação precisa de comandos simples aplicados aos motores.

Onde o giro acontece no vehicle.cpp

A parte do arquivo vehicle.cpp que efetivamente realiza o movimento do robô é a função:

void vehicle::Move(int Dir, int Speed)
{
digitalWrite(EN_PIN, LOW);
analogWrite(PWM1_PIN, Speed);
analogWrite(PWM2_PIN, Speed);
digitalWrite(STCP_PIN, LOW);
shiftOut(DATA_PIN, SHCP_PIN, MSBFIRST, Dir);
digitalWrite(STCP_PIN, HIGH);
}

Essa função é pequena, mas concentra a lógica essencial do movimento. Ela recebe dois parâmetros: Dir, que representa o tipo de movimento desejado, e Speed, que representa a velocidade aplicada aos motores. No programa principal, quando se escreve:

myCar.Move(Clockwise, 255);

o valor Clockwise é enviado para o parâmetro Dir, enquanto o valor 255 é enviado para o parâmetro Speed.

No arquivo vehicle.h, o movimento horário é definido da seguinte forma:

const int Clockwise = 172; // Rotate clockwise

Isso significa que Clockwise não é apenas uma palavra simbólica. Ele corresponde ao número inteiro 172. Esse número é usado pela função Move() para configurar os sentidos de rotação dos motores. O mesmo arquivo também define o movimento contrário:

const int Contrarotate = 83; // Counterclockwise rotation

e a parada:

const int Stop = 0; // stop

A função Move() começa habilitando o circuito de controle dos motores:

digitalWrite(EN_PIN, LOW);

O pino EN_PIN, definido no vehicle.h como o pino 16, atua como sinal de habilitação. Ao colocá-lo em nível baixo, a biblioteca libera o circuito para que os comandos possam chegar aos motores.

Em seguida, a velocidade é aplicada aos dois canais PWM:

analogWrite(PWM1_PIN, Speed);
analogWrite(PWM2_PIN, Speed);

No vehicle.h, esses pinos são definidos como:

#define PWM1_PIN 19
#define PWM2_PIN 23

Esses dois sinais PWM controlam a intensidade de acionamento dos motores. No exemplo do post, o valor usado é 255, que corresponde à velocidade máxima nessa escala de controle. Assim, antes mesmo de definir o sentido de rotação de cada motor, o programa já estabelece com que intensidade o conjunto motriz será acionado.

A parte mais importante para o giro está neste trecho:

digitalWrite(STCP_PIN, LOW);
shiftOut(DATA_PIN, SHCP_PIN, MSBFIRST, Dir);
digitalWrite(STCP_PIN, HIGH);

Esse bloco envia o valor de Dir para um registrador de deslocamento, usando os pinos DATA_PIN, SHCP_PIN e STCP_PIN. No vehicle.h, eles são definidos assim:

#define SHCP_PIN 18
#define DATA_PIN 5
#define STCP_PIN 17

De forma simples, o registrador de deslocamento funciona como uma expansão de saídas digitais. Em vez de o ESP32 precisar de muitos pinos para controlar diretamente todos os sinais de direção dos motores, ele envia um byte com a combinação desejada. Esse byte informa quais motores devem girar para frente, quais devem girar para trás e quais devem ficar parados.

A instrução central é:

shiftOut(DATA_PIN, SHCP_PIN, MSBFIRST, Dir);

Ela transmite o valor de Dir bit a bit. Quando Dir vale Clockwise, o número transmitido é 172. Em representação binária, esse valor corresponde a:

172 = 10101100

Esse padrão de bits é o que configura os motores para produzir a rotação no sentido horário. O robô gira porque as rodas não recebem todas o mesmo comando. Algumas são comandadas em um sentido e outras no sentido oposto, criando um torque em torno do centro do carro. Por isso, o robô não se desloca para frente nem para trás: ele gira.

O próprio vehicle.h ajuda a entender essa lógica, pois define bits individuais associados aos sentidos de cada motor:

const int M1_Forward = 128;
const int M1_Backward = 64;
const int M2_Forward = 32;
const int M2_Backward = 16;
const int M3_Forward = 2;
const int M3_Backward = 4;
const int M4_Forward = 1;
const int M4_Backward = 8;

Essas constantes mostram que cada motor possui bits próprios para frente e para trás. Quando a biblioteca define:

const int Clockwise = 172;

ela está usando uma combinação desses bits. O valor 172 pode ser decomposto como:

172 = 128 + 32 + 8 + 4

ou seja:

Clockwise = M1_Forward + M2_Forward + M3_Backward + M4_Backward

Com isso, os motores M1 e M2 giram em um sentido, enquanto os motores M3 e M4 giram no sentido oposto. Essa combinação produz o giro horário do robô.

Para o giro contrário, a lógica é invertida. O arquivo vehicle.h define:

const int Contrarotate = 83;

Esse valor pode ser decomposto como:

83 = 64 + 16 + 2 + 1

ou seja:

Contrarotate = M1_Backward + M2_Backward + M3_Forward + M4_Forward

Nesse caso, os motores que antes estavam indo para frente passam a girar para trás, e os que estavam para trás passam a girar para frente. O resultado é a rotação no sentido oposto.

A função Move() termina colocando STCP_PIN em nível alto:

digitalWrite(STCP_PIN, HIGH);

Esse comando atualiza as saídas do registrador de deslocamento. É como se o ESP32 dissesse: “agora aplique aos motores esse padrão de bits que acabei de enviar”. Dessa forma, a função transforma um comando simples, como Move(Clockwise, 255), em uma combinação elétrica coordenada para os quatro motores.

Quando o programa chama:

myCar.Move(Stop, 0);

a mesma função é usada, mas agora com Dir = 0 e Speed = 0. Como Stop foi definido no vehicle.h como zero, nenhum bit de direção é ativado, e como a velocidade também é zero, os sinais PWM deixam de aplicar potência aos motores. O carro, então, para.

Assim, quando o programa envia Clockwise, ele está enviando uma combinação que aciona alguns motores para frente e outros para trás. Esse é o princípio físico do giro: o robô não gira porque existe um motor especial para virar, mas porque os motores das rodas são acionados em sentidos opostos de forma coordenada.

Figura 1 – Driver do Acebbot

Para o motor DC, inverter o sentido de giro significa inverter a polaridade aplicada aos seus terminais. Como o ESP32 não pode alimentar diretamente os motores, essa inversão é feita por um circuito de potência, um driver como o L293 da figura 1. O ESP32 envia apenas os sinais de controle; o driver é quem fornece a corrente necessária ao motor. Assim, a biblioteca trabalha em dois níveis: o ESP32 decide “qual motor gira para qual lado” e “com que intensidade”; o circuito de potência transforma essa decisão em corrente elétrica suficiente para movimentar as rodas.

Deixe um comentário