Algoritmo para mezclar entrada analógica de 2 ejes para controlar un accionamiento de motor diferencial

Estoy buscando información sobre cómo implementar una combinación adecuada de 2 señales analógicas de joystick (eje X e Y) para controlar un accionamiento de motor diferencial dual (accionamiento "similar a un tanque") usando un uC (ATMega328p en mi caso, pero lo mismo debería aplicarse a cualquier uC con entradas ADC y salidas PWM):

Tengo un stick analógico, que da 2 valores analógicos:

(dirección)X: 0 a 1023
(acelerador)Y: 0 a 1023

ingrese la descripción de la imagen aquí

La posición de reposo es (dirección y acelerador neutral) 512,512
Acelerador hacia adelante/dirección izquierda es 0,0
Totalmente hacia adelante-totalmente a la derecha es 1023,0
etc.

Los motores están controlados por 2 controladores de puente H, 2 pines PWM para cada uno (adelante, atrás), así:
Motor izquierdo: -255 a 255
Motor derecho: -255 a 255
(los valores positivos habilitan el pin PWM de avance, los valores negativos habilitan el retroceso pin PWM, 0 deshabilita ambos)

El objetivo es mezclar señales analógicas de joystick para lograr la siguiente respuesta:

a) Acelerador hacia adelante, dirección neutral = vehículo avanzando
b) Acelerador hacia adelante, dirección izquierda = vehículo avanzando y girando a la izquierda
c) Acelerador neutral, dirección izquierda = vehículo girando a la izquierda EN SU LUGAR que es el motor derecho completamente hacia adelante, el motor izquierdo completamente hacia atrás

...y de manera similar para otras combinaciones. Por supuesto, la salida debe ser "analógica", es decir, debe permitir una transición gradual, por ejemplo, de la opción a) a b) a c).

El concepto es:

http://www.lynxmotion.com/images/html/build123.htm

(1) Tenga en cuenta que mi algoritmo básico permite el control de velocidad de 'girar en el lugar' cuando se empuja el joystick, por ejemplo, hacia la izquierda en un % de la escala completa. (2) Este requisito debe haber sido resuelto muchas, muchas veces hasta ahora. La comunidad modelo debería tener respuestas a esto. (3) Si el receptor traduce los comandos a la velocidad de la pista usando retroalimentación, el vehículo se comportará de la misma manera cuando cambien las condiciones del suelo. PERO si los comandos se traducen en potencia del motor o voltaje de accionamiento, etc., el rendimiento del vehículo variará según las condiciones del suelo. - presumiblemente 91) es preferible.
Russell, busqué mucho en Google para la respuesta y encontré muchos controladores de motor listos para usar para conectarse directamente al receptor RC, pero no mucha información sobre el algoritmo interno.
¡buen día! renho, un primo que ha estado probando parálisis infantil y una construcción de una silla de ruedas, su programación funcionó bien, ¡pero el voltaje de salida es demasiado bajo! ¡ayúdame! Estoy usando un arduino uno.
@Johnny bienvenido a Electronics.Stackexchange! Consulte las preguntas frecuentes para comprender cómo funciona este sitio y, si tiene alguna pregunta, utilice el botón específico en la esquina superior derecha de la página.
Funcionó ???
En realidad, esto no es posible como un mapeo continuo, a menos que fuerce una parada o una discontinuidad entre el modo de giro en el lugar y uno de los giros hacia adelante o hacia atrás, prohíba el giro en el lugar o gire "en sentido contrario" en reversa. En la investigación de manuales de maquinaria de movimiento de tierra de tamaño completo, se indicó que el único con orugas que encontré con un solo joystick no podía girar en su lugar con las orugas.

Respuestas (3)

La mezcla "adecuada" está abierta a debate :-).

Un problema es que tienes que tomar decisiones sobre qué tan rápido se mueve una pista bajo señales puras de un solo potenciómetro y qué hacer cuando se incluyen señales del otro potenciómetro. Por ejemplo, si empuja el potenciómetro FB (Adelante-Atrás) completamente hacia adelante, y si ambos motores funcionan a toda velocidad hacia adelante, ¿cómo lidiará con la adición de una pequeña cantidad de potenciómetro LR (Izquierda-Derecha) que se agrega? Para obtener la rotación, debe tener una pista yendo más rápido que la otra. Entonces, si ya está corriendo a la velocidad máxima de avance en ambos motores, debe disminuir la velocidad de una u otra pista para poder girar. Pero, si hubiera estado parado, habría acelerado una u otra pista para lograr el mismo resultado.

Entonces, dicho todo esto, aquí hay una solución inicial simple e improvisada de mi cabeza que parece un buen comienzo.

