Problema con Cinemática inversa y Cinemática directa usando arduino uno con motores paso y driver TB6600 para brazo robotico CNC

Hola a todos los internautas. Recién estoy por terminar la carrera, por lo que no tengo un conocimiento tan avanzado en programación de Arduino. Y más contra este reto que es utilizar motores paso a paso y drivers TB6600 que recién los veo. Utilizando varios videos de youtube y un poco de ayudita con el código he podido medio realizar que el brazo robot se mueva. El problema es el siguiente No puedo controlarlo ni va a la posición que quiero.

El brazo de 3GDL R-R-R cuenta con 3 motores paso a paso, sus respectivos drivers y 3 límites de carrera. Se desea que le puedas ingresar una secuencia de coordenadas x,y,z y el brazo pueda realizar la secuencia. El problema es que a veces pareciera que a los motores le cuesta mover el armazón, y en ese proceso pierde pasos. Lo que más me ha funcionado fue utilizar cinemática directa, ingresaba los ángulos y obtenía aproximadamente la posición que quería a puro prueba y error. Sin embargo, las coordenadas que me bota no tienen sentido alguno, pareciera que existiera en otro plano porque a veces mover 10mm a la izquierda o a la derecha viajaba entre los positivos y negativos como si nada, sin sentido. Además, descubrí que al ingresar los ángulos, el movimiento que realizará no es recta, sino tiene como una oscilación. Por lo que su función que es para un brazo tipo CNC no es posible porque debería realizar movimientos angulares y rectos. A continuación les dejo el codigo que estuve utilizando:

#include <AccelStepper.h>
#include <math.h>

const int dir1 = 7, step1 = 4;
const int dir2 = 2, step2 = 5;
const int dir3 = 3, step3 = 6;

const int sensor1Pin = A3;
const int sensor2Pin = A4;
const int sensor3Pin = A5;

const int pot1Pin = A0; // Motor1
const int pot2Pin = A1; // Motor2
const int pot3Pin = A2; // Motor3

AccelStepper motor1(AccelStepper::DRIVER, step1, dir1);
AccelStepper motor2(AccelStepper::DRIVER, step2, dir2);
AccelStepper motor3(AccelStepper::DRIVER, step3, dir3);

const float L1 = 100;
const float L2 = 130;
const float L3 = 170;

const float pi = PI;
const float pasos_por_grado = 1600/360;

float q1, q2, q3;
float theta1, theta2, theta3;
float x, y, z, r, D;

bool referenciado = false;
String inputString = "";
bool stringComplete = false;

unsigned long lastPrintTime = 0;
const unsigned long printInterval = 500; // Imprimir cada 500 ms

int lastPotAngle1 = 0;
int lastPotAngle2 = 0;
int lastPotAngle3 = 0;
const int deadband = 2; // zona muerta en grados

void hacerReferencia() {
  motor2.setSpeed(-800);
  while (digitalRead(sensor3Pin) != LOW) motor2.runSpeed();
  motor2.stop(); motor2.setCurrentPosition(0);

  motor3.setSpeed(-800);
  while (digitalRead(sensor2Pin) != LOW) motor3.runSpeed();
  motor3.stop(); motor3.setCurrentPosition(0);

  motor1.setSpeed(-800);
  while (digitalRead(sensor1Pin) != LOW) motor1.runSpeed();
  motor1.stop(); motor1.setCurrentPosition(0);
}

void moverA_angulos(float q1_ref, float q2_ref, float q3_ref) {
  motor1.moveTo(q1_ref * pasos_por_grado);
  motor2.moveTo(q2_ref * pasos_por_grado);
  motor3.moveTo(q3_ref * pasos_por_grado);

  while (motor1.distanceToGo() != 0 || motor2.distanceToGo() != 0 || motor3.distanceToGo() != 0) {
    motor1.run();
    motor2.run();
    motor3.run();
    imprimirPosicionActual();
  }
}

void imprimirPosicionActual() {
  q1 = motor1.currentPosition() / pasos_por_grado * pi / 180.0;
  q2 = motor2.currentPosition() / pasos_por_grado * pi / 180.0;
  q3 = motor3.currentPosition() / pasos_por_grado * pi / 180.0;

  r = L2 * cos(q2) + L3 * cos(q2 + q3);
  x = r * cos(q1);
  y = r * sin(q1);
  z = L1 + L2 * sin(q2) + L3 * sin(q2 + q3);

  Serial.print("X: "); Serial.print(x);
  Serial.print("  Y: "); Serial.print(y);
  Serial.print("  Z: "); Serial.print(z);

  Serial.print("  θ1: "); Serial.print(q1 * 180 / pi);
  Serial.print("  θ2: "); Serial.print(q2 * 180 / pi);
  Serial.print("  θ3: "); Serial.println(q3 * 180 / pi);
}

int leerPotSuavizado(int pin) {
  long suma = 0;
  for (int i = 0; i < 5; i++) {
    suma += analogRead(pin);
  }
  return suma / 5;
}

void setup() {
  Serial.begin(9600);
  inputString.reserve(200);

  pinMode(sensor1Pin, INPUT_PULLUP);
  pinMode(sensor2Pin, INPUT_PULLUP);
  pinMode(sensor3Pin, INPUT_PULLUP);

  motor1.setMaxSpeed(1600); motor1.setAcceleration(1000);
  motor2.setMaxSpeed(1600); motor2.setAcceleration(1000);
  motor3.setMaxSpeed(1600); motor3.setAcceleration(1000);

  hacerReferencia();
  referenciado = true;

  moverA_angulos(0, 0, 0);
  delay(1000);

  Serial.println("Ingrese q1,q2,q3 separados por comas. Ejemplo: 45,30,60");
}

