jueves, 18 de julio de 2013

Programación Arduino Parte 2

    Como continuación a Programación Arduino Parte 1 escribo esta entrada con los progresos sobre los primeros problemas surgidos.

    El problema de la inestabilidad de la velocidad de los motores, con bajones de velocidad a intervalos regulares y que de algún modo eran debidos a la transmisión de datos de la IMU a Arduino, ha quedado solucionada. He hecho una pequeña modificación en el código del AHRS 9DOF Razor IMU con el que sólo se calculan los ángulos y envían los datos a petición. Así quedan sincronizados el cálculo de los ángulos y envío de los datos con el tiempo de ciclo de la programación en Arduino. Con este cambio la velocidad de los motores es estable.
    El nuevo código para el AHRS 9DOF Razor IMU podéis encontrarlo en AHRS 9DOF Razor IMU Parte 3.

    Al tener Arduino que realizar la petición nos encontramos con que tanto la recepción como la transmisión de la UART de la placa van a estar ocupadas con la IMU así que tenemos que buscarnos un sistema alternativo para el envío de datos para diagnosis. La solución es la librería SoftwareSerial. Esta librería la usaremos sólo para transmitir ya que al ser todo por software la recepción produce interrupciones que afectarían a nuestro código.

    Con todo esto también he realizado la primera placa con la que poder montar todos los componentes y ensamblar todas las partes para poder hacer las primeras pruebas. La placa es muy mejorable e incluso hay alguna cosa que no puede funcionar... en concreto la transmisión de datos desde Arduino para diagnosis. Le habilité la salida A7 hacia un conector en el que ensamblar un emisor de 433 MHz para más tarde encontrarme que la librería SoftwareSerial sólo funciona en las salidas digitales y no en las analógicas como la A7. En el código lo hago a través del pin 12, como de momento la placa tenía previsto conectores para futuros servos puedo conectar ahí un cable.

    Otro fallo que he cometido y que daría marcha atrás si estuviera a tiempo es la placa que utilizo. Me decanté por una placa en eBay que llaman Meduino. La placa en sí está bastante bien, muy parecida a la Arduino Pro Mini. Sin embargo ahí radica el problema. Para ganar en compatibilidad veo mucho mejor usar un clon de la Arduino Pro Mini (o la original, vamos). Por ejemplo yo tengo el riesgo de que dejen de fabricar esa Meduino y no pueda hacer más placas o o pueda sustituir la que tengo ante una avería. Tal vez en la siguiente placa de componentes que haga me plantee "rehacerla" incorporando una Arduino Pro Mini 100% compatible.

    A continuación os adjunto los archivos en Eagle de la placa y el programa de Arduino (disculpad que en algunos sitios esté un poco caótico, está sin depurar ya que está desarrollado en fase de pruebas).

    El código está hecho para hacer las primeras pruebas sobre la estabilización del roll. Con los tres mandos circulares de la emisora, a los que tengo asignados los canales 5, 6 y 7, puedo variar en tiempo real los parámetros Kp, Ki y Kd del PID del roll.

Archivo para Arduino 1.0.3: Cuadricóptero v1.0
Archivos Eagle placa: Placa Cuadricóptero v2.0
Esquema placa: Esquema v2.0

AHRS 9DOF Razor IMU Parte 3

    Como continuación a AHRS 9DOF Razor IMU Parte 2 escribo esta entrada para subir el código con las mejoras necesarias explicadas en Programación Arduino Parte 2.

    Este código permite el cálculo y envío de datos a petición externa, desde Arduino.

Archivo para 9DOF Razor: Razor AHRS v2.0
Archivo para leer los datos enviados: Leer 9DOF Razor v2.0

sábado, 8 de junio de 2013

ESC Parte 2

Como continuación a ESC Parte 1 escribo esta entrada para comentar una incidencia que he tenido. Probablemente es algo obvio para aquellos que vienen del aeromodelismo pero lo anoto por sí alguien se encuentra en la misma situación que yo.

Yo adquirí dos ESC para ir realizando diversas pruebas. Cuando el proyecto estaba más adelantado fui a comprar los otros dos ESC pero me encontré con que ya no estaban. Así qué tubería que adquirir dos unidades muy parecidas. En principio la única diferencia es que los primeros traían el conector para la "Monitorización de descarga balanceada" y los segundos no. 