Si las ollas son mecánicamente independientes, ambas pueden estar al 100% simultáneamente.
Si ambos están en una disposición de tipo joystick, si Yaxis = 100 % y Xaxis = 0 %, entonces agregar algo de B generalmente reducirá A. Se podría construir un joystick donde lo anterior no es cierto, pero esto es inusual.
Suponga que el joystick es del tipo que aumenta Y% cuando X = 100% reducirá X. Se pueden hacer otras suposiciones.

FB = pote delantero-trasero. Centro cero, +Ve para el movimiento hacia adelante de la olla

LR = bote izquierdo derecho. Centro cero. +Ve para bote a la derecha.

K es un factor de escala inicialmente 1.
Si algún resultado excede el 100 %, entonces ajuste K para que el resultado sea = 100 % y use el mismo valor de K para otro motor también.

  • por ejemplo, si el resultado del motor izquierdo = 125 y el resultado del motor derecho = 80, entonces.
    Como 125 x 0,8 = 100, establezca K = 0,8. Después.
    Izquierda = 125 x 0,8 = 100%. Derecha = 80 x 0,8 = 64%.

Después:

  • Motor izquierdo = K x (Frontal_Atrás + Izquierda_Derecha)

  • Motor derecho = K x (Frontal_Atrás - Izquierda_Derecha)

Controles de cordura:

  • LR = 0 (centrado), FB = full fwd -> Ambos motores funcionan completamente hacia adelante.

  • LR = completamente a la izquierda, FB = 0 ->
    El motor izquierdo funciona completamente hacia atrás,
    el motor derecho funciona completamente hacia adelante.
    El vehículo gira en sentido contrario a las agujas del reloj.

  • FB fue 100%, Lr = 0%. Agregue 10% de LR a la derecha.
    L = FB+LR = 100 %- + 10 % R = FB-LR = 100 %- - 10 %

Si el eje mayor < 100 %, escalar hasta = 100 %.
Luego escala otro eje por la misma cantidad.

Gracias Russell. Intentaré implementar esto en la configuración de mi modelo. Por cierto, mi joystick puede mantenerse completamente hacia adelante mientras lo gira de izquierda a derecha y al revés, es muy similar a esto: static.sparkfun.com/images/products/09032-03-L_i_ma.jpg
Actualmente tengo la tarea de resolver el mismo problema en el trabajo. Tengo un controlador de 2 ejes Wii Nunchuk y necesita controlar 2 motores exactamente como se describe en la pregunta. Estoy teniendo un poco de problemas para entender la lógica aquí. ¿A qué se refiere exactamente k1/K1? Uno está en minúsculas y el otro en mayúsculas, ¿son diferentes? ¿Qué es +Ve?
@Tal Tanto k1 como K1 eran idénticos, lo siento. Cambié ambos a K y agregué una explicación sobre cómo se llega a K. K es inicialmente = 1, pero si cualquier resultado del motor es > 100 %, entonces K se establece para que el resultado del motor sea = 100 % y el mismo valor si se usa K para el otro motor. Por lo tanto, si, por ejemplo, los resultados del motor fueran 150/90 %, K se establecería en 2/3, por lo que los resultados del motor ahora son 100/60. Ver respuesta
Genial - gracias por la aclaración. Necesitaba esto escrito en Python, así que si entiendo correctamente, esto debería funcionar: pastebin.com/sWDakvLp . ¿Parece que me estoy perdiendo algo? Parece funcionar en mi entorno de prueba: tendré que conectarlo a los motores finales que usaré para estar seguro.
@Tal En un vistazo rápido, parece correcto EXCEPTO que (a menos que me haya perdido algo) (1) usa 100 como valores máximos cuando podría usar, por ejemplo, 127 (2) Creo que mientras usaba abs (...) Está bien para ver si se necesitaba escalar, esto afecta incorrectamente el movimiento negativo del motor: mapea ambos motores en la dirección de avance si excede la velocidad máxima de avance o retroceso. || Menor en concepto. Fatal en ejecución. Se arregla fácilmente.
1) La velocidad del motor está controlada por PWM, que solo toma valores de 0 a 100, por lo que utilicé 100 como valor máximo. 2) Uso abs para encontrar si se necesita escalar (como dijiste) y para obtener el factor de escala. Si termino con un factor de escala de 0,8, por ejemplo, y lo uso en un número negativo, -125 * 0,8 = -100. Se mantiene la dirección. Creo que funciona, a menos que me esté perdiendo algo. Todavía no he tenido la oportunidad de probarlo en los motores finales: mi jefe construirá una plataforma de prueba con motores adjuntos en los que podré probar.
@Tal Sí, tengo razón, me perdí algo :-). Sí, multiplicar el factor de escala x las direcciones del motor firmadas es correcto (como dices). || Un máximo de 100 tiene sentido ahora que lo ha explicado. Poner un comentario sobre el rango de PWM en el código puede ser una buena idea.
No estaba seguro de si mi código realmente funcionaría, así que configuré el enlace de pastebin anterior para que caducara después de una semana. Dado que parece funcionar, aquí hay un enlace más permanente con algunos comentarios más si alguien vuelve a encontrar el problema: pastebin.com/EKguJ1KP . Pondría esto en una respuesta, pero aparentemente no tengo suficiente representante para publicar una respuesta. Todo el código se basa en la respuesta de Russel McMahon, el crédito es para él, gracias Russel.