void loop() {
  if (stringComplete) {
    int q1_i = inputString.indexOf(',');
    int q2_i = inputString.lastIndexOf(',');

    if (q1_i > 0 && q2_i > q1_i) {
      float q1_input = inputString.substring(0, q1_i).toFloat();
      float q2_input = inputString.substring(q1_i + 1, q2_i).toFloat();
      float q3_input = inputString.substring(q2_i + 1).toFloat();

      moverA_angulos(q1_input, q2_input, q3_input);
    } else {
      Serial.println("Formato incorrecto. Use: q1,q2,q3");
    }

    inputString = "";
    stringComplete = false;
    Serial.println("Ingrese nuevos valores q1,q2,q3:");
  } else {
    // Leer y suavizar potenciómetros
    int pot1Val = leerPotSuavizado(pot1Pin);
    int pot2Val = leerPotSuavizado(pot2Pin);
    int pot3Val = leerPotSuavizado(pot3Pin);

    float angle1 = map(pot1Val, 0, 1023, 0, 900);
    float angle2 = map(pot2Val, 0, 1023, 0, 400);
    float angle3 = map(pot3Val, 0, 1023, 0, 230);

    // Zona muerta para cada motor
    if (abs(angle1 - lastPotAngle1) >= deadband) {
      motor1.moveTo(angle1 * pasos_por_grado);
      lastPotAngle1 = angle1;
    }
    if (abs(angle2 - lastPotAngle2) >= deadband) {
      motor2.moveTo(angle2 * pasos_por_grado);
      lastPotAngle2 = angle2;
    }
    if (abs(angle3 - lastPotAngle3) >= deadband) {
      motor3.moveTo(angle3 * pasos_por_grado);
      lastPotAngle3 = angle3;
    }

    motor1.run();
    motor2.run();
    motor3.run();
  }

  if (millis() - lastPrintTime >= printInterval) {
    imprimirPosicionActual();
    lastPrintTime = millis();
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}

Además, pensé que podía ser mi cableado y la cantidad de pasos o micropasos que este tiene. Pero considero que lo configuré correctamente todos, pero sigue sin funcionar. Así que les dejo el cableado:

Tambien les dejo mi configuración de los drivers, el robot y los modelos de los motores.

Nota: Sé que en el código aparecen potenciometros. Es que intentaba controlarlos manualmente, grabar la posición y hacer que replique la secuencia. Spoiler, tampoco lo logré. Ya que al volver al Home, perdía pasos y en el 4 intento era inestabilidad total. Además, intenté utilizar un CNC shield para controlar los motores, pero tenia el mismo problema, a un motor le costaba moverse, el cual es levantar el brazo. Tuve la suposición de subirle un poco la corriente y así fue probando, pero más era lo que se calentaba que movia.

Si no resuelves primero la parte mecánica difícilmente logres que funcione correctamente la electrónica.

Si el brazo no se mueve bien y suave sin los motores, tienes problemas mecánicos (que suele ser la parte más complicada).
Si los movimientos van como seda e igual le cuesta a los motores, algo está mal configurado o los motores no son los correctos.

Si los motores pierden pasos porque se traba la parte mecánica (por rozamiento o lo que sea) es lógico que los movimientos no se realizarán bien.

Por otro lado, no nos pidas que usemos nuestro tiempo (tan valioso como el tuyo) en revisar un código que no es el que usas, pon el código actual, este que presentas no sirve de nada.

Lo que debes hacer es tomar un motor con su driver y ver como se comporta, angulos, desplazamientos. Establece los límites, compara con lo que tienes y ve descartando problemas.
No trabajes con 3 a la vez. De a 1.
Esto no es por ti y a la vez si.
Porque todo el mundo siempre comete el mismo error, tiene muchos problemas, no sabe por donde comenzar y los ataca a todos simultáneamente.
A menos que seas un experto que lo hayas hechos 100 veces no tienes idea por donde comenzar.
Entonces desarmando el problema en cosas simples comienzas a entender que pasa.
Sigue los consejos de @MaximoEsfuerzo, controlador TB6600, limites de corriente, micropasos, etc, etc.
Revisa cada situación, y cuando tienes los 3 con sus limites puedes volver al problema general y entonces si habla de cinemática directa o inversa.

Gracias por considerar mi post MaximoEsfuerzo.
No era mi intención desperdiciar su tiempo ni mucho menos. No es que el codigo "no sirve de nada", solamente no llego a controlar correctamente la posición. Disculpa si no pude explicar con detalle todo, es mi primer post en la vida que hago.

Este "brazo" es un prototipo que se compró con los motores incluidos y funcionaba. Cuando hice mis primeras pruebas, con tan solo "escuchar" el movimiento es limpio, se desliza correctamente. Lo dejé de intentar por un tiempo (2 meses) y he vuelto a retomar este proyecto. Así que lo que considero es que sea la segunda opción que propones, debe haber algo mal configurado que debe de estar perdiendo pasos.

Justo el otro día leyendo sobre motores paso a paso, me encuentro que también influye el orden en que conecto las bobinas a la borneras, no me refiero que no estoy conectando las bobinas, sino que en un orden en concreto puede tener más o menos vibración y eso también puede generar esa pérdida de pasos que presento. Y este proyecto lo llevo intentando varias semanas, por lo que el código que mandé es el último que hice a pura prueba y error tratando que el brazo llegue a la posición que deseo siendo esta la más exacta que he logrado hacer. Sin embargo, como comenté, igualmente en algún punto está perdiendo pasos y empieza a descontrolarse, por más que tengo seteado un Home y cada que inicie o acabe la secuencia tiene que ir allí para regularse.
Agradezco muchisimo su ayuda

-Leo

Hola Surbyte!

Agradezco mucho tus consejos y el tiempo que le dedicaste al post. En realidad eso fue lo que hice al inicio. Establecí los pasos, ángulos, corriente, limites de recorrido físico, etc. A parte del mecanismo, probé a solas el motor acoplado junto con cada uno de los drivers que presento en el post. Pero sigo sin lograr la solución a mi problema o simplemente no tengo la visión clara para desarrollarlo. Por eso mismo intenté, en otro código, establecer los movimientos manualmente, luego con botones grabar los ángulos de desplazamiento de cada motor utilizando las cinemáticas y con otro botón ejecutar la secuencia hecha por los botones, sin antes ir al home. Al principio parecía funcionar, al poner un lápiz y haciendo que pinte debajo de un cuaderno, sin que este genere nada de fricción es donde me doy cuenta que estaba totalmente equivocado. Por más que haya grabado la secuencia, este parecía dibujar la secuencia en lugares donde no debería, totalmente desfasado al original!

Hace poco encontré una aplicación llamada RoboDK, aunque es para un mayor control de brazos de 6GDL, voy a intentar empaparme de la aplicación y ejecución de movimiento del robot para traer nuevas actualizaciones mientras voy revisando posibles soluciones.

Mi suposición me dicta que no estoy codificando correctamente y algo está fallando. Tal vez la complejidad del estudiante me dicta a que debería agregar lazos de control para minimizar el error, etc etc. Pero viendo otros videos y proyectos similares en internet, realmente se puede lograr sin tanta complejidad. Así que no me rendiré.

-Leo

Si, no sirve porque tú mismo lo has dicho

Entonces no es el código que usas actualmente porque no tienes potenciómetros, entonces... no sirve de nada.

Sube el código actual.

Entiendo, en ese caso añado el último código que he utilizado sin los potenciometros.

#include <AccelStepper.h>
#include <MultiStepper.h>

const int dir1 = 9, step1 = 10;
const int dir2 = 7, step2 = 6;
const int dir3 = 2, step3 = 3;

const int sensor1Pin = 13;
const int sensor2Pin = 12;
const int sensor3Pin = 11;

AccelStepper motor1(AccelStepper::DRIVER, step1, dir1);
AccelStepper motor2(AccelStepper::DRIVER, step2, dir2);
AccelStepper motor3(AccelStepper::DRIVER, step3, dir3);

MultiStepper steppers;

const float pasos_por_grado = 1600.0 / 360.0;
const int repeticiones = 10;
const int pasos_interpolacion = 10;  

float secuencia[13][3] = {
  {852.75 +  25.0, 300.15 +   0.0, 652.05},  // 0°
  {852.75 +  21.7, 300.15 +  12.5, 652.05},
  {852.75 +  12.5, 300.15 +  21.7, 652.05},
  {852.75 +   0.0, 300.15 +  25.0, 652.05},  // 90°
  {852.75 -  12.5, 300.15 +  21.7, 652.05},
  {852.75 -  21.7, 300.15 +  12.5, 652.05},
  {852.75 -  25.0, 300.15 +   0.0, 652.05},  // 180°
  {852.75 -  21.7, 300.15 -  12.5, 652.05},
  {852.75 -  12.5, 300.15 -  21.7, 652.05},
  {852.75 +   0.0, 300.15 -  25.0, 652.05},  // 270°
  {852.75 +  12.5, 300.15 -  21.7, 652.05},
  {852.75 +  21.7, 300.15 -  12.5, 652.05},
  {852.75 +  25.0, 300.15 +   0.0, 652.05}   // 360° 
};



const int num_posiciones = sizeof(secuencia) / sizeof(secuencia[0]);

void hacerReferencia() {
  motor2.setSpeed(-800);
  while (digitalRead(sensor3Pin) != LOW) motor2.runSpeed();
  motor2.stop(); motor2.setCurrentPosition(0);

  motor3.setSpeed(-800);
  while (digitalRead(sensor2Pin) != LOW) motor3.runSpeed();
  motor3.stop(); motor3.setCurrentPosition(0);

  motor1.setSpeed(-800);
  while (digitalRead(sensor1Pin) != LOW) motor1.runSpeed();
  motor1.stop(); motor1.setCurrentPosition(0);
}

void moverInterpolado(float q1_ini, float q2_ini, float q3_ini, float q1_fin, float q2_fin, float q3_fin) {
  for (int step = 1; step <= pasos_interpolacion; step++) {
    float t = (float)step / pasos_interpolacion;

    float q1 = q1_ini + t * (q1_fin - q1_ini);
    float q2 = q2_ini + t * (q2_fin - q2_ini);
    float q3 = q3_ini + t * (q3_fin - q3_ini);

    long destino[3];
    destino[0] = q1 * pasos_por_grado;
    destino[1] = q2 * pasos_por_grado;
    destino[2] = q3 * pasos_por_grado;

    steppers.moveTo(destino);
    steppers.runSpeedToPosition();
  }
}

void setup() {
  Serial.begin(9600);

  pinMode(sensor1Pin, INPUT_PULLUP);
  pinMode(sensor2Pin, INPUT_PULLUP);
  pinMode(sensor3Pin, INPUT_PULLUP);

  motor1.setMaxSpeed(800);
  motor2.setMaxSpeed(800);
  motor3.setMaxSpeed(800);

  steppers.addStepper(motor1);
  steppers.addStepper(motor2);
  steppers.addStepper(motor3);

  hacerReferencia();
  moverInterpolado(0, 0, 0, secuencia[0][0], secuencia[0][1], secuencia[0][2]);
  delay(1000);

  for (int rep = 0; rep < repeticiones; rep++) {
    Serial.print("Repetición #"); Serial.println(rep + 1);
    for (int i = 0; i < num_posiciones - 1; i++) {
      moverInterpolado(
        secuencia[i][0], secuencia[i][1], secuencia[i][2],
        secuencia[i + 1][0], secuencia[i + 1][1], secuencia[i + 1][2]
      );
    }
  }

  Serial.println("Secuencia interpolada finalizada.");
}

void loop() {
  
}

Edit: Lo que hice en este código fue: anteriormente con los potenciómetros grabé los ángulos que debía tomar cada brazo para lograr hacer un cuadrado. Eso lo metí en la variable secuencia. Y utilizando interpolación conseguí que medio se acerque a la figura deseada. Pero da el mismo problema que comenté.

-Leo

Un primer error a tener en cuenta

void AccelStepper::moveTo(long absolute)

Requiere long no floats
Convierte o has el typecast correspondiente

destino[0] = (long)(q1 * pasos_por_grado);
destino[1] = (long)(q2 * pasos_por_grado);
destino[2] = (long)(q3 * pasos_por_grado);
1 Like

Ya corregí dicho error. Y probando un poco el codigo, creo que mi cinemática es lo que está fallando. Cuando introduzco mi angulos q1,q2,q3 y saca mis coordenadas en xyz y luego los vuelvo a introducir a mi cinematica inversa, en teoría, deberían ser los mismos valores. Pero me bota diferentes números.

#include <AccelStepper.h>
#include <math.h>

// --- Pines de los motores ---
const int dir1 = 9, step1 = 10;
const int dir2 = 7, step2 = 6;
const int dir3 = 2, step3 = 3;

// --- Pines de los sensores (NC/NO) ---
const int sensor1Pin = 13;
const int sensor2Pin = 12;
const int sensor3Pin = 11;

// --- Creación de motores ---
AccelStepper motor1(AccelStepper::DRIVER, step1, dir1);
AccelStepper motor2(AccelStepper::DRIVER, step2, dir2);
AccelStepper motor3(AccelStepper::DRIVER, step3, dir3);

// --- Parámetros del brazo ---
const float L1 = 100;
const float L2 = 130;
const float L3 = 170;

const float pi = PI;
const float pasos_por_grado = 6400.0/360.0; 

float q1, q2, q3;
float theta1, theta2, theta3;
float x, y, z, r, D;

bool referenciado = false;

// --- Variables antirrebote ---
const unsigned long debounceDelay = 50;
unsigned long lastDebounce1 = 0, lastDebounce2 = 0, lastDebounce3 = 0;
int lastReading1 = HIGH, lastReading2 = HIGH, lastReading3 = HIGH;
int sensorState1 = HIGH, sensorState2 = HIGH, sensorState3 = HIGH;

// --- Función de referencia con antirrebote ---
void hacerReferencia() {
  Serial.println("Iniciando referencia...");

   // Motor 2
  motor2.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor3Pin);
    if (reading != lastReading2) lastDebounce2 = millis();
    if ((millis() - lastDebounce2) > debounceDelay) sensorState2 = reading;
    lastReading2 = reading;

    if (sensorState2 == LOW) break;
    motor2.runSpeed();
  }
  motor2.stop(); motor2.setCurrentPosition(0);
  Serial.println("Motor2 referenciado");

   // Motor 3
  motor3.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor2Pin);
    if (reading != lastReading3) lastDebounce3 = millis();
    if ((millis() - lastDebounce3) > debounceDelay) sensorState3 = reading;
    lastReading3 = reading;

    if (sensorState3 == LOW) break;
    motor3.runSpeed();
  }
  motor3.stop(); motor3.setCurrentPosition(0);
  Serial.println("Motor3 referenciado");

  // Motor 1
  motor1.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor1Pin);
    if (reading != lastReading1) lastDebounce1 = millis();
    if ((millis() - lastDebounce1) > debounceDelay) sensorState1 = reading;
    lastReading1 = reading;

    if (sensorState1 == LOW) break; // sensor activado
    motor1.runSpeed();
  }
  motor1.stop(); motor1.setCurrentPosition(0);
  Serial.println("Motor1 referenciado");


  referenciado = true;
  Serial.println("Referencia completa ✅");
}

