Supongamos que tenemos un evento que se dispara a una frecuencia dada [$f_0$] (por ejemplo, una frecuencia de muestreo). Llamaremos eventos básicos a estos eventos. Es fácil comprobar que puedo generar otros eventos derivados a partir de estos eventos básicos. Bastará tomar una frecuencia divisora [$f=f_0/k$] y lanzar un evento derivado cada [$k$] eventos básicos. Por ejemplo, si [$k=3$] tengo un evento derivado cada tres eventos básicos.
Si trazamos el número de evento básico en el eje de abscisas y el número de evento derivado en el eje de ordenadas, obtenemos una línea. En este caso, de pendiente [$1/3=f/f_0$].
¿Pero qué ocurriría si la frecuencia es una fracción cualquiera de [$f_0$] y no exactamente un divisor?
El algoritmo
En este caso a veces tendremos que contar [$k$] eventos básicos y otras veces [$k+1$]. No obtenemos una frecuencia exacta, pero en promedio sí será la [$f$] deseada. Pagamos un jitter por tener una frecuencia que no es divisora de la frecuencia básica.
Lo interesante aquí es que seguimos obteniendo una recta, aunque ya no está centrada en el origen y tampoco tiene una pendiente [$1/k$]. Si llamo [$0\le r<1$] a la altura de intersección con el eje de ordenadas y [$t$] al número de evento básico (en definitiva, es el tiempo transcurrido) mi recta es
[$$y=r+\frac{f}{f_0}t$$]
El primer evento derivado se tendrá que lanzar cuando la recta corte a la línea [$y=1$]. Es decir
[$$1=r+\frac{f}{f_0}t$$]
Debido a que el resultado puede no ser un número entero, hemos de redondear al siguiente evento básico que llamaremos [$t_1$].
[$$t_1=\left \lceil \frac{1-r}{f/f_0} \right \rceil$$]
Ya sabemos cuándo lanzar el siguiente evento derivado: será en el evento básico [$t_1$]. Lo interesante aquí es que puedo trasladar de nuevo todo al origen de coordenadas y calcular el valor [$r$] para el siguiente evento derivado empezando desde cero. Usando exactamente las mismas ecuaciones.
Este nuevo valor de [$r$], que llamaré [$r_1$], será el valor de [$r$] en [$t_1$] pero restando [$1$] para volver al origen de coordenadas.
[$$r_1=r+\frac{f}{f_0}t_1-1$$]
Con lo que el algoritmo sería
- Iniciamos con [$r=0$] y el valor [$f=f_0$] de frecuencia que deseemos.
- Calculamos [$t_1$] y [$r_1$] a partir del valor de [$r, f$] y [$f_0$].
- Esperamos [$t_1$] eventos básicos antes de lanzar un evento derivado.
- Hacemos [$r \leftarrow r_1$] y vamos al paso 2
La implementación
Este algoritmo está muy bien, pero hay un detalle de implementación. Tanto el valor [$f/f_0$] como el valor [$r$] están en el intervalo [$[0,1)$]. No es descabellado usar una notación de coma fija para representarlo en [$n$] bits. Entonces, usando el subíndice [$p$] para la coma fija,
[$$f_p=2^n \frac{f}{f_0} $$]
[$$r_p=2^n r$$]
Y las ecuaciones quedan
[$$t_1=\left \lceil \frac{2^n-r_p}{f_p} \right \rceil $$]
[$$r_1=r_p+f_p t_1-2^n$$]
La ecuación de [$t_1$] tiene un problema: el redondeo por exceso. Generalmente las divisiones enteras tienen redondeo por defecto. Es fácil hacer la conversión sumando el denominador menos uno.
[$$t_1=\left \lceil \frac{2^n-r_p}{f_p} \right \rceil=\left \lfloor \frac{2^n-r_p+f_p-1}{f_p} \right \rfloor $$]
Y un paso más es darnos cuenta que [$2^n-1-r_p$] no es más que el complemento a uno de [$r_p$] que notaremos [$ \sim r_p $].
[$$t_1=\left \lfloor \frac{\sim r_p+f_p}{f_p} \right \rfloor $$]
La ecuación de [$r_1$] puede simplificarse también ya que sumar o restar [$2^n$] no tiene efecto en aritmética módulo [$2^n$] por lo que llegamos a
[$$r_1=r_p+f_p t_1$$]
El algoritmo sigue siendo el mismo.
0 comentarios:
Publicar un comentario