Olá, no tutorial de hoje, vamos aprender a programar a lógica de um braço robótico, demonstrando cada etapa do desenvolvimento do código e seu sistema de gravação e reprodução de movimentos. Vamos lá !!

Neste tutorial completo, você aprenderá como programar um braço robótico utilizando a placa Arduino Pro Micro. O tutorial aborda desde a montagem do hardware até a criação e configuração do código, permitindo controlar os movimentos do braço robótico com precisão. Ideal para makers, entusiastas de robótica e estudantes de tecnologia, este projeto utiliza servo motores controlados por PWM, o que facilita o movimento articulado de cada junta do braço. O passo a passo inclui explicações detalhadas sobre os componentes, o funcionamento do código e como conectar tudo de forma simples e eficaz. Quer você seja iniciante ou tenha mais experiência com Arduino, este guia ajudará a criar um projeto funcional e interativo de automação robótica.

braço robotico

Este tutorial será divido nos seguintes tópicos, e caso deseje ir para algum dos tópicos:

Considerações

Lista de materiais

Inclusão da biblioteca Servo

Definição dos objetos e Pinos

Variáveis de controle dos servos e seus valores de leitura

Definição de Variaveis de controle

Controle de tempo com millis

Função Setup

Função Loop

Gravação dos movimentos

Iniciar e parar gravação

Reprodução dos movimentos gravados

Função SmoothTransition

Código completo e comentado

Video do braço robótico funcionando

 

Considerações

Para começar a programar o braço robótico, primeiro é necessário definir o eixo 0 do motor corretamente. 

 

Lista de materiais

Para iniciarmos a programação, vamos precisar dos seguintes componentes:

01 - Estrutura do braço robótico

01 - Arduino Pro micro

04 - Servo motores - MG90S

01 - Protoboard 400 pontos

01 - Fonte p/ Protoboard - 12V p/ 5,0/3,3 V - Branco

01 - Kit Jumper Macho Fêmea - 20 pçs

02 - Módulo Joystick Analógico - 3 Eixos

01 - Kit Jumpers Rígidos Formato de U - 140 pçs

 

Inclusão da biblioteca Servo

No Início do código, a biblioteca Servo.h é incluida.

#include <Servo.h>

A Servo.h facilita o controle de servomotores em microcontroladores como o Arduino. Ela permite associar pinos digitais a objetos Servo e envia comandos para definir a posição do motor, além de controlar o movimento angular dos servos com valores entre 0 e 180 graus.

 

Definição dos objetos e Pinos

Com a bilioteca incluida, devemos definir quatro objetos, sendo cada um para o controle de um motor do braço robótico:

Servo servo1; 
Servo servo2;
Servo servo3;
Servo servo4;

Nesta parte do código, ocorre a definição de quatro objetos de classe Servo, e cada um desses objetos controlará um motor. Sendo assim, cada objeto fica responsável da seguinte maneira:

Servo1: Controla a base do braço.

Servo2: Controla a extensão do braço.

Servo3: Controla a altura do braço.

Servo4: Controla a garra do braço.

Com os objetos definidos para controle do servo motor, definiremos agora, os pinos do joystick, que são os pinos utilizados para controlar a posição dos motores, como veremos mais a baixo:

Pinos dos potenciômetros ( O módulo Joystick, possui em sua construção, dois potênciometros)

int potpin1 = A0;
int potpin2 = A1; 
int potpin3 = A3;
int potpin4 = A2;

Os pinos do joystick são definidos como potpin1 a potpin4 e são pinos analógicos. E eles estão conectados aos potenciômetros do módulo Joystick que serão usados para controlar manualmente os servos. Nesse parte do código, os valores dos potênciometros ficam da seguinte maneira:
potpin1 = A0; - Controla a Base 

potpin2 = A1; - Controla a extensão 

potpin3 = A3; - Controla a altura

potpin4 = A2; -  Controla a Garra

 

