O al menos lo más exacta posible.
Hasta ahora no me había fijado, pero las mediciones del tamaño del pulso PWM tenían algo de ruido. Si bien para muchas aplicaciones sería totalmente aceptable, personalmente para mí es irritante. Veamos el problema:
Se trata de un error cíclico que dura entre 3 y 4 segundos (en el ejemplo), en ambos ejes, y en diferente momento. He tomado una captura donde se pueden ver ambos. En rojo están marcados el inicio y fin del período de error que se repite tras unos segundos. En dichos períodos, las mediciones tomadas tienen un error de una unidad (positiva o negativa). Además de ello, vemos que hay una cantidad considerable de dientes.
1ª mejora del algoritmo de lectura PWM
Que aparezcan dientes en la lectura es normal: si el acelerómetro está en una posición en la que la señal fluctúa entre 2 valores porque el valor que correspondería sería sería “el medio”, es normal que cualquier mínima vibración pueda generar dicho ruido. Sin embargo, cuando en todo momento y posición hay ruido, lo más probable es que otro sea el problema.
Para tratar de mejorar la lectura, lo primero que hay que hacer es permitir que el servicio de las interrupciones sea no bloqueante (permitir anidación de interrupciones). Esto podría causar un desbordamiento de pila en el peor de los casos, pero en el nuestro no debería.
Tras modificar el código para que realice las operaciones críticas al principio de la interrupción de manera atómica (con las interrupciones deshabilitadas), y permitir interrupciones después, el resultado ha sido este:
Como se puede ver en la captura, con esa modificación se han eliminado casi todos los dientes de la señal (más detalle sobre esto más adelante), pero el error cíclico no. ¿Qué es lo que puede estar ocasionando un error de una unidad?
La resolución de medida y el desfase incremental (prescaler)
El timer/contador de 0-255 es configurable mediante un prescaler de 1,8,16,32,64,128,256 o 1024, de manera que la cuenta sólo se incrementa cada tantos ciclos como indica el prescaler. Configurado con un prescaler de 1024, un ciclo PWM del acelerómetro corresponde con 156,25 unidades. He aquí, una fuente de error: hay un desfase entre los ciclos PWM y la resolución de medida, de manera que se acumula un desfase de 0,25 unidades (256 ciclos). En el siguiente diagrama se suponen grupos compuestos por 156 unidades (aunque no se dividen en 156 por motivos de visibilidad):
¿Qué supone un desfase incremental? Lo explicamos en el siguiente diagrama, donde una señal de longitud 2,5 unidades puede tomar diferentes valores en función del desfase acumulado:
En la señal azul, que comienza cerca del principio de la cuenta del prescaler, la longitud de la señal medida es “3-1=2” unidades. Similar al caso de la señal roja.
Sin embargo, la señal verde, de igual duración que las anteriores, al comentar cerca del final de la cuenta del prescaler, su logitud medida es “4-1=3” unidades.
Es decir, la misma señal genera medidas distintas, por lo que obtendremos unos dientes en la señal medida.
Dado que el desfase teórico es de 0,25 unidades, se podríaa multiplicar por 4 la resolución utilizando un prescaler de 256 (4 veces menos que 1024), por lo que cada ciclo PWM duraría 624 unidades exactas de timer. Necesitamos aumentar la resolución del timer por software porque el máximo es 255 unidades.
Como ejemplo explicativo, se muestra el ejemplo anterior habiendo dividido las unidades de tiempo en 2 (duplicada la resolución), de manera que el valor es la diferencia de tiempo dividida a la mitad (redondeada hacia abajo):
En el caso de las señales azul y roja: (6-1)/2 = 5/2 = 2.5 => 2 unidades
En el caso de la señal verde: (7-2)/2 = 5/2 = 2.5 => 2 unidades
De todos modos, el desfase de 0,25 unidades no es suficiente razón para crear el error cíclico que encabeza este artículo, sino que sería motivo para tener tres datos con error cada 4 lecturas (que en otras pruebas se aprecia más).
La solución aplicada hasta ahora
1 – Incrementar la resolución de nuestro timer mediante software. El memsic2125 tiene una resolución de mgs, lo que significa que para 8gs se necesitaría una resolución de 8000 unidades. Al disminuir el preescaler a 256 multiplicamos la resolución por 4 hasta 624, que después redondearemos a 0-155. No necesitamos una altísima resolución porque nuestro objetivo no es tomar medidas precisas, así que utilizar una resolución de 620 para redondear posteriormente da un buen resultado.
2- Definir los servicios de interrupciones como no bloqueantes. Realmente lo que se hace es definirlas como bloqueantes, ejecutar todo el código crítico al principio del servicio, y activar las interrupciones de nuevo. De este modo los timestamps son mucho más fiables y, recordemos, con más resolución (con preescaler de 1024 el error estaba asegurado).
La frecuencia variable
Seguimos, pues, teniendo un error cíclico. Para tratar de arreglarlo, es necesario fijarque que el visualizador muestra que la recepción de datos fluctúa entre 99 y 102 datos por segundo a pesar de que el datasheet del memsic2125 dice que la señal es de 100Hz; este valor es un redondeo y el visualizador lo calcula de manera poco precisa, pero sí que da la idea “de que algo pasa”, ya que fluctúa y no entre 2 simples valores.
Puede ser que el motivo sea que el oscilador interno del sensor fluctúa sensiblemente (sea debido al propio sensor, a la temperatura, al ruido que pueda introducir el arduino en la alimentación, error del reloj de arduino, …). Hay que tener en cuenta que esto implica que:
- No podemos calcular de manera exácta el valor máximo del timer: el valor máximo 156,25 (o 625) dependen del hecho de que la señal sea de 100Hz.
- Aumentar la resolución modificando el prescaler no evita las “décimas de unidad” de duración que causan desfase, como el visto más arriba, de la señal.
- El inicio de cada ciclo PWM se dessincronizará del reloj del arduino.
Determinación de la frecuencia:
Para la determinación de la frecuencia he desarrollado un pequeño sketch de arduino que permite medir la duración de los ciclos PWM (de flanco de subida a flanco de subida). Para ello, permite elegir entre utilizar el Timer/counter1 (16bits) con la funciónalidad de Input Capture, o utilizar el Timer/counter2 realizando la captura por software mediante la interrupción Pin Change Mode.
Descarga del sketch para medir la longitud de ciclo PWM.
Medida con el timer/counter1:
Para utilizar el modo de captura (input capture) del timer/counter1, la señal a medir ha de estar obligatoriamente conectada al pin 8 del arduino.
Cada vez que la señal tiene un flanco positivo, el valor del timer/counter1 se copia automáticamente en el registro temporal de captura ICR1, de modo que aunque el timer/counter1 siga contando, podemos consultar el momento exacto en el que ocurrió el flanco positivo. En el código es necesario configurar el registro TCCR1B con los valores de prescaler del contador según el datasheet del ATmega168.
Por el puerto serie (a 115200 bps) el sketch mostrará los distintos valores máximo y mínimo leídos en el tiempo (previo descarte de los 10 primeros por ser posiblemente erróneos).
Los resultados para la señal recibida del acelerómetro, durante una ejecución de 30-60 segundos, son los siguientes:
prescaler | minimo valor de ICR1 | máximo valor de ICR1 | desfase de valor con relación al máximo | Frecuencias mínima/máxima |
---|---|---|---|---|
8 | 19926 (9,963 ms) | 19942 (9,971 ms) | 16 unidades => 0,08% | 100,290… Hz / 100,37…Hz |
64 | 2490 (9,96 ms) | 2493 (9,972 ms) | 3 unidades => 0,12% | 100,280..Hz / 100,40..Hz |
256 | 623 (9,968 ms) | 624 (9,984 ms) | 1 unidad => 0,16% | 100,16..Hz / 100,32..Hz |
1024 | 155 (9,92 ms) | 156 (9,984 ms) | 1 unidad => 0,64% | 100,16..Hz / 100,80..Hz |
- desfase de valor con relación al máximo: [[ 100 x (máximo-mínimo)/máximo ]]
- frecuencias mínima-máxima: 1000ms/tiempo_mínimo, 1000ms/tiempo_máximo
Se presupone que el valor máximo corresponde a 1Hz – cosa que no es correcta del todo, pero se simplifica la fórmula. La completa sería: [[ (máximo-mínimo)/máximo * 10ms/(tiempo_maximo_ms) ]] - duración del ciclo: [[ 1/desfase_temporal ]]
Medida con el timer/counter2:
Para utilizar el timer/counter2 para medir la longitud de ciclo, la señal a medir ha de estar conectada al pin 2 del arduino, ya que el software está programado para que esté en dicho pin.
El funcionamiento es distinto. El timer/counter2 no dispone de la funcionalidad de captura, por lo que es necesario realizar el proceso por software. Se programa el servicio de interrupción de Pin Change, de modo que cada vez que se detecta un flanco positivo en el pin2, se envía por puerto serie el valor del timer/counter2 (no sólo el máximo/mínimo, sino que se envían todos). Se podría aumentar la resolución por software, pero por simplicidad y porque era la prueba preliminar de medida del ancho del ciclo, no se ha llevado a cabo.
¿En qué influye la frecuencia variable?
Lo primero de todo, resolución y fiabilidad de la lectura. La resolución de la señal PWM es del orden de 1 mg(mili-g), por lo que la señal PWM tiene hasta 8000mgs (desde -4gs hasta +4gs).
Resolución: ¿Es posible medir todo el rango de 8000mgs? 8000mgs*100Hz (frecuencia PWM) = 800.000 mgs por segundo. Sería necesario poder discriminar todos y cada uno de ellos. Esto implica disponer de 20 ciclos de reloj por mg (en el peor de los casos, leer una señal de 1mg). Se necesitaría un software bastante dedicado para poder leer un único mg, ya que la entrada y salida de la interrupción consumen 8 ciclos de reloj, y la programación en C añade irremediablemente un preámbulo al servicio de la interrupción. 12 ciclos se tendrían para poder leer 1 único mg. Para leer una señal de 7999mgs, el problema sería parecido, ya que 40 ciclos después se ejecutaría de nuevo la interrupción marcando el inicio del siguiente ciclo PWM. Sin embargo, para valores intermedios no debería presentar problema, ya que el sepaciado en el tiempo sería suficiente para que el número de ciclos disponibles para procesar fuese crítico.
Para leer 1mg se necesitaría utilizar un contadord e 16bits con modo captura y prescaler de 8, donde cada 1mg equivaldría a 2,5 unidades de contador (en teoría). Se podría utilizar el prescaler de 1 si se incrementa la cuenta máxima del timer mediante software.
En nuestro caso no necesitamos una resolución grande, ya que el objetivo no es “medir la inclinación”, sino detectar colisiones. Por motivos de haber utilizado al principio el timer/counter2 con prescaler de 1024, cualquier medición se mapea a un valor 0-155, por lo que nos serviría utilizar el prescaler 1024 (156 valores) o 256 (624 valores). Menos prescaler indica más consumo, así que los prescalers 64 y 8, en principio, quedarían descartados.
Fiabilidad de la lectura:
En un principio parece que el error de la duración de cada ciclo PWM (desde 0,08% hasta 0,64%) es insignificante. Notar el hecho de que ninguno de los ciclos dura los 10 milisegundos que deberían durar de media a 100Hz.
Teniendo en cuenta los dos datos anteriores, la primera conclusión que se extrae es que los datos son más fiables (de manera absoluta, no relativa) cuanto menor es el pulso PWM: Leer unos pocos mgs tiene un error acumulador que puede ser imperceptible; leer 2000 mgs con prescaler 8 tiene incorporado hasta 1,6 unidades de error (0,8% de 2000); leer 6000 mgs tiene un error de hasta 4,8mgs (0,8% de 6000).
La fiabilidad, de todos modos, se puede calcular a partir de los resultados de frecuencias mínimas y máximas calculadas para el caso de prescaler 8 y el efecto de ellas en el desfase incremental (comentado más arriba). Suponiendo que la frecuencia PWM oscila entre 100,29Hz y 100,37Hz (esto es mucho suponer porque podría aumentar o desplazarse el rango por temperatura u otras variables ocultas), podemos calcular el valor mínimo y máximo que darían los distintos prescalers. Así:
prescaler | ICR1 a 100,37 | ICR1 a 100,29Hz |
---|---|---|
8 | 19926,27.. | 19942,167.. |
64 | 2490,78.. | 2492,727.. |
256 | 622,69.. | 623,19.. |
1024 | 155,67.. | 155,798.. |
- ICR1 = 16Mhz/frecuenciaPWM x 1/prescaler
Como se puede observar, en todos los casos hay una fracción de contador que no se puede medir y que origina en todos los casos un desfase incremental en las medidas. Gracias a estos cálculos podremos estimar cuál es la mayor resolución con la que podremos leer en cada uno de los prescalers.
En el caso de utililzar el prescaler 1024, el incremento/decremento de la longitud PWM de un ciclo a otro, teniendo en cuenta que las medidas sufren el desfase incremental, hacen que una misma longitud pueda ser leída como 3 valores distintos dependiendo de si aumenta o disminuye la frecuencia en un ciclo dado y en función del desfase acumulado.
Solución al desfase incremental producido por la frecuencia variable
Para solucionar el desfase incremental lo que se hace es resetear el prescaler al inicio del ciclo PWM (flanco positivo). El ATmega168 comparte prescaler entre el Timer0 y el Timer1, y el Timer2 dispone de su propio prescaler.
Los resultados son los siguientes:
prescaler | rango de valores ICR1 | moda ICR1 | unidades de error |
---|---|---|---|
8 | 19982-19992 | 19988-19991 | 10 |
64 | 2496-2499 | 2498 | 4 |
256 | 624 | 624 | 0 |
1024 | 155-156 | 156 | 1 |
Como se puede apreciar, las lecturas han mejorado mucho. Algo a tener en cuenta es que a pesar de las fluctuaciones dentro de un rango, hay un valor moda. Variaciones como las apreciadas con prescaler 8 podrían ser debidas diversas variables como las nombradas más atrás (temperatura, …). Para prescalers altos (64,256,1024) la moda es un único valor.
Solución a la frecuencia variable
El problema de la frecuencia variable no se puede solucionar incrementando la resolución de lectura (y después dividiendo). Hacer esto puede ser beneficioso , dado que se disminuye la ventana de error y por lo tanto, al dividir, la lectura es más estable. Sin embargo, la ventana de error sige existiendo en las inmediaciones de una unidad de contador y la siguiente, sensible a la mínima variación de frecuencia.
La solución óptima sería medir la duración del pulso PWM y la del ciclo PWM. Tras obtener estos dos valores, calcular la duración del pulso con respecto al ciclo.
Solución técnica
Se ha mejorado el algoritmo para la lectura de pulsos PWM, pero dado que vamos a leer 2 señales PWM con el mismo timer(Timer2), no es posible evitar el desfase incremental de manera efectiva reseteando el prescaler, por lo que se utilizará la resolución de 624 unidades y su posterior redondeo para disminuir un poco la ventana de error.
Definir las interrupciones como no bloqueantes (tanto la de Compare Match A como la de Pin Change) pero dejando bloqueantes las secciones críticas de adquisición del timestamp:
Compare Match A:
- DEFINIRLA COMO BLOQUEANTE y poner al principio de la interrupción ATOMIC_BLOCK(ATOMIC_FORCEON) – Bloque atómico (interrupciones deshabilitadas) y tras finalizar el bloque se fuerza la habilitación de interrupciones.
- Actualizar la variable Overflow (que debería llamarse “variable_interna_de_veces_contadas_hasta_156 “) y de Output Compare Register 2A(para que cuente de nuevo hasta 156 – no debería ser necesario pero parece que este valor se borra automáticamente)
- Borrar a mano Output Compare Flag 2A (OCF2A) para que la interrupción de Pin Change no se equivoque si nada más terminar este bloque atómico salta una de ellas (ver más abajo)
- Desbloquear (permitir interrupciones)
Pin Change:
- DEFINIRLA COMO BLOQUEANTE y poner al principio de la interrupción ATOMIC_BLOCK(ATOMIC_FORCEON)
- Guardar un timestamp del momento de la interrupción
- Tener en cuenta que si el flag de COMPARE MATCH está activo y el TCNT2 es <=1 (damos una ventana de 512 ciclos) es que TCNT2 se ha borrado pero la interrupción del compare match no se ha ejecutado todavía, por lo que la variable de Overflow (“variable_interna_de_veces_contadas_hasta_156”) no se ha actualizado y hay que actualizar a mano el timestamp (incrementar el componente superior del par).
- Calcular los pines afectados
- Desbloquear (permitir interrupciones)
Muchos problemas se solucionarían con varios timers con capture mode, lo que no quita que pudieran ocasionar también problemas (oscilaciones en los límites entre unidades). Como el arduino sólo dispone de 1 timer con capture mode, un eje es obligatorio capturarlo por software, por lo que capturamos ambos ejes de la misma manera porque el código sirve tanto para uno, como para más ejes.
Post a Comment