// --- Función para mover a ángulos ---
void moverA_angulos(float q1_ref, float q2_ref, float q3_ref) {
  q1 = q1_ref * pi / 180;
  q2 = q2_ref * pi / 180;
  q3 = q3_ref * pi / 180;

  // Cinemática Directa
  r = L2 * cos(q2) + L3 * cos(q2 + q3);
  x = r * cos(q1);
  y = r * sin(q1);
  z = L1 + L2 * sin(q2) + L3 * sin(q2 + q3);

  // Cinemática Inversa
  D = (pow(x, 2) + pow(y, 2) + pow(z - L1, 2) - pow(L2, 2) - pow(L3, 2)) / (2 * L2 * L3);
  theta1 = atan2(y, x);
  theta3 = atan2(-sqrt(1 - pow(D, 2)), D);
  theta2 = atan2(z - L1, sqrt(pow(x, 2) + pow(y, 2))) - atan2(L3 * sin(theta3), L2 + L3 * cos(theta3));


  theta1=theta1*(180/pi);
  theta2=theta2*(180/pi);
  theta3=theta3*(180/pi);


  // Mover motores
  long destino1 = (long)(q1_ref * pasos_por_grado);
  long destino2 = (long)(q2_ref * pasos_por_grado);
  long destino3 = (long)(q3_ref * pasos_por_grado);

  motor1.moveTo(destino1);
  motor2.moveTo(destino2);
  motor3.moveTo(destino3);

  while (motor1.distanceToGo() != 0 || motor2.distanceToGo() != 0 || motor3.distanceToGo() != 0) {
    motor1.run();
    motor2.run();
    motor3.run();
  }

  Serial.print("Posición final X: "); Serial.println(x);
  Serial.print("Posición final Y: "); Serial.println(y);
  Serial.print("Posición final Z: "); Serial.println(z);

  Serial.print("Theta1: "); Serial.println(theta1);
  Serial.print("Theta2: "); Serial.println(theta2);
  Serial.print("Theta3: "); Serial.println(theta3);
}

