martes, 28 de agosto de 2012

Control motores Parte 2

    Como continuación a Control motores Parte 1 escribo esta entrada en la que explico el cambio en la concepción del código que permite tener una gran precisión en los tiempos, sumamente importantes para que las señales enviadas a los variadores produzcan una regulación suave y precisa. Es tan grande el cambio en la concepción del programa y su precisión que paso a la versión 2.0.

    El código está preparado para el control de los 4 motores si bien su estructura permite de forma fácil ampliarlo para el control de más motores o servos.

    Para no aburrir con todo el largo proceso de prueba y error de las distintas formas que se me ocurría y que finalmente no funcionaban, paso a describir el funcionamiento de la opción adoptada.
    Hay que tener en cuenta que donde necesitamos gran precisión es en el tiempo que están activadas (en HIGH) las salidas. El tiempo de ciclo de 20 ms no tiene gran importancia que tenga pequeñas variaciones, de hecho las hay de una emisora a otra (en mi emisora Turnigy TGY 9X tengo calculado unos 19 ms) y si movemos varias palancas de la emisora a la vez el tiempo de ciclo de cada canal puede variar en algún milisegundo. Es más, podéis hacer la prueba en el código a cambiar el tiempo de ciclo en

if (micros() - TiempoControlCiclo >= 20000)

y en lugar de los 20000 µs poner valores mayores o inferiores. Si lo probáis con un servo con tiempos inferiores (por ejemplo 5000) veréis que al intentar mover la palanca del servo su respuesta es más limpia y no tiembla tanto ya que está actualizando su posición más veces por segundo (el cuádruple para un tiempo de 5000). En cambio si hacemos la prueba con valores más grandes, por ejemplo 50000, comprobamos que podemos mover más fácilmente la palanca de su posición ya que el servo está actualizándose menos veces por segundo, en nuestro ejemplo 2,5 veces más lento.

    Para conseguir la mayor precisión en tiempo en el HIGH tenemos que utilizar la instrucción delaymicroseconds() que tiene una precisión de 1 µs. Si utilizamos la función micros() su precisión es de 4 µs. La idea es ordenar los pulsos de menor a mayor, activar todas las salidas e ir desactivándolas con los delay. El tema es que esas instrucciones llevan un tiempo ejecutarlas además de que no se puede utilizar el delay si queremos que sea de 0 segundos. La solución es decalar los pulsos, es la constante "Retardo" que establezco en 10 µs. Así siempre podemos utilizar la instrucción delaymicroseconds() cuyo valor para cada salida se calcula según el pulso, el retardo y el tiempo de ejecución de las instrucciones que ha sido calculado empíricamente. Los cálculos de ordenar los pulsos y recalcular los tiempos de delay se calculan antes de establecer los HIGH de modo que conseguimos tener unos tiempos muy precisos para los pulsos. Además todo el código es ejecutado en un tiempo reducido, unos 32 µs los primeros cálculos más el decalaje (para 4 motores son 30 µs) más el tiempo del mayor pulso que en el caso de motores es de unos 2000 µs y para servos 2500 µs.

    Aquí tenéis el código con algún comentario que espero sea suficiente para entenderlo. Mi idea a medio plazo es sacar otra versión preparada para manejar algunos servos además de los 4 motores.

Archivo para Arduino 1.0.1: Control motores v2.0

