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

9 comentarios:

  1. Hola, he comprado la emisora turnigy 9x, hay que configurar algo en la propia emisora????
    Podrías poner un esquema del conexionado del receptor al arduino???


    Gracias.

    ResponderEliminar
  2. Hola José. El esquema de conexión está publicado en la entrada Leer emisora RC Parte 1.

    ResponderEliminar
  3. Muy interesante su información, me podría ayudar con un código para leer un solo canal, y que me activara un LED cuando una palanca este arriba y otro led cuando la palanca este abajo

    ResponderEliminar
  4. Para leer un solo canal puedes coger el código e ir eliminando todas las líneas que leen los canales del 2 al 8.
    Lo de activar los LED no te debería ser complicado si te has puesto a aprender y practicar un poco con Arduino. Si el valor del canal 1 es el máximo, encender LED 1 y si no lo es apagarlo. Si el valor es el mínimo, encender LED 2 y si no lo es apagarlo.

    ResponderEliminar
  5. Hola!! Porque no en Interrupcion externa para pin2 y pin3?Saludos!!

    ResponderEliminar
  6. Sí, es otra opción. Con interrupciones sólo en pin2 y pin3 no se producen interrupciones en puntos del programa no deseados. Lo que no sé es si la medida del tiempo tiene más resolución y se podría bajar esos 2µs de rizado.

    ResponderEliminar
  7. gracias que bien que leo tu block espero puedas seguir publicando eres un gran blogger :)

    ResponderEliminar
  8. Hola muchisimas gracias por el gran aporte, me esta siendo de grn ayuda.
    pero no lo logro entender.
    entiendo lo que respecta a while (bitRead(PIND, 2) == 0, pero lo que no logro entender es porque mides primero el tiempo parcial y despues en el bucle se lo restas al tiempo del while, micros() - TiempoParcial1????
    Espero que me puedas resolver mi duda

    TiempoParcial1 = micros();
    while (bitRead(PIND, 2) == 0)
    if ((micros() - TiempoParcial1) > 13000

    ResponderEliminar
  9. Hola. Esa instrucción está porque si se produce algún error con la emisora y la entrada nunca pasa a 1 el programa se quedaría indefinidamente en este while. Al cabo de 13 ms termino la instrucción y anoto que hay un error en la emisora en la siguiente línea de código que ya no has puesto y que es ErrorRadio = true;

    ResponderEliminar