Tiempo después haciendo pruebas con los cuatro ESC y motores comprobé que no todos tenían la misma respuesta. Con el mismo pulso unos iban a una velocidad y otros a otra. Lo primero fue pensar en que fueran distintos los ESC comprados en dos veces pero no había una correlación. Tras muchas pruebas, estudios y búsqueda de información encontré la respuesta y con ella la solución. Los ESC se pueden calibrar y de hecho será una rutina para un aeromodelista. Se trata simplemente de indicar al ESC los puntos mínimo y máximo para los pulsos. Con una emisora de RC se hace con la correspondiente palanca llevándola al máximo y mínimo recorrido. Yo lo he hecho con el programa de control de los motores estableciendo el mínimo en 1000 µs y el máximo en 2000 µs. Llevando este procedimiento de la misma manera en los cuatro ESC desaparece el "problema". Y yo que me veía estableciendo linealizaciones particulares para cada ESC en la programación en Arduino...

sábado, 11 de mayo de 2013

Programación Arduino Parte 1

    En las distintas entradas he ido desarrollando el código para el funcionamiento de los bloques principales. Ahora hay que juntarlos todos. Por supuesto hay que tener en cuenta que el desarrollo del código de los distintos bloques se ha hecho pensando que tendrá que ir unido al resto.

    Cuando empecé a estudiar un poco sobre cuadricópteros y la programación en Arduino una característica que tuve claro que quería para mi proyecto fue que el código funcionara sin interrupciones. Hay aplicaciones para las que las interrupciones no suponen ningún problema. Sin embargo en un cuadricóptero lo considero un aspecto fundamental. Las interrupciones producen pequeños fallos en el timing que generan por ejemplo inexactitudes en las medidas de los canales de la emisora RC o saltos en los pulsos enviados a los ESC (y servos si los hay). Estos efectos tienen como consecuencia una mala estabilización del cuadricóptero.

    Quiero aclarar la problemática de las interrupciones. Lo que no podemos hacer es, por ejemplo, utilizar una librería ya confeccionada que utiliza interrupciones. Generalmente estas librerías no podemos activar y desactivar sus interrupciones según nos interese así que si la utilizamos sus interrupciones se producirán en cualquier momento dentro de nuestro código. En algunas partes no tendrá importancia pero en otras nos producirá grandes errores en los timing produciendo los fallos antes nombrados.
    Sin embargo sí que podríamos utilizar interrupciones si nos interesara si somos nosotros los que las controlamos, activándolas en el momento en que nos interese y desactivándolas una vez terminado ese proceso para que no intercepten el resto del código. Por ejemplo se puede desarrollar el código para el control de los motores (los disparos a los variadores) mediante interrupciones. Estas interrupciones estarán activadas justo el tiempo de duración de esta pequeña parte de código (entre 1 ms y 2,1 ms) con lo que no habrá problemas. Por el momento lo cierto es que yo no me he puesto más a fondo con las interrupciones y mis códigos los he desarrollado sin ellas pero probablemente se gane en precisión utilizándolas de esta manera. Este es un aspecto mejorable que tal vez aborde en el futuro.

    Según lo explicado, que el código no tenga interrupciones básicamente supone:

    - No poder usar la librería Servo.h para el control de los ESC (ni de servos). Esta librería funciona mediante interrupciones.
    - No poder utilizarlas para tomar las lecturas de los canales de la emisora RC (tal vez podrían utilizarse si las podemos desactivar pero no estoy seguro si conseguiríamos alguna mejora).
    - Secuencia del código totalmente lineal con estricto estudio de los tiempos de ejecución para que no se desborde el tiempo de ciclo.

    El tiempo de ejecución del código en el AHRS 9DOF Razor IMU con el envío de los datos está alrededor de los 15 ms, inferior a los 20 ms aproximados (medido en 19,58 ms) del ciclo de la emisora que marca el ciclo del programa. Por tanto en cada ciclo dispondremos de una lectura del IMU. La secuencia del programa será:

1 - Lectura de los canales de la emisora. Si queremos leer los 8 canales esto nos consume un tiempo entre 8,5 ms y 15 ms. Durante la lectura de la emisora tenemos limitado el uso del micro para otras tareas (sólo los intervalos sueltos de 1 ms anotados en el código). Por tanto nos quedan 19,58 ms del ciclo de la emisora menos los 15 ms del tiempo de ejecución, unos 4,5 ms para ejecutar el resto de tareas.

2 - Lectura de los datos del AHRS 9DOF Razor IMU enviados por puerto serie. Aunque el envío tarda unos 3 ms a la velocidad de 57600 baudios, Arduino tiene un buffer donde se van almacenando y la lectura cuando toda la trama de 17 bytes ha llegado es de sólo 0,13 ms.

3 - Ejecución de los PID para la estabilización que darán los valores necesarios para los cuatro motores. En este momento este apartado está sin terminar de desarrollar. No tengo datos de tiempos de ejecución pero la previsión es que no sean críticos.

4 - Control de los motores. Secuencia de código ya desarrollada que envía los disparos a los los ESC de los cuatro motores. El tiempo de ejecución está entre 1 ms y 2,1 ms.

5 - Otras tareas que se irán implementando con tiempos de ejecución previsibles muy cortos. Por ejemplo lectura de las tensiones de las celdas de la batería para prevenir que se descarguen en exceso.

    Con estos datos tenemos que el micro está ocupado 15 + 0,13 + 2,1 = 17,23 ms lo que nos deja 19,58 - 17,23 = 2,35 ms para la ejecución de los PID y otras tareas menores.

    En este punto he empezado a hacer pruebas con un código que engloba los puntos 1, 2, 4 y 5 y he tropezado con un problema. La velocidad de los motores no es estable, se acelera y frena un poco a intervalos regulares de algo menos de 1 segundo. Tras mucho investigar estoy llegando a una conclusión: la lectura del puerto serie produce interrupciones. Como el ciclo del envío de datos del AHRS 9DOF Razor IMU es distinto al del código en Arduino la recepción de datos se va produciendo en distintos momentos del código. Según cuándo se produzca nos interferirá en distintas partes del programa como ahora estamos viendo que lo hace en los pulsos a los motores.

    ¿Y cuál es la solución a este problema? Se me ocurre sincronizar la comunicación entre AHRS 9DOF Razor IMU y Arduino. Es decir, Arduino tiene que solicitar a AHRS 9DOF Razor IMU los datos y en ese momento es cuando tiene que enviarlos. Así tenemos que tener previsto que en ese instante de recepción de datos Arduino no esté haciendo otras tareas para que no interfiera con las mismas. Tenemos dos formas de hacerlo. Una es que AHRS 9DOF Razor IMU ejecute todo su código y envíe los datos ante la petición de Arduino. La otra es que AHRS 9DOF Razor IMU esté siempre ejecutando su código y sólo envíe los datos ante la petición de Arduino. Tendré que probar las dos opciones y ver los pros y contras de cada una. Por el momento se produce un gran inconveniente y es que al usar la transmisión de Arduino hacia el AHRS 9DOF Razor IMU ya no podemos usarlo para monitorizar resultados en el Monitor Serial del ordenador. Toca hacer pruebas y encontrar soluciones.

Tenéis una nueva entrada en Programación Arduino Parte 2.

domingo, 13 de enero de 2013