23 comentarios:

  1. Hola que tal, estoy intentando hacer algo similar y encuentro algunas dudas.

    ¿Para que quieres utilizar 8 canales si solo necesitas 4? Con los cuatro podrias utilizar 4 entradas y olvidarte de los diodos y demas.

    Por otro lado dices que tienes ciertas variaciones en las lecturas de los pulsos y para enmendar el tema usas medias. Pero con las medias no lo vas a solucionar, pues siempre la lectura cambia y la media se vuelve loca, necesitarias muchas muestras y luego solo tendrias una cada cierto tiempo.

    Deberias utilizar un filtro de orden uno:

    v.filtrada= v.sin filtrar*K.filtro+V.filtrada*(1-k.filtro)

    Creo que por algun sitio tenia un programita con el filtro, dime tu correo.

    ResponderEliminar
  2. Hola. Creo que tu comentario en realidad va dirigido a la entrada "Leer emisora RC".
    Sobre el uso de 8 canales es para aprovechar todas las posibilidades de la emisora y con el tiempo poder ir implementando más funciones al cuadricóptero. Desde poder cambiar en vuelo en tiempo real los parámetros PID para pruebas hasta el manejo de una cámara fotográfica que algún día llegaré a poner.
    Sobre las medias no te entiendo, la media de X valores nunca se vuelve loca... simplemente promedia el valor haciendo un resultado parecido al de un filtro de orden 1. De hecho en la entrada indico que he hecho pruebas con los filtros Butterworth, Bessel y Chebyshev de orden 1 y 2 y he comprobado que en mi caso no se obtienen mejores resultados que con el promedio de valores. Con el promedio tampoco necesito muchas muestras, lo tengo ajustado por defecto a 6 y no se tiene un valor cada cierto tiempo, se promedian los últimos 6 valores, no en paquetes de 6 en 6 con lo que sigo teniendo un valor cada 20 ms aunque se produce un pequeño retardo de 120 ms.

    ResponderEliminar
    Respuestas
    1. Este comentario ha sido eliminado por el autor.

      Eliminar
    2. Este comentario ha sido eliminado por el autor.

      Eliminar
    3. Este comentario ha sido eliminado por el autor.

      Eliminar
    4. Juan López, estoy siguiendo este blog porque estoy construyendo mi cuadcopter para mi proyecto final de electronica. Como vas con respecto al control PID? Aún no he comenzado las pruebas en un balancín, lo empezare hoy pero aún no tengo bien claro como hacerlo. Podrías echarme una mano?

      Eliminar
  3. Hola Juan.

    Mi email puedes verlo entrando en mi perfil.

    No entiendo muy bien eso de que se te enciende el LED AREF, yo tengo una Arduino UNO y el AREF no tiene ningún LED, imagino que tu Mega sí la tiene. Lo que no entiendo es lo de que ya no se te enciende con un servo analógico ya que que yo sepa con ese código no se pueden controlar más que los servos analógicos que por otra parte son los mayoriteriamente utilizados.

    Sobre tu código tiene trampa :) La estabilidad de tu servo la consigues por la banda muerta que le pones de 3 grados. Muchas aplicaciones no admiten semejante banda muerta, de hecho al variar la palanca de la emisora se ve perfectamente que el servo se mueve a trompicones. Imagina si pones un par de servos así para el roll y pitch automático de estabilización de una cámara en el cuadricóptero. Si le quitas el código para la banda muerta se puede ver que el servo baila bastante.

    Sobre el filtro imagino que es cuestión de ajustar la constante de ganancia pero he comprobado que tiene un retardo del doble que el filtro con el promedio de 6 valores que yo hago. Y a pesar de producir mayor retraso en las zonas planas tiende a tener un pequeño rizado que prácticamente no se tiene con el promedio.

    En esta web

    http://www.schwietering.com/jayduino/filtuino/

    saqué todo tipo de filtros de primer y segundo orden para comprobar que lo que mejor resultados me daba era el promedio.

    ResponderEliminar
  4. Hola otra vez Juan.

    Olvidaba preguntarte sobre lo del "control adaptativo predictivo" en lugar de PID. ¿En qué consiste ese control?

    ResponderEliminar
  5. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  6. Ese tipo de control, ¿tiene algo que ver con la lógica difusa? Yo sólo he estudiado control por realimentación con PID.

    La librería servo funciona con interrupciones y tengo comprobado que puede interferir con algunas partes de códigos críticos con el cómputo del tiempo. Por eso decidí estructurar todo el código de forma lineal, sin interrupciones. Tengo cuantificado el tiempo de ejecución de todas las partes de modo que se ejecutan todas en menos de 20 milisegundos que es el tiempo de ciclo que lo marco con la emisora RC. Leo la emisora, leo los datos del IMU, ejecuto la librería PID y controlo los motores. El código que he desarrollado para los motores envía los pulsos a todos a la vez con lo que se ejecuta en un máximo de 2,5 ms.

    Pues sí que controlo yo esto que no sale mi email... El mío también es de gmail: cheyenneforoarduino

    ResponderEliminar
  7. Debo felicitarte sinceramente por este gran trabajo, te has marcado un curro impresionante y parece que con unos resultados buenísimos.
    Yo estoy empezando con mi cuacopter aunque de momento estoy recopilando información porque nunca he programado y estoy perdidisimo además de soprendido del nivel que hay por la red con arduino, jeje. Este blog es el que mas me está ayudando con mi proyecto y espero volar pronto mi cuacopter. Gracias por este blog.

    ResponderEliminar
  8. Por cierto, tengo una duda, aunque aun no me ha llegado la emisora pero es una turnigy 6x. Podría sumar los canales tal y como has hecho tu, conectando 3 canales a un pin y los otros 2 canales a otro pin? o con tu código tendría variaciones de respuesta al programarlo para 9 canales?
    gracias por anticipado, estoy pez con arduino.

    ResponderEliminar
  9. Hola Edwin, me alegro de que te esté siendo tan útil mi blog. Como siempre digo, ten en cuenta que mi cuadricóptero todavía no vuela. No porque no lo haya conseguido, simplemente porque puedo invertir muy poco tiempo y por tanto voy despacio.

    Mi código te podría valer para leer los 6 canales pero tienes que borrar las líneas de código que lee los canales 7 y 8 porque al no leer la señal se activará a true la variable ErrorRadio.

    ResponderEliminar
  10. Cheyenne, gracias por la respuesta. Cuando me llegue la emisora haré algunas pruebas haré lo que dices a ver qué tal.
    Te comento otra duda, a ver si puedes orientarme. Para estabilizar mi cuadcoptero estoy utilizando el acelerómetro del Nunchuck Wii, de momento todo va bien sin embargo no consigo que una vez el acelerómetro vuelva a su posición horizontal, el motor vuelva a tener las revoluciones anteriores. Es decir, de momento solo consigo que el acelerómetro sume revoluciones a cada motor pero no que vuelvan a su estado anterior una vez esté en horizontal.
    Te copio el código y si puedes le echas un vistazo.
    Gracias


    int ejeY; int ejeX; int joyX;

    void setup() {

    Serial.begin(115200);
    chuck.begin();
    chuck.update();
    chuck.calibrateJoy();

    }

    void loop() {
    chuck.update(); //xMap = map(ejeX,-127,127,-127,127);
    ejeX=chuck.readAccelX();
    ejeY=chuck.readAccelY();
    joyX=chuck.readJoyY();

    delay (50);
    // Habilitamos la consola para aumentar o disminuir los pulsos

    if (ejeX>=16) {
    PulsoMotorDerecha = PulsoMotorDerecha + ejeX/50;
    }
    else
    {PulsoMotorDerecha=PulsoMotorDelante;
    }
    if (ejeX<=-6) {
    PulsoMotorIzquierda = PulsoMotorIzquierda - ejeX/50;
    }
    else{PulsoMotorIzquierda =PulsoMotorDelante;
    }

    if (ejeY>=5) {
    PulsoMotorDelante = PulsoMotorDelante + ejeY/50;
    }


    if (ejeY<=-6) {
    PulsoMotorAtras = PulsoMotorAtras - ejeY/50;
    }
    else{PulsoMotorAtras =PulsoMotorDelante;
    }

    if (joyX>15){
    PulsoMotorDerecha = PulsoMotorDerecha + joyX/50; PulsoMotorIzquierda = PulsoMotorIzquierda + joyX/50; PulsoMotorDelante = PulsoMotorDelante + joyX/50;
    PulsoMotorAtras= PulsoMotorAtras + joyX/50;

    }
    if (joyX<-15){
    PulsoMotorDerecha = PulsoMotorDerecha + joyX/50; PulsoMotorIzquierda = PulsoMotorIzquierda + joyX/50; PulsoMotorDelante = PulsoMotorDelante + joyX/50;
    PulsoMotorAtras= PulsoMotorAtras + joyX/50;

    }

    ResponderEliminar
  11. Edwin, no sé muy bien qué quieres que haga tu código. Tampoco he utilizado esa librería y no sé lo que representan las variables ejeX, ejeY y joyX. De todas formas creo que tienes que leer bastante más documentación en la red para ver cómo resuelven la estabilización. Para empezar te diré que para mantenerlo horizontal las variaciones de velocidad que le des a un motor se las tienes que dar al contrario al opuesto. Además esas variaciones siempre hay que controlarlas a través de un PID, si no nunca conseguirás estabilizarlo.

    ResponderEliminar
  12. Gracias Cheyenne, estoy en ello. Leyendo documentación y ahora estoy buscando todo lo posible sobre el control PID, a ver si consigo enterarme de las cosas mas importantes durante lo que queda de mes. ahh las variables que mencionas son las que utilizo para la lectura del acelerometro del Nunchuck, es loq ue estoy utilizando como acelerometro porque sale mucho mas ecónomico.

    ResponderEliminar
  13. Hola Cheyenne, ya estoy más documentado sobre el control PID, al menos ya se lo que es y una ligera idea de como implementarlo en mi código final. Hoy empiezo a hacer unas pruebas en un balancín y dos motores. Espero dar con los parámetros correctos y del como atacar los dos motores a la vez con el control PID. Aunque me surge aún la duda de si tengo que utilizar tantos controles PID como número de motores o con 2 PID puedo controlar los 4 motores. Que me dirías? Te agradecería un comentario.
    Gracias por adelantado

    ResponderEliminar
  14. Hola Edwin. Perdona pero se me pasaron estos comentarios tuyos.
    En general hay que establecer un PID por cada grado de libertad del sistema. El balanceo un PID, el alabeo otro PID, la guiñada otro. De esta forma a un motor realmente le está llegando la suma de los PID de los grados de libertad en los que interviene.

    ResponderEliminar
  15. Hola, cheyenne, mis mas sinceras felicitaciones por tu blog, lo he descubierto recientemente y me encanta. Tengo una pregunta que hacerte, ¿como controlas los tiempos de ejecución de los programas? ¿Utilizas algún programa en particular? Un saludo

    ResponderEliminar
  16. Gracias Césarcmu.
    No utilizo ningún programa en particular para calcular los tiempos de ejecución. Utilizo la función millis() o micros() según la precisión que necesito, poniendo las instrucciones entre los eventos que quiero calcular y saco el tiempo por la consola.

    ResponderEliminar
  17. donde esta el código que no logro verlo?

    ResponderEliminar
  18. Hola. Lo tienes al final de la entrada, está el enlace a la descarga, "Control motores v2.0"

    ResponderEliminar