Aquí hay una solución que no requiere complicadas cadenas if/else, no reduce la potencia cuando se mueve completamente hacia adelante o gira en el lugar, y permite curvas suaves y transiciones de movimiento a giro.

La idea es sencilla. Suponga que los valores del joystick (x, y) son coordenadas cartesianas en un plano cuadrado. Ahora imagina un plano cuadrado más pequeño girado 45º dentro de él.

avión de ejemplo

Las coordenadas del joystick te dan un punto en el cuadrado más grande, y el mismo punto superpuesto en el cuadrado más pequeño te da los valores del motor. Solo necesita convertir las coordenadas de un cuadrado a otro, limitando los nuevos valores (x, y) a los lados del cuadrado más pequeño.

Hay muchas formas de hacer la conversión. Mi método favorito es:

  1. Convierta las coordenadas iniciales (x, y) en coordenadas polares.
  2. Gírelos 45 grados.
  3. Convierte las coordenadas polares de nuevo a cartesianas.
  4. Vuelva a escalar las nuevas coordenadas a -1.0/+1.0.
  5. Sujete los nuevos valores a -1.0/+1.0.

Esto supone que las coordenadas iniciales (x,y) están en el rango -1.0/+1.0. El lado del cuadrado interior siempre será igual a l * sqrt(2)/2, por lo que el paso 4 consiste simplemente en multiplicar los valores por sqrt(2).

Aquí hay un ejemplo de implementación de Python.

import math

def steering(x, y):
    # convert to polar
    r = math.hypot(x, y)
    t = math.atan2(y, x)

    # rotate by 45 degrees
    t += math.pi / 4

    # back to cartesian
    left = r * math.cos(t)
    right = r * math.sin(t)

    # rescale the new coords
    left = left * math.sqrt(2)
    right = right * math.sqrt(2)

    # clamp to -1/+1
    left = max(-1, min(left, 1))
    right = max(-1, min(right, 1))

    return left, right

La idea original de este método, con un método de transformación mucho más complicado, surgió de este artículo .

esto parece muy fácil, y me está costando muchísimo validarlo... ¿puedes echarle un vistazo a mi código hacky para intentar validarlo? los valores izquierdo y derecho parecen discontinuos alrededor de 0. stackoverflow.com/q/66723576/3033594
@ testname123 Desafortunadamente, no puedo ayudarlo con eso, pero el código de muestra que pegué aquí se copió directamente de una implementación funcional, así que estoy seguro de que funciona. github.com/pjwerneck/besiege-scripts/blob/master/bsg/…

A continuación se muestra un ejemplo de implementación de algoritmo de mezcla como se describe en la respuesta de Russel McMahon:

http://www.youtube.com/watch?v=sGpgWDIVsoE

//Atmega328p based Arduino code (should work withouth modifications with Atmega168/88), tested on RBBB Arduino clone by Modern Device:
const byte joysticYA = A0; //Analog Jostick Y axis
const byte joysticXA = A1; //Analog Jostick X axis

const byte controllerFA = 10; //PWM FORWARD PIN for OSMC Controller A (left motor)
const byte controllerRA = 9;  //PWM REVERSE PIN for OSMC Controller A (left motor)
const byte controllerFB = 6;  //PWM FORWARD PIN for OSMC Controller B (right motor)
const byte controllerRB = 5;  //PWM REVERSE PIN for OSMC Controller B (right motor)
const byte disablePin = 2; //OSMC disable, pull LOW to enable motor controller

int analogTmp = 0; //temporary variable to store 
int throttle, direction = 0; //throttle (Y axis) and direction (X axis) 

int leftMotor,leftMotorScaled = 0; //left Motor helper variables
float leftMotorScale = 0;

int rightMotor,rightMotorScaled = 0; //right Motor helper variables
float rightMotorScale = 0;

float maxMotorScale = 0; //holds the mixed output scaling factor

int deadZone = 10; //jostick dead zone 