Variáveis de controle dos servos e seus valores de leitura

Definição das váriaveis

int val1, val2, val3, val4;
int s1 = 90, s2 = 50, s3 = 10, s4 = 25;

Define as variaveis val1 a val4 para armazenar os valores analógicos entre 0 e 1023 lidos pelos pinos dos potênciometros.

Essa variaveis são definidas para armazenas o valor dos ângulos atuais dos servos. As variaveis s1 a s4 estão definidas para a posição inicial, em que o braço robótico fique parado na posição definida como 0, no começo do código. 

 

Variaveis e Arrays para Gravação dos movimentos

const int maxSteps = 500; // Reduzido para economizar memória
int recordedSteps = 0;

byte pos1[maxSteps], pos2[maxSteps], pos3[maxSteps], pos4[maxSteps];

maxSteps: O número máximo de passos (ou posições) que o sistema pode gravar durante o processo de gravação de movimentos. Esse limite é definido para evitar problemas de memória do Arduino.

recordedSteps: Mantém o número de passos gravados.

byte : Essa variável definida como byte utiliza 8 bits de memória que armazenam valores entre 0 e 255, que é suficiente para os ângulos dos servo motores que vão de 0 a 180º graus.

pos1[maxSteps], pos2[maxSteps], pos3[maxSteps], pos4[maxSteps]: Definem um Array para cada servo motor durante a gravação, e depois utiliza esse array para reproduzir os movimentos.

 

Definição de Variaveis de controle

Aqui são definidos as variaveis necessárias para verificar o estado atual do modo, que mais a frente, verifica em qual modo o microcontrolador se encontra, se é o de gravação ou reprodução.

bool isRecording = false;
bool isReplaying = false;
int replayIndex = 0;

isRecording: Variável booleana que indica se o sistema está no modo de gravação de movimentos.

isReplaying: Variável booleana que indica se o sistema está no modo de reprodução de movimentos gravados.

replayIndex: Controla o índice atual no processo de reprodução de movimentos.

 

Controle de tempo com millis

Aqui são definidas as variáveis de controle de tempo, necessárias para contagem e controle dos Leds.

unsigned long previousMillis = 0;
const long interval = 500; // Intervalo de 500ms para piscar o LED

previousMillis: Usado para armazenar o tempo anterior (em milissegundos) para comparação com o tempo atual. É utilizado para controlar o intervalo entre as piscadas dos LEDs.

interval: O intervalo de tempo (500ms) usado para alternar o estado dos LEDs durante a gravação de movimentos.

 

Função Setup

Na função Setup, temos algumas definições de pinos e também a escrita da posição inicial do braço do motor.

void setup() {
  pinMode(14, INPUT_PULLUP); // Botão de gravação
  pinMode(15, INPUT_PULLUP); // Botão de reprodução
  
  pinMode(17, OUTPUT); // LED indicador de gravação
  pinMode(30, OUTPUT); // LED indicador de reprodução

  Serial.begin(9600);

  servo1.attach(10); // base, pino digital 10
  servo2.attach(5);  // extensão, pino digital 5
  servo3.attach(6);  // altura, pino digital 6
  servo4.attach(3);  // garra, pino digital 3

  servo1.write(s1);
  servo2.write(s2);
  servo3.write(s3);
  servo4.write(s4);

  digitalWrite(17, 1); // Apaga LED de gravação inicialmente
  digitalWrite(30, 1); // Apaga LED de reprodução inicialmente
}

Nas funções pinMode os pinos são configurados como entradas com resistores de pull-up internos para os pinos 14 e 15, sendo utilizados para gravação e reprodução. Já os pinos 17 e 30, são definidos como saída e são eles que controlam os leds presentes na placa Arduino Nano.

A função Serial.begin é chamada para funções de debug do código, para conseguirmos definir o zero, inicial do motor.