// --- Setup ---
void setup() {
  Serial.begin(9600);

  pinMode(sensor1Pin, INPUT_PULLUP);
  pinMode(sensor2Pin, INPUT_PULLUP);
  pinMode(sensor3Pin, INPUT_PULLUP);

  motor1.setMaxSpeed(1600); motor1.setAcceleration(1000);
  motor2.setMaxSpeed(1600); motor2.setAcceleration(1000);
  motor3.setMaxSpeed(1600); motor3.setAcceleration(1000);

  delay(200); // dar tiempo a estabilizar lectura de sensores

  hacerReferencia(); // mover a home con antirrebote

  moverA_angulos(0, 0, 0);
  Serial.println("Brazo en Home");
  Serial.println("Ingrese q1,q2,q3 separados por comas. Ejemplo: 45,30,60");
}

// --- Loop principal ---
String inputString = "";
bool stringComplete = false;

void loop() {
  if (stringComplete) {
    int q1_i = inputString.indexOf(',');
    int q2_i = inputString.lastIndexOf(',');

    if (q1_i > 0 && q2_i > q1_i) {
      float q1_input = inputString.substring(0, q1_i).toFloat();
      float q2_input = inputString.substring(q1_i + 1, q2_i).toFloat();
      float q3_input = inputString.substring(q2_i + 1).toFloat();

      moverA_angulos(q1_input, q2_input, q3_input);
    } else {
      Serial.println("Formato incorrecto. Use: q1,q2,q3");
    }

    inputString = "";
    stringComplete = false;
    Serial.println("Ingrese nuevos valores q1,q2,q3:");
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n') stringComplete = true;
  }
}