Estabilización

    Llegamos al punto que creo es el más crítico: la estabilización del cuadricóptero, que consigamos que se mantenga en vuelo lo más estático posible minimizando los vaivenes y vibraciones. Pensando que sería uno de los puntos sobre los que más podría aportar por el momento he tenido un pequeño gran tropiezo.
    Empecé haciendo una modelización matemática con su correspondiente bucle de realimentación con control mediante un PID. Para las primeras pruebas particularicé con un brazo del cuadricóptero (dos motores con sus dos hélices). Mis cálculos dieron que el sistema era estable con un PD. Sin embargo las pruebas reales con un brazo con sus dos motores sobre un balancín fueron bastante desastrosas. Varias son las causas por las que creo que no obtuve ningún buen resultado.

    - Puede influir que la modelización que hice para simplificarla haya llevado a producir resultados erróneos (por ejemplo desprecio el tiempo que a un motor con su hélice le cuesta alcanzar la velocidad que se le marca, es decir, desprecio el momento de inercia el conjunto motor-hélice).

    - Los rozamientos del brazo con el pie vertical que lo sujeta como balancín afectan al sistema como también los cables que iban de los motores y la IMU (situados en el brazo) a la batería y la placa Arduino (situados externamente junto al ordenador), desvirtuando la respuesta que realmente tendría en el aire.

    - También empiezo a creer que la estabilización de un cuadricóptero mediante un control PID es un sistema un poco caótico, es decir, pequeñas variaciones en las variables de entrada (los parámetros PID) producen salidas muy dispares.

    Añadir también que el sistema es tan complejo como que cuando estamos cerca de la posición de equilibrio necesitamos unos valores PID distintos a cuando estemos lejos de esa posición. O aunque no sea estrictamente necesario para que el sistema sea estable el cambio de valores del PID hace que tengamos una estabilidad del vuelo mayor. Tenemos que calcular los mejores valores PID en ambos casos y cambiarlos en el programa según intervengan unos u otros. También tendremos que decidir en qué situación (a qué ángulo sobre el punto de equilibrio) cambiamos esos valores.

    Cuando tengamos todo esto (para lo cual veo que aún me falta lo suyo...) un paso más allá será detectar si sobre una posición estática del cuadricóptero éste se está desplazando para que el bucle de control actúe y lo devuelva automáticamente a su posición. Esto espero que algún día llegue al punto de ponerme con ello...

   El siguiente paso es hacer unas pruebas más reales para lo cual estoy montando todos los elementos del cuadricóptero en un montaje prácticamente definitivo. No tendré cables  que desvirtúen los resultados. Las primeras pruebas la idea era hacerlas estando colgado el cuadricóptero de dos hilos sobre un brazo. De este modo vuelvo a la sencillez del balancín pero con menos rozamientos ya que puedo dar a los 4 motores una pequeña fuerza de modo que los hilos sean sólo un elemento de seguridad sustentándolo ligeramente. Sin embargo me han sugerido que lo mejor es hacer las pruebas sujetando el cuadricóptero con una mano en el centro. Probablemente empezaré de esa forma y cuando lo tenga algo controlado pasaré a lo de los hilos.

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

martes, 24 de julio de 2012