Servo.attach() é uma função que associa os pinos digitais aos servos correspondentes. E as funções servo1.write(s1) até servo4.write(s4) escrevem os valoes definidos mais acima como a posição inicial do braço robótico, que é baseada nas variáveis s1 a s4.

Definindo a escrita digital dos pinos 17 e 30 como 1 (HIGH) , os LEDs são definidos como apagados.

 

Função Loop

A função loop é onde acontece a lógica de controle em tempo real, leitura dos potenciômetros, gravação, reprodução e controle dos LEDs.

if (!isReplaying) { // Permite gravação e controle manual quando não está reproduzindo
    val1 = analogRead(potpin1);
    if (val1 < 100) {
      s1 = s1 - 2;
      if (s1 <= 10) {
        s1 = 10;
      }
      servo1.write(s1);
      delay(25);  // Adiciona um delay para movimento mais lento
    }
    if (val1 > 900) {
      s1 = s1 + 2;
      if (s1 >= 170) {
        s1 = 170;
      }
      servo1.write(s1);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

    // Controle da extensão do braço (s2)
    val2 = analogRead(potpin2);
    if (val2 < 100) {
      s2 = s2 + 2;
      if (s2 >= 140) {
        s2 = 140;
      }

      if (s2 >= 120 && s3 <= 20) {
        s2 = 120;
      }

      servo2.write(s2);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

    if (val2 > 900) {
      s2 = s2 - 2;
      if (s2 <= 10) {
        s2 = 10;
      }

      if (s2 >= 120 && s3 <= 20) {
        s2 = 120;
      }

      servo2.write(s2);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

    // Controle da altura do braço (s3)
    val3 = analogRead(potpin3);
    if (val3 > 900) {
      s3 = s3 - 2;
      if (s3 <= 10) {
        s3 = 10;
      }

      if (s2 >= 120 && s2 < 130 && s3 <= 20) {
        s3 = 20;
      }
      else if (s2 >= 130 && s2 < 140 && s3 <= 30) {
        s3 = 30;
      }
      else if (s2 == 140 && s3 <= 65) {
        s3 = 65;
      }

      servo3.write(s3);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

    if (val3 < 100) {
      s3 = s3 + 2;
      if (s3 >= 170) {
        s3 = 170;
      }

      if (s2 >= 120 && s2 < 130 && s3 <= 20) {
        s3 = 20;
      }
      else if (s2 >= 130 && s2 < 140 && s3 <= 30) {
        s3 = 30;
      }
      else if (s2 == 140 && s3 <= 65) {
        s3 = 65;
      }

      servo3.write(s3);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

    // Controle da garra do braço    val4 = analogRead(potpin4);
    if (val4 < 100) {
      s4 = s4 + 2;
      if (s4 < 0) {
        s4 = 0;
      }
      servo4.write(s4);
      delay(25);  // Adiciona um delay para movimento mais lento
    }
    if (val4 > 900) {
      s4 = s4 - 2;
      
      if (s4 > 30) {
        s4 = 30;
      }

      if(s4 < 0)
      {
        s4 = 0;
      }

      servo4.write(s4);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

Entrando um pouco na lógica que acontece na função Loop, temos:

if (!isReplaying) { // Permite gravação e controle manual quando não está reproduzindo
    val1 = analogRead(potpin1);
    if (val1 < 100) {
      s1 = s1 - 2;
      if (s1 <= 10) {
        s1 = 10;
      }
      servo1.write(s1);
      delay(25);  // Adiciona um delay para movimento mais lento
    }
    if (val1 > 900) {
      s1 = s1 + 2;
      if (s1 >= 170) {
        s1 = 170;
      }
      servo1.write(s1);
      delay(25);  // Adiciona um delay para movimento mais lento
    }

em if (!isReplaying) temos uma variável que permite realizar o controle e a gravação, quando o código não está reproduzindo o que foi gravado. 

Já na linha que temos val1 = analogRead(potpin1), Definimos que a variável de nome Val1, é igual a leitura analógica do potênciometro do pino 1. Que é o pino do Joystick, utilizado para controlar a base, como foi definido mais acima.

Após a definição do valor da váriavel val1, o if seguinte:

if (val1 < 100) define que: se a variavel val1 for menor que 100

{

    s1 = s1 - 2; - o valor da variável s1 vai ser igual ao valor atual de s1 menos 2, ou seja, se s1 for por exemplo 50, o valor de s1, será 50 - 2, ou seja 48.

    if (s1 <= 10) - Se o valor s1 for menor do que 10

    { s1 = 10; } - Define o valor de s1 como 10. Isso define o limite máximo da variável s1, que será escrita no servo1, mais abaixo.

    servo1.write(s1); - Aqui, o valor do ângulo, armazenado na variável s1 é escrito no servo1.

    delay(25); - Aqui, um delay é definido para diminuir a velocidade de leitura.

}

Com isso, essa lógica, segue a mesma para os outros motores, e por exemplo, no caso, do servo do braço, mais algumas comparações são feitas e limites são definidos para que o braço não ultrapasse o "chão" e acabe erguindo a estrutura inteira. 

 

Gravação dos movimentos

Durante a gravação de movimentos, o estado de isRecording é verificado, junto a recordedSteps,  que tem que ser menor que o valor de maxSteps. Nessa parte do código, os arrays definidos antes do setup são utilizados para gravar os valores de s1 a s4. Com um pequeno delay para evitar multiplas leituras de uma só vez. 

if (isRecording && recordedSteps < maxSteps) {
  pos1[recordedSteps] = s1;
  pos2[recordedSteps] = s2;
  pos3[recordedSteps] = s3;
  pos4[recordedSteps] = s4;
  recordedSteps++;
  delay(25);
}

isRecording: Quando está gravando, as posições atuais dos servos são armazenadas nos arrays pos1 a pos4, permitindo registrar os movimentos para reprodução futura.

 

Iniciar e parar gravação

Nessa função quando o botão 14 é pressionado e não está reproduzindo na !isReplaying, o estado de gravação da variável !isRecording é alternado. Se a gravação parar, os LEDs dos pinos 17 e 30 serão apagados e gravação finalizada é enviada na Serial. Se a gravação começar, o recordedSteps é reiniciado. Também é adicionado um delay para evitar um debounce do botão.

// Iniciar/parar gravação
  if (digitalRead(14) == LOW && !isReplaying) {
    isRecording = !isRecording;
    if (!isRecording) {
      Serial.println("Gravação finalizada");
      digitalWrite(17, 1); // Garante que ambos os LEDs estejam desligados
      digitalWrite(30, 1);
    } else {
      recordedSteps = 0; // Reseta a gravação para começar do início
      Serial.println("Gravação iniciada");
    }
    delay(500); // Debounce do botão
  }

 

Controle dos LEDs durante a gravação

Nessa parte do código, quando a gravação é iniciada, há uma definição de currentMillis = millis(); onde o valor de currentMillis é definido, para que haja a comparação mais a baixo, onde curretMillis - previousMillis tem que ser maior ou igual a interval que no começo do código, foi definido como 500 ms. Se essa condição for verdadeira, o valor de previousMilis = currentMillis. Isso define que o valor anterior de previousMilis será removido, e será adicionado o valor atual de millis();

  if (isRecording) {
    unsigned long currentMillis = millis();

    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;
      bool currentState = digitalRead(17); // Ler o estado atual de um dos LEDs
      digitalWrite(17, !currentState); // Alterna o estado de ambos os LEDs
      digitalWrite(30, !currentState);
    }
  } else if (!isReplaying) {
    digitalWrite(17, 1); // Garante que os LEDs estejam apagados durante o uso comum
    digitalWrite(30, 1);
  }

Em currentState = digitalRead(17), o valor de currentState é definido como o valor do pino digital 17, para que mais abaixo, os valores dos pinos 17 e 30 sejam definidos com o valor de currentstate invertido, ou seja, o valor é definido antes e invertido na saída. Por exemplo, se o valor de pino 17 for 0, abaixo da linha bool currentState = digitalRead(17) o valor que é escrito, será 1. Isso inverte o estado da saída do LED. 

millis(): é usado para verificar o tempo atual e alternar os LEDs a cada 500ms, piscando-os durante o processo de gravação.

else if (!isReplaying) -  Garante que os leds estejam sempre apagados, durante o uso comum.

 

Reprodução dos movimentos gravados

Ao pressionar o botão 15, o estado de !isReplaying é alterado. Se a reprodução for iniciada, os servos fazem uma transição suave para a posição inicial e o replayIndex é resetado para 0. Os leds são acesos e os servos vão para a posição inicial suavemente. O delay é adicionado para debouncing no botão. 

// Controle de reprodução de movimentos gravados
  if (digitalRead(15) == LOW && !isRecording) {
    isReplaying = !isReplaying;  // Alterna o estado de reprodução
    if (isReplaying) {
      smoothTransition(pos1[0], pos2[0], pos3[0], pos4[0]);  // Transição suave para a posição inicial
      replayIndex = 0;  // Reseta o índice de reprodução
      Serial.println("Iniciando reprodução");
      digitalWrite(17, 0);  // Acende os LEDs durante a reprodução
      digitalWrite(30, 0);
    } else {
      Serial.println("Reprodução interrompida");
      smoothTransition(s1, s2, s3, s4);  // Retorna suavemente para a posição atual
      digitalWrite(17, 1);  // Apaga os LEDs ao interromper a reprodução
      digitalWrite(30, 1);
    }
    delay(500);  // Debounce para o botão de reprodução
  }

 

Nessa parte, o isReplaying é verificado para ver se o código está em modo de reprodução. com isso se replayIndex for menor que recordedSteps, as posições dos servo1 a servo4, são escritas com o valor armazenado de pos1[replayIndex] até pos4[replayIndex]. 

if (isReplaying) {
  if (replayIndex < recordedSteps) {
    servo1.write(pos1[replayIndex]);
    servo2.write(pos2[replayIndex]);
    servo3.write(pos3[replayIndex]);
    servo4.write(pos4[replayIndex]);
    replayIndex++;
    delay(30);
  } else {
    smoothTransition(pos1[0], pos2[0], pos3[0], pos4[0]);
    replayIndex = 0;
  }
}

Em smoothTransition quando todos os passos são reproduzidos, a transição suave retorna os servos à posição inicial.

 

Função SmoothTransition

Essa função, tem como objetivo mover os servos das posições atuais para as posições alvo, gradualmente. Ela suaviza a transição para que os movimentos não sejam abruptos.

void smoothTransition(int target1, int target2, int target3, int target4) {
  int current1 = servo1.read();
  int current2 = servo2.read();
  int current3 = servo3.read();
  int current4 = servo4.read();

  while (current1 != target1 || current2 != target2 || current3 != target3 || current4 != target4) {
    if (current1 < target1) current1++;
    else if (current1 > target1) current1--;

    if (current2 < target2) current2++;
    else if (current2 > target2) current2--;

    if (current3 < target3) current3++;
    else if (current3 > target3) current3--;

    if (current4 < target4) current4++;
    else if (current4 > target4) current4--;

    servo1.write(current1);
    servo2.write(current2);
    servo3.write(current3);
    servo4.write(current4);

    delay(25); // Ajuste o delay para controlar a suavidade da transição
  }
}

As variáveis current1, current2, current3 e current4 são definidas como as posições atuais dos servos através do servoX.read(). Isso captura a posição atual dos servos antes de iniciar a transição. Com isso o loop while verifica se qualquer um dos servos ainda não atingiu sua posição-alvo. Se a posição atual (current1 a current4) for menor que a posição-alvo (target1 a target 4 ), a posição é incrementada em 1 unidade; se for maior, ela é decrementada.
Esse processo contínuo garante que cada servo se mova gradualmente em direção à sua posição final, sem movimentos bruscos.
A cada iteração do loop, os servos são movidos para suas novas posições incrementadas ou decrementadas através das chamadas para servo1.write(current1), servo2.write(current2), e assim por diante.
O delay(25) é utilizado para tornar a transição mais lenta e suave.

 

Código completo e comentado:

#include <Servo.h>  // Inclui a biblioteca Servo para controlar os servomotores

// Declara quatro objetos Servo para controlar os motores
Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;

// Pinos analógicos aos quais os potenciômetros estão conectados
int potpin1 = A0;  // Pino A0 para o potenciômetro 1
int potpin2 = A1;  // Pino A1 para o potenciômetro 2
int potpin3 = A3;  // Pino A3 para o potenciômetro 3
int potpin4 = A2;  // Pino A2 para o potenciômetro 4

// Variáveis para armazenar os valores lidos dos potenciômetros
int val1, val2, val3, val4;
// Ângulos iniciais dos servos
int s1 = 90, s2 = 50, s3 = 10, s4 = 25;

// Definição do número máximo de passos que podem ser gravados
const int maxSteps = 500;  // Reduzido para economizar memória
int recordedSteps = 0;     // Conta quantos passos foram gravados

// Arrays que armazenam os ângulos dos servos durante a gravação
byte pos1[maxSteps], pos2[maxSteps], pos3[maxSteps], pos4[maxSteps];

// Variáveis de controle para gravação e reprodução
bool isRecording = false;  // Indica se está gravando movimentos
bool isReplaying = false;  // Indica se está reproduzindo movimentos
int replayIndex = 0;       // Índice atual durante a reprodução

// Variáveis para controle de tempo e piscar dos LEDs
unsigned long previousMillis = 0;  // Armazena o último tempo de atualização
const long interval = 500;         // Intervalo de 500 ms para piscar o LED

void setup() {
  // Configura os pinos para os botões de gravação e reprodução como entradas com resistores de pull-up
  pinMode(14, INPUT_PULLUP);  // Botão de gravação
  pinMode(15, INPUT_PULLUP);  // Botão de reprodução

  // Configura os pinos 17 e 30 como saídas para controlar LEDs indicadores
  pinMode(17, OUTPUT);  // LED indicador de gravação
  pinMode(30, OUTPUT);  // LED indicador de reprodução

  // Inicializa a comunicação serial a 9600 bps
  Serial.begin(9600);

  // Anexa os servos aos pinos digitais
  servo1.attach(10);  // Servo da base, conectado ao pino digital 10
  servo2.attach(5);   // Servo de extensão, conectado ao pino digital 5
  servo3.attach(6);   // Servo de altura, conectado ao pino digital 6
  servo4.attach(3);   // Servo da garra, conectado ao pino digital 3

  // Define as posições iniciais dos servos
  servo1.write(s1);
  servo2.write(s2);
  servo3.write(s3);
  servo4.write(s4);

  // Garante que os LEDs estejam apagados inicialmente
  digitalWrite(17, 1);  // LED de gravação apagado
  digitalWrite(30, 1);  // LED de reprodução apagado
}

// Função para fazer a transição suave dos servos para as posições alvo
void smoothTransition(int target1, int target2, int target3, int target4) {
  // Lê as posições atuais dos servos
  int current1 = servo1.read();
  int current2 = servo2.read();
  int current3 = servo3.read();
  int current4 = servo4.read();

  // Enquanto qualquer servo não atingir a posição alvo
  while (current1 != target1 || current2 != target2 || current3 != target3 || current4 != target4) {
    // Move o servo1 para a posição alvo de forma suave
    if (current1 < target1) current1++;
    else if (current1 > target1) current1--;

    // Move o servo2 para a posição alvo de forma suave
    if (current2 < target2) current2++;
    else if (current2 > target2) current2--;

    // Move o servo3 para a posição alvo de forma suave
    if (current3 < target3) current3++;
    else if (current3 > target3) current3--;

    // Move o servo4 para a posição alvo de forma suave
    if (current4 < target4) current4++;
    else if (current4 > target4) current4--;

    // Define as novas posições dos servos
    servo1.write(current1);
    servo2.write(current2);
    servo3.write(current3);
    servo4.write(current4);

    delay(25);  // Delay para suavizar a transição
  }
}

void loop() {
  // Se não estiver reproduzindo, permite controle manual e gravação
  if (!isReplaying) {
    // Lê o valor do potenciômetro conectado ao pino A0
    val1 = analogRead(potpin1);
    if (val1 < 100) {  // Se o valor for muito baixo, diminui o ângulo do servo1
      s1 = s1 - 2;
      if (s1 <= 10) s1 = 10;  // Limita o ângulo mínimo
      servo1.write(s1);        // Define o novo ângulo para o servo1
      delay(25);               // Delay para suavizar o movimento
    }
    if (val1 > 900) {  // Se o valor for muito alto, aumenta o ângulo do servo1
      s1 = s1 + 2;
      if (s1 >= 170) s1 = 170;  // Limita o ângulo máximo
      servo1.write(s1);         // Define o novo ângulo para o servo1
      delay(25);                // Delay para suavizar o movimento
    }

    // Controle semelhante para os outros servos (servo2, servo3 e servo4)
    // Controle da extensão do braço (servo2)
    val2 = analogRead(potpin2);
    if (val2 < 100) {
      s2 = s2 + 2;
      if (s2 >= 140) s2 = 140;  // Limita a extensão máxima
      if (s2 >= 120 && s3 <= 20) s2 = 120;  // Restrição baseada na altura
      servo2.write(s2);  // Define o novo ângulo para o servo2
      delay(25);         // Delay para suavizar o movimento
    }
    if (val2 > 900) {
      s2 = s2 - 2;
      if (s2 <= 10) s2 = 10;  // Limita a extensão mínima
      if (s2 >= 120 && s3 <= 20) s2 = 120;  // Restrição baseada na altura
      servo2.write(s2);  // Define o novo ângulo para o servo2
      delay(25);         // Delay para suavizar o movimento
    }

    // Controle da altura do braço (servo3)
    val3 = analogRead(potpin3);
    if (val3 > 900) {
      s3 = s3 - 2;
      if (s3 <= 10) s3 = 10;  // Limita a altura mínima
      if (s2 >= 120 && s2 < 130 && s3 <= 20) s3 = 20;  // Restrição baseada na extensão
      else if (s2 >= 130 && s2 < 140 && s3 <= 30) s3 = 30;
      else if (s2 == 140 && s3 <= 65) s3 = 65;
      servo3.write(s3);  // Define o novo ângulo para o servo3
      delay(25);         // Delay para suavizar o movimento
    }
    if (val3 < 100) {
      s3 = s3 + 2;
      if (s3 >= 170) s3 = 170;  // Limita a altura máxima
      if (s2 >= 120 && s2 < 130 && s3 <= 20) s3 = 20;
      else if (s2 >= 130 && s2 < 140 && s3 <= 30) s3 = 30;
      else if (s2 == 140 && s3 <= 65) s3 = 65;
      servo3.write(s3);  // Define o novo ângulo para o servo3
      delay(25);         // Delay para suavizar o movimento
    }

    // Controle da garra (servo4)
    val4 = analogRead(potpin4);
    if (val4 < 100) {
      s4 = s4 + 2;
      if (s4 < 0) s4 = 0;  // Limita a abertura mínima da garra
      servo4.write(s4);    // Define o novo ângulo para o servo4
      delay(25);           // Delay para suavizar o movimento
    }
    if (val4 > 900) {
      s4 = s4 - 2;
      if (s4 > 30) s4 = 30;  // Limita a abertura máxima da garra
      if (s4 < 0) s4 = 0;    // Evita valores negativos
      servo4.write(s4);      // Define o novo ângulo para o servo4
      delay(25);             // Delay para suavizar o movimento
    }

    // Gravação de movimentos
    if (isRecording && recordedSteps < maxSteps) {
      // Armazena as posições atuais dos servos nos arrays de gravação
      pos1[recordedSteps] = s1;
      pos2[recordedSteps] = s2;
      pos3[recordedSteps] = s3;
      pos4[recordedSteps] = s4;
      recordedSteps++;  // Incrementa o contador de passos gravados
      delay(25);        // Delay para capturar os movimentos de forma controlada
    }
  }

  // Controle de início e parada de gravação
  if (digitalRead(14) == LOW && !isReplaying) {
    isRecording = !isRecording;  // Alterna o estado de gravação
    if (!isRecording) {
      Serial.println("Gravação finalizada");
      digitalWrite(17, 1);  // Apaga o LED de gravação
      digitalWrite(30, 1);  // Apaga o LED de gravação
    } else {
      recordedSteps = 0;  // Reseta o contador de gravação
      Serial.println("Gravação iniciada");
    }
    delay(500);  // Debounce para o botão de gravação
  }

  // Piscar LEDs enquanto grava
  if (isRecording) {
    unsigned long currentMillis = millis();
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;  // Atualiza o tempo anterior
      bool currentState = digitalRead(17);  // Lê o estado atual do LED
      digitalWrite(17, !currentState);      // Alterna o estado do LED
      digitalWrite(30, !currentState);      // Alterna o estado do outro LED
    }
  } else if (!isReplaying) {
    digitalWrite(17, 1);  // Garante que os LEDs estejam apagados quando não está gravando ou reproduzindo
    digitalWrite(30, 1);
  }

  // Controle de reprodução de movimentos gravados
  if (digitalRead(15) == LOW && !isRecording) {
    isReplaying = !isReplaying;  // Alterna o estado de reprodução
    if (isReplaying) {
      smoothTransition(pos1[0], pos2[0], pos3[0], pos4[0]);  // Transição suave para a posição inicial
      replayIndex = 0;  // Reseta o índice de reprodução
      Serial.println("Iniciando reprodução");
      digitalWrite(17, 0);  // Acende os LEDs durante a reprodução
      digitalWrite(30, 0);
    } else {
      Serial.println("Reprodução interrompida");
      smoothTransition(s1, s2, s3, s4);  // Retorna suavemente para a posição atual
      digitalWrite(17, 1);  // Apaga os LEDs ao interromper a reprodução
      digitalWrite(30, 1);
    }
    delay(500);  // Debounce para o botão de reprodução
  }

  // Se estiver no modo de reprodução
  if (isReplaying) {
    if (replayIndex < recordedSteps) {
      // Define as posições dos servos de acordo com os valores gravados
      servo1.write(pos1[replayIndex]);
      servo2.write(pos2[replayIndex]);
      servo3.write(pos3[replayIndex]);
      servo4.write(pos4[replayIndex]);
      replayIndex++;  // Incrementa o índice de reprodução
      delay(30);      // Delay para controlar a velocidade de reprodução
    } else {
      // Retorna suavemente à primeira posição gravada
      smoothTransition(pos1[0], pos2[0], pos3[0], pos4[0]);  // Suave retorno à posição inicial
      replayIndex = 0;  // Reinicia o índice para repetir o loop de reprodução
    }
  }
}

 

Video do braço robótico funcionando

Veja abaixo, um video, demonstrando o braço robótico funcionando, e seus modos de gravação e reprodução.

Com isso, hoje aprendemos a programar completamente o braço robótico. Fique atento para mais tutoriais em nosso blog.