Cuando introduzco los valores 100,100,100

Me aparecen valores que obviamene no son los que introduje. Voy a revisar más sobre este problema

¿comprobaste que tus ecuaciones estén correctas?

al ingresar los siguientes datos en MATLAB…

L1 = 100;
L2 = 130;
L3 = 170;

q1_ref = 45;
q2_ref = 30;
q3_ref = 60;

q1 = q1_ref * (pi / 180)
q2 = q2_ref * (pi / 180)
q3 = q3_ref * (pi / 180)

% Cinemática Directa}
r = L2 * cos(q2) + L3 * cos(q2 + q3)
x = r * cos(q1)
y = r * sin(q1)
z = L1 + L2 * sin(q2) + L3 * sin(q2 + q3)

% Cinemática Inversa
D = (x^2 + y^2 + (z - L1)^2 - L2^2 - L3^2) / (2 * L2 * L3)

theta1 = atan2(y,x)
theta3 = atan2(-sqrt(1 - D^2), D)
theta2 = atan2(z - L1, sqrt(x^2 + y^2)) - atan2(L3 * sin(theta3), L2 + L3 * cos(theta3))

theta1_degree = theta1 * (180/pi)
theta2_degree = theta2 * (180/pi)
theta3_degree = theta3 * (180/pi)

se obtiene la siguiente salida:

q1 = 0.7854
q2 = 0.5236
q3 = 1.0472

r = 112.5833
x = 79.6084
y = 79.6084
z = 335

D = 0.5000

theta1 = 0.7854
theta3 = -1.0472
theta2 = 1.7245

theta1_degree = 45.0000
theta2_degree = 98.8039
theta3_degree = -60.0000

Hola belmont1591!

Sí, es justo lo que estaba viendo. Utilice MATLAB para ir comprobando y llegué a la conclusión que mis variables en float al parecer no llegaban a los caracteres necesarios y no realizaban bien los calculos. Así que los puse en double y ya funcionó. Además, tuve que poner en las ecuaciones "signos diferentes" como:

clear all;
close all;

L1=100;
L2=130;
L3=170;

q1_gra=90;
q2_gra=50;
q3_gra=20;

pi=3.1416;

q1=q1_gra*pi/180;
q2=q2_gra*pi/180;
q3=q3_gra*pi/180;

r=L2*cos(q2)+L3*cos(q2+q3);
x=r*cos(q1)
y=r*sin(q1)
z=L1+L2*sin(q2)+L3*sin(q2+q3)

D=(x^2+y^2+(z-L1)^2-L2^2-L3^2)/(2*L2*L3)
D = max(min(D, 1), -1);  % Limita D entre -1 y 1
t1=atan(y/x);
t3=atan(-sqrt(1-D^2)/D);
t2=atan((z-L1)/(sqrt(x^2+y^2)))-atan((L3*sin(t3))/(L2+L3*cos(t3)));

t1=-t1*180/pi
t2=t2*180/pi
t3=-t3*180/pi

Porque necesito que sea "codo arriba" por la forma del brazo. Pero no sé porque mis t1 t2 t3 no salen igual que mis q1 q2 q3 en algunos casos. Por ejemplo, cuando introduzco 180,50,20 me sale que t1 es 0 y por ende el motor no deberia moverse, pero no tiene sentido que no se mueva. Pero en mi codigo arduino al poner, sí gira 180°, ya que está con los valores que le ingreso.

Ahora cabe recalcar que descubrí que mis motores paso a paso tenia moto reductores que son FLE42-10. Por lo que a mis pasos tuve que multiplicar por 10. Aún así. Probando girar 180°, tuve que ponerle una cantidad de pasos exagerada porque sino no llegaba.

#include <AccelStepper.h>
#include <math.h>

// --- Pines de los motores ---
const int dir1 = 9, step1 = 10;
const int dir2 = 7, step2 = 6;
const int dir3 = 2, step3 = 3;

// --- Pines de los sensores (NC/NO) ---
const int sensor1Pin = 13;
const int sensor2Pin = 12;
const int sensor3Pin = 11;

// --- Creación de motores ---
AccelStepper motor1(AccelStepper::DRIVER, step1, dir1);
AccelStepper motor2(AccelStepper::DRIVER, step2, dir2);
AccelStepper motor3(AccelStepper::DRIVER, step3, dir3);

// --- Parámetros del brazo ---
const float L1 = 120;
const float L2 = 130;
const float L3 = 170;

double pi = 3.1416;


const float pasos_por_rev = 8280.0;  
const float grados_por_rev = 360.0;
const float pasos_por_grado = pasos_por_rev / grados_por_rev;  

double q1, q2, q3;
double theta1, theta2, theta3;
double x, y, z, r, D;

bool referenciado = false;

// --- Variables antirrebote ---
const unsigned long debounceDelay = 50;
unsigned long lastDebounce1 = 0, lastDebounce2 = 0, lastDebounce3 = 0;
int lastReading1 = HIGH, lastReading2 = HIGH, lastReading3 = HIGH;
int sensorState1 = HIGH, sensorState2 = HIGH, sensorState3 = HIGH;

