ADVERTENCIA muchas fotos.
Una de las herramientas que me faltaban en mi "tallercito" era un generador de señales, buscando en internet que proyecto interesante había, me tope con este:
AVR DDS signal generator V2.0
Que a su vez se basó en este otro:
Mini DDS (Direct Digital Synthesis)
Ambos basados en la línea de uC AVR.
Resumiendo un poco, la idea es usar un puerto de un uC conectado a una red R-2R que funcione como DAC (en mi caso el puerto es de 8 pines => resultado DAC de 8 bits) y permitir generar una señal analógica en función de los datos almacenados en uC.
Evidentemente la velocidad del uC influirá y mucho en la frecuencia máxima a obtener e incluso el código tiene que ser muy a la medida para ahorrar el mayor tiempo posible en las rutinas de muestreo, por lo tanto por un lado es necesario trabajar con el uC al mayor clock posible y otro las rutinas de muestreo deberán realizarse si o si en Assembler para obtener el mejor desempeño posible.
Si se ponen a leer la última página que mencioné, ahí te dan el "truco" necesario para muestrear la señal almacenada en el uC mediante una rutina en assembler muy sencilla:
Código de Jesper:
Este será el corazón del generador. Si bien el código no parece decir demasiado, la traducción es la siguiente:
Como resultado, por c/loop es necesario 9 clk's. Entonces, sin tener en cuenta los acumuladores, la frecuencia máxima de muestreo daría Fosc/9.
Mediante el uso del acumulador (que funciona como un pre-escaler), podemos definir la resolución de muestreo, es decir con que frecuencia se muestrea al buffer de datos:
[LATEX]Resolucion=\frac{F_{osc}}{Acumulador-cuenta-total_{24bits}.9clk}=\frac{F_{osc}}{150994944}[/LATEX]
En base a la resolución, la frecuencia de salida estará dada por:
[LATEX]F_{out}=Acumulador_{cuenta-parcial}.Resolucion[/LATEX]
Si bien la solución resulta sencilla, les aseguro que a mi en un primer momento no se me ocurrió pensarlo de esa forma, lo cual insisto que esa parte del código resulta ser el corazón del proyecto.
Como opción para reducir los clk's del ciclo loop para obtener un fsampling mayor, se puede reducir el acumador a 16bits, lo que a la larga implica menor resolución.
En mi caso, el código varía un poco, ya que pretendía hacer un control para que el usuario pudiera cambiar el tipo y la frecuencia de la señal, esto traía como inconvenientes agregar una salto condicional y por ende agregar más clk's por c/loop (menor frecuencia de muestreo), sin embargo el gran problema de la rutina de Jesper era que trabajaba directamente sobre la memoria de código del uC, haciendo 1clk más lento su lectura respecto a la memoria RAM (supongo que estaba limitado en RAM). En consecuencia, saque de un lado y agregué del otro, quedando en 9clk's al igual que su rutina.
Mi código:
Lo más destacado:
A diferencia de Jesper, yo combino el lenguaje Assembler para generar la señal y C para el resto de las funciones que no requieren tanta optimización.
Termine usando un cristal de 16MHz en un Atmega16, de esta forma consigo una fsampling=1,77MHz, permitiendo idealmente una frecuencia máxima de salida de 888kHz. Como el filtro pasa bajos no es ideal, esa frecuencia máxima de salida la fijé en 500kHz usando un filtro pasa-bajos de 4to orden lineal (mas detalles ver este hilo).
Como resultado en base a las mediciones obtuve:
- PCB sin soldar (Top layer):
- PCB sin soldar (Bottom layer):
- PCB a contra luz (Top layer):
- PCB a contra luz (Bottom layer):
- Soldaduras (Top layer):
- Soldaduras 1 (Bottom layer - lado de los componentes):
- Soldaduras 2 (Bottom layer - lado de los componentes):
- Soldaduras 3 (Bottom layer - lado de los componentes):
De momento al proyecto le falta un gabinete y terminar con el filtro de línea (no consigo el inductor de modo común ).
Les dejo una fotos del generador funcionando:
En un rato para no hacer demasiado largo el mensaje, subo en otro las distintas mediciones que fuí haciendo del generador.
Luego voy a detallar bien el circuito, los inconvenientes que tengo de momento y por último subiré el proyecto completo, con los PCB, el código del uC y el programa de PC para hacer enviar una señal y controlar el generador mediante el puerto serie.
Una de las herramientas que me faltaban en mi "tallercito" era un generador de señales, buscando en internet que proyecto interesante había, me tope con este:
AVR DDS signal generator V2.0
Que a su vez se basó en este otro:
Mini DDS (Direct Digital Synthesis)
Ambos basados en la línea de uC AVR.
Resumiendo un poco, la idea es usar un puerto de un uC conectado a una red R-2R que funcione como DAC (en mi caso el puerto es de 8 pines => resultado DAC de 8 bits) y permitir generar una señal analógica en función de los datos almacenados en uC.
Evidentemente la velocidad del uC influirá y mucho en la frecuencia máxima a obtener e incluso el código tiene que ser muy a la medida para ahorrar el mayor tiempo posible en las rutinas de muestreo, por lo tanto por un lado es necesario trabajar con el uC al mayor clock posible y otro las rutinas de muestreo deberán realizarse si o si en Assembler para obtener el mejor desempeño posible.
Si se ponen a leer la última página que mencioné, ahí te dan el "truco" necesario para muestrear la señal almacenada en el uC mediante una rutina en assembler muy sencilla:
Código de Jesper:
PHP:
; main loop
;
; r28,r29,r30 is the phase accumulator
; r24,r25,r26 is the adder value determining frequency
;
; add value to accumulator
; load byte from current table in ROM
; output byte to port
; repeat
;
LOOP1:
add r28,r24 ; 1
adc r29,r25 ; 1
adc r30,r26 ; 1
lpm ; 3
out PORTB,r0 ; 1
rjmp LOOP1 ; 2 => 9 cycles
- El puntero "Z" está compuesto por el r31-r30, por lo tanto r30 será el byte inferior de dicho puntero, que se encargará de apuntar a 256 direcciones distintas.
- Mediante el uso de 2 registros más (r28 y r29) en conjunto con r30 se arma un acumulador de 24bits.
- En r24, r25 y r26 estará almacenado el paso del muestreo, por c/loop se r30 se irá incrementando mediante el paso, teniendo en cuenta el carry de los bytes inferiores,
- Mediante la instrucción "lpm" se lee la dirección donde se encuentran almacenados los datos y se lo guarda en r0. Para facilitar el muestreo es necesario que dicho "buffer" sea de 256 elementos, que justamente es la misma cantidad de direcciones que puede direccionar r30.
- Por último se almacena el valor de r0 en el puerto B (8 bits) que será el puerto conectado al DAC.
- Luego se repite el ciclo una y otra vez.
Como resultado, por c/loop es necesario 9 clk's. Entonces, sin tener en cuenta los acumuladores, la frecuencia máxima de muestreo daría Fosc/9.
Mediante el uso del acumulador (que funciona como un pre-escaler), podemos definir la resolución de muestreo, es decir con que frecuencia se muestrea al buffer de datos:
[LATEX]Resolucion=\frac{F_{osc}}{Acumulador-cuenta-total_{24bits}.9clk}=\frac{F_{osc}}{150994944}[/LATEX]
En base a la resolución, la frecuencia de salida estará dada por:
[LATEX]F_{out}=Acumulador_{cuenta-parcial}.Resolucion[/LATEX]
Si bien la solución resulta sencilla, les aseguro que a mi en un primer momento no se me ocurrió pensarlo de esa forma, lo cual insisto que esa parte del código resulta ser el corazón del proyecto.
Como opción para reducir los clk's del ciclo loop para obtener un fsampling mayor, se puede reducir el acumador a 16bits, lo que a la larga implica menor resolución.
En mi caso, el código varía un poco, ya que pretendía hacer un control para que el usuario pudiera cambiar el tipo y la frecuencia de la señal, esto traía como inconvenientes agregar una salto condicional y por ende agregar más clk's por c/loop (menor frecuencia de muestreo), sin embargo el gran problema de la rutina de Jesper era que trabajaba directamente sobre la memoria de código del uC, haciendo 1clk más lento su lectura respecto a la memoria RAM (supongo que estaba limitado en RAM). En consecuencia, saque de un lado y agregué del otro, quedando en 9clk's al igual que su rutina.
Mi código:
PHP:
.global generar_senial
#define DATA_REG r31
#define ACUMULADOR_INF r24
#define ACUMULADOR_SUP r25
#define X_INF r26
#define PASO_BYTE_1 r19
#define PASO_BYTE_2 r20
#define PASO_BYTE_3 r22
#define PORT_SALIDA PORTC
generar_senial:
movw X_INF,r24 ;En r27:r26 => PUNTERO "X" => Puntero de la senial
mov PASO_BYTE_1,r18 ;En r18 se encuentra el paso más bajo
clr DATA_REG ;Lo uso para almacenar el dato momentaneo almacenado en el vector de la senial
clr ACUMULADOR_INF ;Lo uso de acumulador del "Paso" byte inferior
clr ACUMULADOR_SUP ;Lo uso de acumulador del "Paso" byte superior
clr FLAG_INT_REG ;Limpio el flag de salida!
;Valor del "Paso" de muestreo => en funcion de "f" y los clk's
loop_senial:
add ACUMULADOR_INF,PASO_BYTE_1 ;Sumo el 1er byte del "Paso" => 1 Clk +
adc ACUMULADOR_SUP,PASO_BYTE_2 ;Sumo el 2do byte del "Paso" teniendo en cuenta el carry producido por la suma del 1er acumulador => 1 Clk +
adc X_INF,PASO_BYTE_3 ;Sumo el 3er byte del "Paso" teniendo en cuenta el carry producido por la suma del 2do acumulador => 1 Clk +
ld DATA_REG,X ;Obtengo el dato almacenado en RAM mediante el puntero "X" y lo llevo al registro 20 => 2 Clk +
out _SFR_IO_ADDR(PORT_SALIDA),DATA_REG ;Modifico el valor del PuertoC en función del dato leído previamente => 1 Clk +
cpi FLAG_INT_REG,1 ;Usando el r18 como flag de interrupcion externa 0, comparo dicho registro con "1" => 1 Clk +
brne loop_senial ;Si no es igual Z=0 (Flag Zero) => salta => 2 Clk
; ------------
ret
- Trabajo con el puntero X (r27:r26) sobre una posición de memoria RAM.
- Comparo un flag que será el registro r18, el cual se modificará en dos rutina de interrupción distintas, la externa y la del puerto serie.
- El resto se mantiene igual, salvo la inicialización que hago de los registros.
A diferencia de Jesper, yo combino el lenguaje Assembler para generar la señal y C para el resto de las funciones que no requieren tanta optimización.
Termine usando un cristal de 16MHz en un Atmega16, de esta forma consigo una fsampling=1,77MHz, permitiendo idealmente una frecuencia máxima de salida de 888kHz. Como el filtro pasa bajos no es ideal, esa frecuencia máxima de salida la fijé en 500kHz usando un filtro pasa-bajos de 4to orden lineal (mas detalles ver este hilo).
Como resultado en base a las mediciones obtuve:
- Señales:
- Senoidal.
- Triangular.
- Diente de sierra (pendiente "+" y "-")
- PWM.
- TTL
- Resolución de un 1Hz.
- Tonos puros hasta 400kHz (senoidal) [a discutir]. Hasta 20kHz, el THD resultaba despreciable, "medición" realizada con el FFT del OCR (hay que tomarlo con pinzas).
- Señales poliarmónicas limitadas a 100kHz (en 500kHz empieza actuar el filtro).
- En señales PWM, se obtiene una resolución del 1% en el duty.
- Señales TTL manejadas digitalmente, de 10kHz hasta 1,6MHz.
- 4 Vp de salida [a discutir].
- La posibilidad de almacenar en la EEPROM, dos señales que el usuario necesite, mediante el uso del puerto serie [característica más que interesante para aumentar la funcionalidad del generador].
- Al igual que la opción anterior, enviar una señal en forma temporal (solo almacenada en RAM), mediante el puerto serie.
- Controlar el generador mediante el puerto serie, esto abre la posibilidad de hacer barridos en frecuencia y en duty [otra característica interesante para aumentar la funcionalidad del generador].
- Control del puerto serie desde PC mediante un programa hecho en Java.
- PCB sin soldar (Top layer):
- PCB sin soldar (Bottom layer):
- PCB a contra luz (Top layer):
- PCB a contra luz (Bottom layer):
- Soldaduras (Top layer):
- Soldaduras 1 (Bottom layer - lado de los componentes):
- Soldaduras 2 (Bottom layer - lado de los componentes):
- Soldaduras 3 (Bottom layer - lado de los componentes):
De momento al proyecto le falta un gabinete y terminar con el filtro de línea (no consigo el inductor de modo común ).
Les dejo una fotos del generador funcionando:
En un rato para no hacer demasiado largo el mensaje, subo en otro las distintas mediciones que fuí haciendo del generador.
Luego voy a detallar bien el circuito, los inconvenientes que tengo de momento y por último subiré el proyecto completo, con los PCB, el código del uC y el programa de PC para hacer enviar una señal y controlar el generador mediante el puerto serie.