Leer emisora RC Parte 2

    Como continuación a Leer emisora RC Parte 1 escribo esta entrada en la que explico las mejoras necesarias que he introducido en el código. Son varios los cambios introducidos que producen una estabilidad muy grande en la medición de los canales y por eso paso a la versión 2.0.

    Recordemos que el código original tenía unas variaciones en las mediciones que oscilaban en unos 12 µs. Aunque pueda parecer poco con las pruebas que he ido haciendo es relativamente importante. Si en 20 ms o 40 ms (uno o dos ciclos) se produce esa variación brusca le estamos indicando al programa del cuadricóptero que queríamos esa variación brusca, los PID se pondrán en marcha y las señales finales a los motores también. Uno o dos ciclos más tarde estaremos indicando un cambio brusco en dirección contrario. El resultado será que no conseguiremos un ajuste fino del cuadricóptero ni una buena estabilidad. El suavizado de las lecturas mediante el promedio de las últimas lecturas (6 por defecto) aminora estos efectos pero tras estudiar el problema a fondo podemos mejorarlo mucho más.

    En primer lugar tenemos que estudiar por qué se produce esa variación en las medidas y he encontrado dos causas. Lo primero es pensar en la precisión de la propia emisora pero elimino esta posibilidad ya que cualquier motor con su ESC o servo funciona con una precisión y estabilidad exquisita cuando se maneja directamente con la emisora.
    Causas encontradas:
    La primera es la precisión en la medición del tiempo. La función micros() tiene una medición mínima de 4 µs, esto es, no nos da lecturas de por ejemplo 9 µs, 10 µs o 11 µs. Nos da lecturas de 8 µs o 12 µs. Sobre esta función no podemos hacer directamente nada.
    La segunda es la velocidad en la ejecución del código. Para muchas cosas Arduino es suficientemente rápido pero en casos como el que nos lleva vemos que se vuelve lento. Vamos con la pequeña parte del código en la que esto se produce:

  while (digitalRead(2) == 1) {
    if ((micros() - TiempoParcial1) > 2000) {
       ErrorRadio = true;
       break;
    }
    else {
      ErrorRadio = false;
    }
  }

    Aunque son muy pocas instrucciones cada una de ellas lleva algún microsegundo ejecutarla. El problema es que al estar todo dentro de un while el tiempo de ejecución no es fijo. Pongamos que el micro ejecuta la primera instrucción, el while. Lee la entrada y ve que es 1. Entonces sigue ejecutando todo el resto de instrucciones de modo que si nada más ejecutar la primera la entrada pasa a 0 no se dará cuenta hasta que vuelva otra vez al while lo que supone añadir el tiempo de ejecutar todas las instrucciones. Y eso para Arduino son unos cuantos microsegundos. En el siguiente ciclo sin embargo puede pasar que justo cuando la entrada cambia a 0 es cuando se ejecuta el while por lo que saldrá ya del mismo sin apenas pérdida de tiempo.

    Aleatoriamente las dos causas se van sumando o restando. Entre la anulación máxima o la suma máxima tenemos esa variación de 12 µs (que en alguna lectura esporádica llega incluso a 16 µs).

    Entonces, ¿qué podemos hacer? Sobre la precisión de micros() directamente nada. Y en la serie de instrucciones del while todas son necesarias y no conozco ninguna forma de optimizarlo salvo la instrucción (digitalRead(2) == 1). Esta instrucción del IDE está compuesta por una docena de instrucciones del micro. Las mediciones que he realizado me da que el tiempo de ejecución para la instrucción a = digitalRead(2) es de 4,8 µs. Hay otra forma de leer una entrada que es con los registros del micro. Para un Atmega 168/328 la instrucción es a = bitRead(PIND, 2) y he calculado un tiempo de ejecución de tan solo 0,06 µs. Utilizando esto el ahorro de tiempo es muy importante. Una prueba preliminar simplemente cambiando esta instrucción hace que el rizado en la medición baje a 8 µs con muchas zonas de 4 µs. Es más esporádica una variación de 12 µs. Pero además el rizado es menos caótico haciendo que el promedio de lecturas sea más estable.
    Sólo hay un problema y es que el mapeo de pines puede ser distinto y de hecho lo es de un micro a otro en el IDE. En concreto para los micros Atmega 8/168/328 el mapeo es el mismo pero en el Atmega 2560 cambia y en el Atmega 32U4 de la nueva placa Leonardo también. La instrucción sólo está probada para Atmega 8/168/328 pero anoto cómo debería ser para los otros micros aunque repito que no lo he probado:

                            Cualquier micro       Atmega 8/168/328          Atmega 2560            Atmega 32U4

Pin digital 2        digitalRead(2)        bitRead(PIND, 2)         bitRead(PINE, 4)       bitRead(PIND, 1)
Pin digital 3        digitalRead(3)        bitRead(PIND, 3)         bitRead(PINE, 5)       bitRead(PIND, 0)
Pin digital 4        digitalRead(4)        bitRead(PIND, 4)         bitRead(PING, 5)       bitRead(PIND, 4)

    Implementada esta mejora y con el promedio de los últimos 6 valores tomados se consigue tener un rizado máximo de unos 2 µs. Aunque tengo en mente otra posible mejora a nivel de filtrado por el momento considero el código totalmente operativo aunque más adelante tal vez me ponga a mejorarlo.

    Por último, como este código es el que usaremos como base de tiempos en el código completo del cuadricóptero hay que tener previsto un sustituto ante fallo de la emisora. Así, si se produce algún fallo la variable ErrorRadio se vuelve true pero tenemos que, además de seguir intentando volver a leer la emisora, establecer una rutina que haga que el resto del código siga ejecutándose cada 20 ms. Con tan sólo tres líneas de código ha quedado implementado

Archivo para Arduino 1.0.1: Leer radio v2.0