// --- Función de referencia con antirrebote ---
void hacerReferencia() {
  Serial.println("Iniciando referencia...");

  // Motor 2
  motor2.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor3Pin);
    if (reading != lastReading2) lastDebounce2 = millis();
    if ((millis() - lastDebounce2) > debounceDelay) sensorState2 = reading;
    lastReading2 = reading;

    if (sensorState2 == LOW) break;
    motor2.runSpeed();
  }
  motor2.stop();
  motor2.setCurrentPosition(0);
  Serial.println("Motor2 referenciado");

  // Motor 3
  motor3.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor2Pin);
    if (reading != lastReading3) lastDebounce3 = millis();
    if ((millis() - lastDebounce3) > debounceDelay) sensorState3 = reading;
    lastReading3 = reading;

    if (sensorState3 == LOW) break;
    motor3.runSpeed();
  }
  motor3.stop();
  motor3.setCurrentPosition(0);
  Serial.println("Motor3 referenciado");

  // Motor 1
  motor1.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor1Pin);
    if (reading != lastReading1) lastDebounce1 = millis();
    if ((millis() - lastDebounce1) > debounceDelay) sensorState1 = reading;
    lastReading1 = reading;

    if (sensorState1 == LOW) break;  // sensor activado
    motor1.runSpeed();
  }
  motor1.stop();
  motor1.setCurrentPosition(0);
  Serial.println("Motor1 referenciado");


  referenciado = true;
  Serial.println("Referencia completa ✅");
}

// --- Función para mover a ángulos ---
void moverA_angulos(double q1_ref, double q2_ref, double q3_ref) {
  Serial.print("Valor q1 introducido: ");
  Serial.println(q1_ref);
  Serial.print("Valor q2 introducido: ");
  Serial.println(q2_ref);
  Serial.print("Valor q3 introducido: ");
  Serial.println(q3_ref);

  q1 = q1_ref *pi/180;
  q2 = q2_ref *pi/180 ;
  q3 = q3_ref *pi/180;

  // Cinemática Directa
  r = L2 * cos(q2) + L3 * cos(q2 + q3);
  x = r * cos(q1);
  y = r * sin(q1);
  z = L1 + L2 * sin(q2) + L3 * sin(q2 + q3);

  // Cinemática Inversa
  D = (pow(x, 2) + pow(y, 2) + pow(z - L1, 2) - pow(L2, 2) - pow(L3, 2)) / (2 * L2 * L3);
  D = constrain(D, -1.0, 1.0);
  theta1 = atan2(y, x);
  theta3 = atan2(sqrt(-1 - pow(D, 2)), D);
  theta2 = atan2((z - L1), sqrt(pow(x, 2) + pow(y, 2))) - atan2(L3 * sin(theta3), L2 + L3 * cos(theta3));


  theta1 = theta1 * (180 / pi);
  theta2 = theta2 * (180 / pi);
  theta3 = theta3 * (180 / pi);


  // Mover motores
  long destino1 = (long)(q1_ref*pasos_por_grado);
  long destino2 = (long)(q2_ref * pasos_por_grado);
  long destino3 = (long)(q3_ref * pasos_por_grado);

  motor1.moveTo(destino1);
  motor2.moveTo(destino2);
  motor3.moveTo(destino3);

  while (motor1.distanceToGo() != 0 || motor2.distanceToGo() != 0 || motor3.distanceToGo() != 0) {
    motor1.run();
    motor2.run();
    motor3.run();
  }

  Serial.print("Posición final X: ");
  Serial.println(x);
  Serial.print("Posición final Y: ");
  Serial.println(y);
  Serial.print("Posición final Z: ");
  Serial.println(z);

  Serial.print("Theta1: ");
  Serial.println(theta1);
  Serial.print("Theta2: ");
  Serial.println(theta2);
  Serial.print("Theta3: ");
  Serial.println(theta3);
}

// --- Setup ---
void setup() {
  Serial.begin(9600);

  pinMode(sensor1Pin, INPUT_PULLUP);
  pinMode(sensor2Pin, INPUT_PULLUP);
  pinMode(sensor3Pin, INPUT_PULLUP);

  motor1.setMaxSpeed(1600);
  motor1.setAcceleration(1000);
  motor2.setMaxSpeed(1600);
  motor2.setAcceleration(1000);
  motor3.setMaxSpeed(1600);
  motor3.setAcceleration(1000);

  delay(200);  // dar tiempo a estabilizar lectura de sensores

  hacerReferencia();  // mover a home con antirrebote

  moverA_angulos(0, 0, 0);
  Serial.println("Brazo en Home");
  Serial.println("Ingrese q1,q2,q3 separados por comas. Ejemplo: 45,30,60");
}

// --- Loop principal ---
String inputString = "";
bool stringComplete = false;

void loop() {
  if (stringComplete) {
    int q1_i = inputString.indexOf(',');
    int q2_i = inputString.lastIndexOf(',');

    if (q1_i > 0 && q2_i > q1_i) {
      float q1_input = inputString.substring(0, q1_i).toFloat();
      float q2_input = inputString.substring(q1_i + 1, q2_i).toFloat();
      float q3_input = inputString.substring(q2_i + 1).toFloat();

      moverA_angulos(q1_input, q2_input, q3_input);
    } else {
      Serial.println("Formato incorrecto. Use: q1,q2,q3");
    }

    inputString = "";
    stringComplete = false;
    Serial.println("Ingrese nuevos valores q1,q2,q3:");
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n') stringComplete = true;
  }
}

No entiendo que estoy haciendo mal. Encontré un modelo similar que se llama Dobot magician. Intenté utilizar la cinematica que se encuentran en internet que hay varios papers. Pero sigue sin salirme :cry:. Estoy que me guio con la app RoboDK del Dobot Magician para ver las coordenadas, pero nada.

He tenido un avance, ya sé cuales son los rangos efectivos para mis motores y la cinematica directa medio funciona ir a los angulos que le doy. Seguiré revisando que más se puede mejorar.

Ya entendí porque el valor de esos pasos. Es porque

const float pasos_por_rev = 8000.0*(1.035);  // 200 * 4 * 10*ajuste = 8280.0 pasos/rev

200 de los pasos, 4 de mi microstep, 10 del reductor y un ajuste por la misma friccion. Y ahí si llega a los 180° que le doy.

Edit: Además, el porqué de los cambios de signos es que yo inicializo mi home en el 0,0,0 que es cuando choca los motores con los fin de carrera. Por ende, por más que los cálculos me den valores negativos en q1q2q3. Estos no pueden tener valores negativos porque colisionan. Pero eso también conlleva a que los cálculos salgan mal, me parece.

Por eso digo que el robot Dobot Magician se asemeja porque