void setup()  { 

  //initialization of pins  
  Serial.begin(19200);
  pinMode(controllerFA, OUTPUT);
  pinMode(controllerRA, OUTPUT);
  pinMode(controllerFB, OUTPUT);
  pinMode(controllerRB, OUTPUT);  

  pinMode(disablePin, OUTPUT);
  digitalWrite(disablePin, LOW);
} 

void loop()  { 
  //aquire the analog input for Y  and rescale the 0..1023 range to -255..255 range
  analogTmp = analogRead(joysticYA);
  throttle = (512-analogTmp)/2;

  delayMicroseconds(100);
  //...and  the same for X axis
  analogTmp = analogRead(joysticXA);
  direction = -(512-analogTmp)/2;

  //mix throttle and direction
  leftMotor = throttle+direction;
  rightMotor = throttle-direction;

  //print the initial mix results
  Serial.print("LIN:"); Serial.print( leftMotor, DEC);
  Serial.print(", RIN:"); Serial.print( rightMotor, DEC);

  //calculate the scale of the results in comparision base 8 bit PWM resolution
  leftMotorScale =  leftMotor/255.0;
  leftMotorScale = abs(leftMotorScale);
  rightMotorScale =  rightMotor/255.0;
  rightMotorScale = abs(rightMotorScale);

  Serial.print("| LSCALE:"); Serial.print( leftMotorScale,2);
  Serial.print(", RSCALE:"); Serial.print( rightMotorScale,2);

  //choose the max scale value if it is above 1
  maxMotorScale = max(leftMotorScale,rightMotorScale);
  maxMotorScale = max(1,maxMotorScale);

  //and apply it to the mixed values
  leftMotorScaled = constrain(leftMotor/maxMotorScale,-255,255);
  rightMotorScaled = constrain(rightMotor/maxMotorScale,-255,255);

  Serial.print("| LOUT:"); Serial.print( leftMotorScaled);
  Serial.print(", ROUT:"); Serial.print( rightMotorScaled);

  Serial.print(" |");

  //apply the results to appropriate uC PWM outputs for the LEFT motor:
  if(abs(leftMotorScaled)>deadZone)
  {

    if (leftMotorScaled > 0)
    {
      Serial.print("F");
      Serial.print(abs(leftMotorScaled),DEC);

      analogWrite(controllerRA,0);
      analogWrite(controllerFA,abs(leftMotorScaled));            
    }
    else 
    {
      Serial.print("R");
      Serial.print(abs(leftMotorScaled),DEC);

      analogWrite(controllerFA,0);
      analogWrite(controllerRA,abs(leftMotorScaled));  
    }
  }  
  else 
  {
  Serial.print("IDLE");
  analogWrite(controllerFA,0);
  analogWrite(controllerRA,0);
  } 

  //apply the results to appropriate uC PWM outputs for the RIGHT motor:  
  if(abs(rightMotorScaled)>deadZone)
  {

    if (rightMotorScaled > 0)
    {
      Serial.print("F");
      Serial.print(abs(rightMotorScaled),DEC);

      analogWrite(controllerRB,0);
      analogWrite(controllerFB,abs(rightMotorScaled));            
    }
    else 
    {
      Serial.print("R");
      Serial.print(abs(rightMotorScaled),DEC);

      analogWrite(controllerFB,0);
      analogWrite(controllerRB,abs(rightMotorScaled));  
    }
  }  
  else 
  {
  Serial.print("IDLE");
  analogWrite(controllerFB,0);
  analogWrite(controllerRB,0);
  } 

  Serial.println("");

  //To do: throttle change limiting, to avoid radical changes of direction for large DC motors

  delay(10);

}
Interesante, parece que este código está alimentando 2 pines analógicos a 2 controladores de motor diferentes. Intentaré adaptar el código y modificarlo para mi configuración. Placa controladora Arduino Uno + 1 Sabertooth. 1 joystick al pin analógico A0 (x) pin A1 (y) leyendo y pasando valores al pin PWM 10 y 3 que van a S1 y S2 de Sabertooth. Creo que estoy cerca, pero me estoy confundiendo sobre cómo configurar el interruptor DIP en la placa Sabertooth. Por ahora, estoy probando con la configuración del interruptor para recibir una entrada analógica, el interruptor 4 todavía está en la posición de accionamiento diferencial, pero lo pondré en modo independiente más tarde para realizar más pruebas. Creo que este original
@user20514 bienvenido a electronics.stackexchange! Como puede notar, este no es un foro sino un sitio de preguntas y respuestas, por lo tanto, el espacio de respuestas no está destinado a la discusión. Sea libre de hacer una nueva pregunta si tiene algo que preguntar, o use comentarios para (de hecho) comentar sobre preguntas y respuestas existentes.
@Kamil: el video se muestra como privado. ¿Aún está disponible? youtube.com/watch?v=sGpgWDIVsoE
@RussellMcMahon reactivado :)