Estos valores serían prácticamente los mismos para mi caso. El problema es que no existe una ficha técnica tan detallada como quisiera. Ya que mi brazo es un producto chino de Aliexpress. Así que seguiré con los calculos para ver que me cuadre todo.

¿Cabría la posibilidad de que el valor incorrecto del ángulo se deba a qué no se está tomando en cuenta el cuadrante en el que éste se mide?

Por ejemplo, recordando que dependiendo del cuadrante, se requiere que a theta se le sume/reste 180° o 360°

Veo que anteriormente habías utilizado la función “atan2” y luego “atan”

¿Tienes el link del paper y/o libro para revisar las ecuaciones de cinemática directa e inversa?

Ojo que si estás usando una placa UNO/Nano/Mega, float y double son equivalentes, tal vez ese sea parte del problema, la precisión de las ecuaciones.

Puede que en MatLab funcione bien cambiando la precisión de las variables pero no va a dar los mismos resultados en el modelo real.

Totalmente de acuerdo contigo belmont1591.
Por mi sistema, necesito que mis ángulos estén estrictamente en el primer cuadrante para tener que sean siempre positivos, ya que estoy tomando mi home como q1,q2,q3=0,0,0. Así que no pueden ser negativos porque se empiezan a chocar contra los limites de carrera.

Me estuve viendo varios videos de cinemática para brazos de 3 GDL e inclusive los de 4 GDL donde su efector final solo gira ahí mismo. Y me he dado cuenta que esos cálculos para brazos "estándar" no funcionan para mi brazo. Esto se explica un poco mejor de los cálculos que hice.

Lo que ocurre es lo siguiente, En el dobot magician dice que sus limites son -10<q3<95, 0<q2<85 y -90<q1<90. Pero en mi brazo dije que no puede admitir angulos negativos puesto que su home es el extremo de todo y mis angulos en 0. Por ende Sería aproximadamente 0<q3<90, 0<q2<90 y 0<q1<350. Con esto en mente decidí hacer la cinemática nuevamente.

Teniendo que en mi Cinematica Directa es:

R=L2*S2+L3*C23;
X=R*C1;
Y=R*S1;
Z=L1+L2*C2+L3*S23;

Y ahora sí tiene sentido cuando lo probé. Estuve midiendo con una cinta métrica y al darle al home (0,0,0) es 170,0,250. Ya que en el home mi q2 está totalmente vertical, por lo que en X solo influye L3. Y mi Z sería L1 y L1. Me aseguré en ver cuales caían en 0 o 1 para los senos y cosenos. Luego probé con otros valores como (90,20,30) y me di cuenta que mi Z aumenta cuando debía bajar. Así que por eso resto en Z en sin(q2-q3). Con esto, mis calculos están correctos en el sistema real. Yo le doy los angulos y me van a las coordenadas que quiero.

Sin embargo, quiero intentar lo mismo para la cinemática inversa y me encuentro con problemas. Ni se acerca ni logra dar los angulos en los cuadrantes correctos.

clc
clear

L1 = 120;
L2 = 130;
L3 = 170;

theta1 = 90;
theta2 = 20;
theta3 = 30;

%% --- Cinemática directa (para verificar) ---
S1=sind(theta1);
S2= sind(theta2);
S23= sind(theta2-theta3);
C1=cosd(theta1);
C2= cosd(theta2);
C23= cosd(theta3+theta2);

R=L2*S2+L3*C23;
X=R*C1;
Y=R*S1;
Z=L1+L2*C2+L3*S23;

fprintf('\nPosición: X=%.2f, Y=%.2f, Z=%.2f\n', X, Y, Z);

%% --- Cinemática inversa ---
r = sqrt(X^2 + Y^2);
q1 = atan2d(Y, X);
% q3 = acosd((r^2 + (Z-L1)^2 - L2^2 - L3^2) / (2 * L2 * L3));
num=r^2 + (Z-L1)^2 - L2^2 - L3^2;
den=2 * L2 * L3;
D=(num/den);
q3=atan2d(sqrt(1-D^2),D^2);
q2 = atan2d(Z-L1, sqrt(X^2 + Y^2)) - atan2d(L3*sind(q3), L2 + L3*cosd(q3));

fprintf('\nÁngulos calculados: q1=%.2f°, q2=%.2f°, q3=%.2f°\n', q1, q2, q3);

Sí entiendo, MaximoEsfuerzo.
A mi tambien me parecia imposible de creer, puesto que lo que los diferencia es el tamaño de la variable y con un float debería ser más que suficiente puesto que no son números muy grandes ni requiero tantos decimales. Y tienes toda la razón, acabo de hacer la prueba y ver diferencias entre el código que subí anteriormente y el actual y el error radicó en las fórmulas. Acabo de volver mis variables a float y no hubo inconveniente. Solo para tener más exactitud, dejaré a pi como double por si las dudas. Quedando mi código como:

#include <AccelStepper.h>
#include <math.h>

// --- Pines de los motores ---
const int dir1 = 9, step1 = 10;
const int dir2 = 7, step2 = 6;
const int dir3 = 2, step3 = 3;

// --- Pines de los sensores (NC/NO) ---
const int sensor1Pin = 13;
const int sensor2Pin = 12;
const int sensor3Pin = 11;

// --- Creación de motores ---
AccelStepper motor1(AccelStepper::DRIVER, step1, dir1);
AccelStepper motor2(AccelStepper::DRIVER, step2, dir2);
AccelStepper motor3(AccelStepper::DRIVER, step3, dir3);

// --- Parámetros del brazo ---
const float L1 = 120;
const float L2 = 130;
const float L3 = 170;

dobule  pi = PI;


const float pasos_por_rev = 8000.0*(1.035);  // 200 * 4 * 10*ajuste = 8280.0 pasos/rev
const float grados_por_rev = 360.0;
const float pasos_por_grado = pasos_por_rev / grados_por_rev;  

float q1, q2, q3;
float theta1, theta2, theta3;
float x, y, z, r, D;

bool referenciado = false;

// --- Variables antirrebote ---
const unsigned long debounceDelay = 50;
unsigned long lastDebounce1 = 0, lastDebounce2 = 0, lastDebounce3 = 0;
int lastReading1 = HIGH, lastReading2 = HIGH, lastReading3 = HIGH;
int sensorState1 = HIGH, sensorState2 = HIGH, sensorState3 = HIGH;

// --- Función de referencia con antirrebote ---
void hacerReferencia() {
  Serial.println("Iniciando referencia...");

  // Motor 2
  motor2.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor3Pin);
    if (reading != lastReading2) lastDebounce2 = millis();
    if ((millis() - lastDebounce2) > debounceDelay) sensorState2 = reading;
    lastReading2 = reading;

    if (sensorState2 == LOW) break;
    motor2.runSpeed();
  }
  motor2.stop();
  motor2.setCurrentPosition(0);
  Serial.println("Motor2 referenciado");

  // Motor 3
  motor3.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor2Pin);
    if (reading != lastReading3) lastDebounce3 = millis();
    if ((millis() - lastDebounce3) > debounceDelay) sensorState3 = reading;
    lastReading3 = reading;

    if (sensorState3 == LOW) break;
    motor3.runSpeed();
  }
  motor3.stop();
  motor3.setCurrentPosition(0);
  Serial.println("Motor3 referenciado");

  // Motor 1
  motor1.setSpeed(-800);
  while (true) {
    int reading = digitalRead(sensor1Pin);
    if (reading != lastReading1) lastDebounce1 = millis();
    if ((millis() - lastDebounce1) > debounceDelay) sensorState1 = reading;
    lastReading1 = reading;

    if (sensorState1 == LOW) break;  // sensor activado
    motor1.runSpeed();
  }
  motor1.stop();
  motor1.setCurrentPosition(0);
  Serial.println("Motor1 referenciado");


  referenciado = true;
  Serial.println("Referencia completa ✅");
}

// --- Función para mover a ángulos ---
void moverA_angulos(float q1_ref, float q2_ref, float q3_ref) {
  Serial.print("Valor q1 introducido: ");
  Serial.println(q1_ref);
  Serial.print("Valor q2 introducido: ");
  Serial.println(q2_ref);
  Serial.print("Valor q3 introducido: ");
  Serial.println(q3_ref);

  q1 = q1_ref *pi/180;
  q2 = q2_ref *pi/180 ;
  q3 = q3_ref *pi/180;

  // Cinemática Directa
  r = L2 * sin(q2) + L3 * cos(q2 + q3);
  x = r * cos(q1);
  y = r * sin(q1);
  z = L1 + L2 * cos(q2) + L3 * sin(q2 - q3);

  // Cinemática Inversa
  D = (pow(x, 2) + pow(y, 2) + pow(z - L1, 2) - pow(L2, 2) - pow(L3, 2)) / (2 * L2 * L3);
  D = constrain(D, -1.0, 1.0);
  theta1 = -atan2(y, x);
  theta3 = atan2(sqrt(1 - pow(D, 2)), D);
  theta2 = atan2((z - L1), sqrt(pow(x, 2) + pow(y, 2))) + atan2(L3 * sin(theta3), L2 + L3 * cos(theta3));


  theta1 = theta1 * (180 / pi);
  theta2 = theta2 * (180 / pi);
  theta3 = theta3 * (180 / pi);


  // Mover motores
  long destino1 = (long)(q1_ref * pasos_por_grado);
  long destino2 = (long)(q2_ref * pasos_por_grado);
  long destino3 = (long)(q3_ref * pasos_por_grado);

  motor1.moveTo(destino1);
  motor2.moveTo(destino2);
  motor3.moveTo(destino3);

  while (motor1.distanceToGo() != 0 || motor2.distanceToGo() != 0 || motor3.distanceToGo() != 0) {
    motor1.run();
    motor2.run();
    motor3.run();
  }

  Serial.print("Posición final X: ");
  Serial.println(x);
  Serial.print("Posición final Y: ");
  Serial.println(y);
  Serial.print("Posición final Z: ");
  Serial.println(z);

  Serial.print("Theta1: ");
  Serial.println(theta1);
  Serial.print("Theta2: ");
  Serial.println(theta2);
  Serial.print("Theta3: ");
  Serial.println(theta3);
}

// --- Setup ---
void setup() {
  Serial.begin(9600);

  pinMode(sensor1Pin, INPUT_PULLUP);
  pinMode(sensor2Pin, INPUT_PULLUP);
  pinMode(sensor3Pin, INPUT_PULLUP);

  motor1.setMaxSpeed(1600);
  motor1.setAcceleration(1000);
  motor2.setMaxSpeed(1600);
  motor2.setAcceleration(1000);
  motor3.setMaxSpeed(1600);
  motor3.setAcceleration(1000);

  delay(200);  // dar tiempo a estabilizar lectura de sensores

  hacerReferencia();  // mover a home con antirrebote

  moverA_angulos(0, 0, 0);
  Serial.println("Brazo en Home");
  Serial.println("Ingrese q1,q2,q3 separados por comas. Ejemplo: 45,30,60");
}

// --- Loop principal ---
String inputString = "";
bool stringComplete = false;

void loop() {
  if (stringComplete) {
    int q1_i = inputString.indexOf(',');
    int q2_i = inputString.lastIndexOf(',');

    if (q1_i > 0 && q2_i > q1_i) {
      float q1_input = inputString.substring(0, q1_i).toFloat();
      float q2_input = inputString.substring(q1_i + 1, q2_i).toFloat();
      float q3_input = inputString.substring(q2_i + 1).toFloat();

      moverA_angulos(q1_input, q2_input, q3_input);
    } else {
      Serial.println("Formato incorrecto. Use: q1,q2,q3");
    }

    inputString = "";
    stringComplete = false;
    Serial.println("Ingrese nuevos valores q1,q2,q3:");
  }
}

void serialEvent() {
  while (Serial.available()) {
    char inChar = (char)Serial.read();
    inputString += inChar;
    if (inChar == '\n') stringComplete = true;
  }
}

Ahora voy a ver como resolver la cinemática inversa que es lo que está fallando.

Dejalo si querés pero no hace diferencia en los chips AVR de 8 bits porque tanto float como double ocupan 4 bytes (por eso son equivalentes).