[Aporte] Generador de Señales (DDS)

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:

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
Este será el corazón del generador. Si bien el código no parece decir demasiado, la traducción es la siguiente:

  • 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
Lo más destacado:

  • 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.
Fotos del armado del proyecto:
- PCB sin soldar (Top layer):

P1050207.jpg

- PCB sin soldar (Bottom layer):

P1050204.jpg

- PCB a contra luz (Top layer):

P1050197.jpg

- PCB a contra luz (Bottom layer):

P1050200.jpg

- Soldaduras (Top layer):

P1050238.jpg

- Soldaduras 1 (Bottom layer - lado de los componentes):

P1050224.jpg

- Soldaduras 2 (Bottom layer - lado de los componentes):

P1050225.jpg

- Soldaduras 3 (Bottom layer - lado de los componentes):

P1050226.jpg

De momento al proyecto le falta un gabinete y terminar con el filtro de línea (no consigo el inductor de modo común :cry:).

Les dejo una fotos del generador funcionando:

P1050243.jpg

P1050244.jpg

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.
 
Tiene varios puntos que fluctúan entre "Muy Buenos" y "Excelentes", me gustó la posibilidad de almacenar una forma de onda es muy interesante.
El rango de frecuencias y señales cubre una enorme cantidad de posibilidades de uso.
Estamos hablando de prestaciones similares a las de un instrumento de unos 500U$, y tal vez más.

(Parte Cholula) El armado me "Guta", te quedó "Remonono"

Gracias por compartirlo
 
Tiene varios puntos que fluctúan entre "Muy Buenos" y "Excelentes", me gustó la posibilidad de almacenar una forma de onda es muy interesante.
El rango de frecuencias y señales cubre una enorme cantidad de posibilidades de uso.
Estamos hablando de prestaciones similares a las de un instrumento de unos 500U$, y tal vez más.

(Parte Cholula) El armado me "Guta", te quedó "Remonono"

Gracias por compartirlo

Gracias Fogo.

Si, la posibilidad de almacenar la forma de onda creo que es el "agregado" más destacado.
Dejo las mediciones que mencioné:
- Senoidal 1kHz:

sen1k.jpeg

sen1kfft.jpeg

- Senoidal 10kHz:

sen10k.jpeg

- Senoidal 20kHz:

sen20k.jpeg

- Senoidal 400kHz:

sen400k.jpeg

- Triangular 10kHz:

triang10k.jpeg

- Triangular 100kHz:

tring100k.jpeg

En azul la salida del DAC, en amarillo la salida luego del filtro.

- Diente de sierra 10kHz:

sierra10.jpeg

En azul la salida del DAC, en amarillo la salida luego del filtro.

- Diente de sierra 100kHz:

sier100k.jpeg

En azul la salida del DAC, en amarillo la salida luego del filtro. El filtro hace que la pendiente descendente sea más lenta y tenga un gran pico.

Las imágenes de las señales PWM son del soft anterior, luego de la modificación utilizando la señal TTL para generarlas, por tal motivos las imágenes viejas que tengo carecen de sentido.

A diferencia de las señales analógicas, las TTL se toman directamente desde un pin y su resolución es de un acumulador de 8bits (poca resolución). La resolución en el modo TTL fue mejorado con la actualización del soft, permitiendo una resolución de 24 bits en bajas frecuencias.

- TTL de casi 10kHz:

ttl10k.jpeg

- TTL 1MHz:

ttl1mhz.jpeg

- TTL 1,6MHz:

ttl1_6mhz.jpeg

Ahora los problemas:

- La idea original era llegar a 500kHz, poniendo la frecuencia de corte del filtro cercana a esa frecuencia. Sin embargo a la hora de generar los 500kHz aparece el siguiente inconveniente:

sen500k.jpeg

No sé porque, .....

Sin embargo ese armónico de 277kHz ...del amplificador no inversor):

se500k_b.jpeg
 
¿ Y agregar un filtro escalonado según la frecuencia ? :unsure:

O sea, ¿ir tirando la frecuencia de corte más arriba según la señal?

A continuación dejo el esquemático del circuito con su explicación según la etapa:

- Rectificación y filtrado:

Ver el archivo adjunto 100502

Uso un transformador 6v+6v de 300mA, que luego de de rectificar obtengo casi +/-9v (un poco más de 8,5v).

El capacitor principal de filtrado es de 1000uF, que para una corriente estimada de 50mA (uC + LCD) provocará un ripple de 500mV.

En las mediciones el ripple obtenido fue de 280mV ramal positivo:

Ver el archivo adjunto 100503

Y 320mV entre ramal positivo y ramal negativo (lo que le llega de alimentación al operacional):

Ver el archivo adjunto 100504

C1 y C2 bis están para reforzar el filtrado de alta frecuencia y C1 analógico fue colocado ni bien comienza el plano de masa analógico.

Como mejora, se podría agregar capacitores en palalelo a c/diodo del puento para eliminar el ruido RF que se mete por la linea de alimentación y pueda mezclarse con los diodos.

- Alimentación digital y filtrado según el fabricante del uC:

Alimentación_digital.jpg

Se alimenta con un 7805 con los capacitores que recomienda la hoja de datos. Mientras que el uC se alimenta a través de filtros L-C que recomienda el fabricante.

- El uC Atmega16:

uC.jpeg

  • Conector P2 para programar el uC.
  • Pull-up de 4k7Ohms para el pin del reset.
  • Conexiones para un display LCD 2x16.
  • Conexiones para el uso de la USART.
  • Conexiones para los dos pulsadores del usuario conectados a las interrupciones externas.
  • Conexiones para el manejo de dos leds que indicarán que salida está funcionando, si la analógica o la TTL.
  • Salida TTL a un conector BNC para PCB.
  • Conexión a un potenciómetro de cursor para el usuario.
  • Los 8 bits que irán conectados a la red R-2R para generar la señal analógica.
  • Conexiones de alimentación.
  • C10, filtro recomendado por el fabricante para el ADC, solo utilizado para el cursor.

- Cristal y pulsador del reset:

cristal y pulsador.jpeg

Para el oscilador usé un cristal de 16MHz, yo conseguí capacitores de 22pF (el fabricante recomienda 15pF hasta 22pF).

- La conexión del LCD (usado en modo 4bits):

LCD.jpeg

  • C11, capacitor de desacople.
  • Backlight conectado mediante una resistencia de 47Ohms para obtener casi 17mA de corriente de polarización. No está controlado, para este proyecto no ví la necesidad de agregar un transistor para controlar su habilitación.
  • R21 1kOhms y R20 no fue colocado, en esta configuración se consigue un excelente brillo.

- Pulsadores para el usuario:

pulsadores.jpeg

  • Pulsador /INT1 directo a masa, con una rutina de antirrebote por soft alcanza.
  • Pulsador /INT0, requiere de un antirrebote por hard, ya que será el pulsador encargado de sacar al uC de la rutina en assembler que mencioné en el 1er mensaje.

El filtro del antirebote /INT0 lo pensé para un Tao de 100mS, bastante elevado para garantizar que esa interrupción externa solo salte cuando el usuario presiona el pulsador. Según la hoja de datos del uC, la entrada será tomada como nivel bajo cuando la tensión en el pin sea menor a 0,2.Vcc=1v (para 5v) y la resistencia de pull-up será de 50kOhms.

Como resultado, al presionar el pulsador, luego del transistorio en el pin debería haber 5v*10k/(10k+50k)=833mV, bastante al límite de ser tomado como un nivel lógico bajo.

En la práctica no tuve problema con eso, pero tal vez sea recomendable reducir el valor de R32 a 4k7 por ejemplo, dando un Tao de 47mS.

- Potenciómetro (cursor) y leds:

Potenciometro y leds.jpeg

Los leds están polarizados con un poco menos de 10mA.

- Conversor RS232 a TTL (Max232):

max232.jpeg

Circuito tal como recomienda su hoja de datos.

- Red R-2R (DAC):

Red R2r.jpeg

Para más detalles buscar en el foro que hay un buen tutorial sobre este tema. En mi caso terminé usando resistencias con tolerancias del 1%, de 1kOhms y 2kOhms. Valores muy chicos cargarían el puerto, valores muy grandes harían que el rise-time de lo pines sea demasiado lento.

- Seguidor y pasa altos de 1er orden:



  • Filtros de 10uF cerca de la alimentación amplificador.
  • El seguidor sirve para no cargar la red R-2R.
  • El pasa altos con fc cercana en 16Hz, sirve para eliminar la continua (el offset).
  • Amplificador no inversor con ganancia de 2,5 veces => se consigue casi 6,25 Vp.
  • El control de amplitud se hace con un simple potenciómetro usado como atenuador.

- Filtro pasa bajos de 4to orden lineal 0,5º:



  • Opté por un filtro que sea lineal para no generar tanta distorsión, pero en vez de ir por un filtro Bessel (el tipo de filtros más lineal), preferí uno que sea de +/-0,5º con mayor Q.
  • Frecuencia de corte cercana a 530kHz, que varía según el operacional. Por ej. un TL071 tiene un ancho de banda menor a un TL081, por ende, este último conseguirá un polo dominante a mayor frecuencia que el primero.
  • El control de offset se hace mediante otro potenciómetro conectado a la entrada "+" del último filtro para agregar un valor de continua. Mediante una llave se habilita o no ese offset, en caso de deshabilitarlo, la entrada "+" queda con un pull-down a GND.

    Eso sería el circuito completo del proyecto, después subo un par de videos de como funciona el generador.
 
Última edición:
Al generador le hice un par de modificaciones:

  • Hard:
    • Agregué una resistencia de 680 Ohms en serie con el pote (del lado de la señal, para atenuar más) de amplitud para no llegar a la saturación. De esta forma la amplitud máxima queda cerca de 4,8Vp.
    • Agregué una resistencia de 1 kOhms en serie con el pote del offset, cuando llegaba a -V, el offset se daba vuelta y se hacía positivo :confused: .
  • Soft:
    • Modifiqué la resolución en las frecuencias altas, reemplazando el acumulador de 24bits, por uno de 8 bits, esto me permite muestrear a 2,28MHz. Como resultado, mejoro la salida en 500kHz, pero sigo teniendo una armónica molesta a muy baja frecuencia.
    • Descarté realizar el PWM con la salida analógica, en su lugar lo reemplacé por una señal cuadrada. No tenía sentido modificar el duty sin la componente de continua.
    • Agregué una señal TTL-PWM, de esta forma variar el duty con la componente de continua toma mayor sentido. Por otro lado, esta señal la limito a 100kHz, debido a que depende de un preescaler y la resolución de duty en alta frecuencia no es taaan bueno (ya van a ver).

Por otro lado hice un barrido en frecuencia (por octavas) a máxima amplitud, midiendo solo la salida (es decir, no hice Vout/Vin, ya que no me interesaba saber la transferencia del filtro) obtuve la siguiente curva en dB:

Rta en frecuencia.jpg

  • Cerca de 15Hz se encuentra la frecuencia de corte inferior.
  • A partir de 200Hz la respuesta es de 0dB.
  • En 100kHz se empieza a notar un aumento en la señal.
  • En 200kHz se alcanza ese pico de 0,3dB (muy poco).
  • Cerca de 475kHz se alcanza los -3dB, frecuencia de corte superior.

Acá una previa de como está quedando el generador con el gabinete:

P1050248.JPG

Elementos del gabinete:

  • Pulsador Rojo: sirve para desplazarse en el menú y salir de la señal actual que se esté generando.
  • Pulsador Negro: funciona como "Ok".
  • Potenciómetro inferior izquierdo: amplitud.
  • Potenciómetro inferior derecho: offset.
  • Potenciómetro superior: cursor para el menú.
  • LLave: para habilitar el offset.
  • LEDs: indican que salida tiene señal.
  • BNC izquierdo: salida TTL.
  • BNC derecho: salida analógica.

Videos:

- Señales analógicas


- Señales TTL


- Enviar señal a EEPROM


- Control vía puerto serie usando señales analógicas - Barrido en frecuencia


- Control vía puerto serie usando señales TTL - Barrido en duty


Mañana subo el proyecto completo.
 
Última edición:
Subo los siguientes archivos:

  • Generador de Señal - Proyecto Altium: se encuentra el esquemático y el PCB del generador.
  • Filtro de línea - Proyecto Altium: se encuentra el esquemático y el PCB del filtro de línea.
  • Firmware: todos los archivos fuente del software para el uC (tanto C como Assembler), junto con el ejecutable ".hex".
  • Generador de Señal - Java: el ejecutable Jar para controlar el generador desde la Pc. También incluye la librería RxTx para 32/64 bits que deberán ser instalados en el directorio Java Jre de la Pc.
  • Señales: incluyo un par de señales y una función para Matlab que sirve para pasar un vector a un archivo ".txt".

Cualquier duda me avisan.
 

Adjuntos

  • Generador de Señal - Proyecto Altium.zip
    1.8 MB · Visitas: 120
  • Filtro de línea - Proyecto Altium.zip
    556.7 KB · Visitas: 56
  • Firmware.zip
    148.7 KB · Visitas: 89
  • Generador de Señal - Java.zip
    2.4 MB · Visitas: 69
  • Señales.zip
    5.3 KB · Visitas: 53
Hola, buenos dias!
Hace un par de semanas vi este estupendo post, que por cierto, gracias a cosmefulanito04 por compartirlo, me decidi a hacer uno parecido, ya que estoy armando un amplificador y me vendria genial tener un generador de ondas para probarlo.
Pues bien, me propuse diseñarlo, y he conseguido hacer una especie de menú. Tengo que resaltar que estoy trabajando con PIC, no con AVR, asi que cambian todas las instrucciones. También estoy haciendo el programa entero en ensamblador, ya que hace poco aprendi a programar en este código.
Bien, como dijo el compañero cosmefulanito, la información fue sacada de otra página, que también consulté. Estuve viendo el código máquina, y por lo visto existe una tabla de valores preconfigurados que permiten la salida de la señal, ya sea senoidal, cuadrada, etc. Sin embargo, no consigo entender cómo actúa el acumulador de fase y cómo se determina la frecuencia :cry: Según he supuesto, los 3 registros que suman 24 bits determinan qué valor de la tabla debe salir por el puerto, de forma que a la siguiente vuelta del bucle cogen al siguiente, pero no consigo saber cómo. :S Les adjunto mi programa y mi diseño en proteus (lo que llevo hasta ahora):
Si alguien pudiera explicarme exactamente cómo funciona se lo agradecería :)

PHP:
;Configuración de los puertos
			bcf		03h,6		;Seleccionamos Banco 1
			bsf		03h,5		;Seleccionamos Banco 1
			movlw	00h			;Colocamos valor '0' en el registro W
			movwf	85h			;Configuramos Puerto A como salida
			movwf	86h			;Configuramos Puerto B como salida
			movlw	b'11110000'
			movwf	87h
			bcf		83h,5		;Seleccionamos Banco 0
					
;Configurar LCD
			clrf	05h				;Limpiamos Puerto A
			clrf	06h				;Limpiamos Puerto B
			clrf	07h
			call 	Retardo1s		;Llamamos a Retardo para iniciar correctamente el LCD
			movlw	b'00001100'		;Pantalla encendida, cursor apagado, intermitencia apagada
			call	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD
			movlw	b'00000001'		;Borramos pantalla y colocamos cursor al inicio
			call 	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD
			movlw	b'00111000'		;Bus 8 bits, activación de dos líneas de texto, matriz de caracteres de 5x7
			call	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD
			movlw	b'10000000'		;Colocación al inicio de la primera línea
			call	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD
			movlw	b'00000110'		;Incrementa posición del cursor, no desplaza texto
			call	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD

;Mensaje de inicio del LCD			
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'G'				;Colocamos caracter 'G' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'e'				;Colocamos caracter 'e' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'e'				;Colocamos caracter 'e' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'd'				;Colocamos caracter 'd' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'D'				;Colocamos caracter 'D' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'D'				;Colocamos caracter 'D' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'S'				;Colocamos caracter 'S' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	b'11000000'		;Colocación al inicio de la segunda línea
			call	Instruccion		;Llamamos a Instruccion para enviar una instrucción al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'I'				;Colocamos caracter 'I' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'c'				;Colocamos caracter 'c' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'd'				;Colocamos caracter 'd' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'.'				;Colocamos caracter '.' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'.'				;Colocamos caracter '.' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'.'				;Colocamos caracter '.' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			call	Retardo1s
			call	Retardo1s
			
Senoidal	movlw	0xC0
			call	Instruccion
			movlw	'F'				;Colocamos caracter 'F' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'c'				;Colocamos caracter 'c' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'S'				;Colocamos caracter 'S' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'e'				;Colocamos caracter 'e' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'd'				;Colocamos caracter 'd' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'l'				;Colocamos caracter 'l' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
SSenoidal	movlw	09h
			movwf	07h
			btfsc	07h,4
			goto	ARS1
			btfsc	07h,5
			goto	ARS2
			btfsc	07h,7
			goto	ARS3
			goto 	SSenoidal

ARS1		btfss	07h,4
			goto	Sierra
			goto	ARS1
ARS2		btfss	07h,5
			goto	Cuadrada
			goto	ARS2
ARS3		btfss	07h,7
			goto 	Senoidal
			goto	ARS3
			return	
		
Cuadrada	movlw	0xC0	
			call	Instruccion
			movlw	'F'				;Colocamos caracter 'F' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'c'				;Colocamos caracter 'c' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'C'				;Colocamos caracter 'C' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'd'				;Colocamos caracter 'd' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'd'				;Colocamos caracter 'd' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
SCuadrada	movlw	09h
			movwf	07h
			btfsc	07h,4
			goto	ARC1
			btfsc	07h,5
			goto	ARC2
			btfsc	07h,7
			goto	ARC3
			goto 	SCuadrada

ARC1		btfss	07h,4
			goto	Senoidal
			goto	ARC1
ARC2		btfss	07h,5
			goto	Triangular
			goto	ARC2
ARC3		btfss	07h,7
			goto 	Cuadrada
			goto	ARC3
			return	
			
Triangular	movlw	0xC0
			call	Instruccion
			movlw	'F'				;Colocamos caracter 'F' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'c'				;Colocamos caracter 'c' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'.'				;Colocamos caracter '.' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'T'				;Colocamos caracter 'T' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'g'				;Colocamos caracter 'g' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'l'				;Colocamos caracter 'l' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
STriangular	movlw	09h
			movwf	07h
			btfsc	07h,4
			goto	ART1
			btfsc	07h,5
			goto	ART2
			btfsc	07h,7
			goto	ART3
			goto 	STriangular

ART1		btfss	07h,4
			goto	Cuadrada
			goto	ART1
ART2		btfss	07h,5
			goto	Sierra
			goto	ART2
ART3		btfss	07h,7
			goto 	Triangular
			goto	ART3
			return	
			
Sierra		movlw	0xC0
			call	Instruccion
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'F'				;Colocamos caracter 'F' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'u'				;Colocamos caracter 'u' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'c'				;Colocamos caracter 'c' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'o'				;Colocamos caracter 'o' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'n'				;Colocamos caracter 'n' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'S'				;Colocamos caracter 'S' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'i'				;Colocamos caracter 'i' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'e'				;Colocamos caracter 'e' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'r'				;Colocamos caracter 'r' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	'a'				;Colocamos caracter 'a' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
			movlw	' '				;Colocamos caracter ' ' en registro W
			call	Caracter		;Llamamos a Caracter para enviar un caracter al LCD
SSierra		movlw	09h
			movwf	07h
			btfsc	07h,4
			goto	ARSAW1
			btfsc	07h,5
			goto	ARSAW2
			btfsc	07h,7
			goto	ARSAW3
			goto 	SSierra

ARSAW1		btfss	07h,4
			goto	Triangular
			goto	ARSAW1
ARSAW2		btfss	07h,5
			goto	Senoidal
			goto	ARSAW2
ARSAW3		btfss	07h,7
			goto 	Sierra
			goto 	ARSAW3
			return	
			
;Introducir caracter
Caracter	movwf	06h
			movlw	03h
			movwf	05h
			call	RetardoLCD
			movlw	01h
			movwf	05h
			call	RetardoLCD
			return

;Introducir instrucción
Instruccion	movwf	06h
			movlw	02h
			movwf	05h
			call 	RetardoLCD
			movlw	00h
			movwf	05h
			call	RetardoLCD
			return
		
;Retardo para LCD de 1ms
RetardoLCD	movlw	87h
			movwf	20h
DelayLCD1	movlw	0Bh
			movwf	21h
DelayLCD2	decfsz	21h,1
			goto	DelayLCD2
			decfsz	20h,1
			goto	DelayLCD1
			return
			
;Retardo general 1s
Retardo1s	movlw	0xA5
			movwf	20h
Delay1		movlw	0x29
			movwf	21h
Delay2		movlw	0xF5
			movwf	22h
Delay3		decfsz	22h,1
			goto	Delay3
			decfsz	21h,1
			goto	Delay2
			decfsz	20h
			goto	Delay1
			return		
			
			end
 

Adjuntos

  • Generador DDS.bmp
    79.7 KB · Visitas: 39
El acumulador de 24 bits sirve para fijar el paso de muestreo que se le dará a la señal y funciona como un preescaler variable.

¿Por qué es de 24bits?

Debido a que mientras mayor sea el tamaño del acumulador, mayor resolución en el paso de muestreo que vas a tener, esto muy importante para poder fijar el "deltaF" mínimo.

Entonces de esos 24 bits, solo te quedás con los últimos 8 bits más significativos y los usás como índice para el vector de la señal. El truco está en que ese vector de datos que tendrá la señal almacenada sea de 256 elementos (muestras) para que de esta forma con 8 bits puedas fácilmente esos elementos y cuando haya desborde en el índice (es decir el acumulador de 24bits desborde), vuelva al primer elemento (o muestra) del vector de datos. Fijate que de esta forma armas una cola circular de datos que siempre se repite al paso de la frecuencia que vos elegistes.

Para que lo termines de entender, volviendo al código que subí, al uC le toma 9clks en completar un bucle para actualizar el valor de la señal (eso mismo vas a tener que analizar vos con el PIC y la rutina en assembler que hagas) y como base de tiempo usé un cristal de 16MHz, entonces mi resolución queda definida por:

[LATEX]Resolucion=\frac{F_{osc}}{Tamanio_acumulador.9}= \frac{16MHz}{2^{24}.9}=0,105.... Hz[/LATEX]

Es decir que soy capaz de imponerle al generador una frecuencia de por ej. 99,92 Hz aproximadamente.

Si en cambio el acumulador solo fuera de 16bits, la resolución pasa a ser de:

[LATEX]Resolucion=\frac{F_{osc}}{Tamanio-acumulador.9}= \frac{16MHz}{2^{16}.9}=27,12 Hz[/LATEX]

Esto quiere decir que no tendría forma de fijar 100Hz clavados y como mucho me acercaría a los 108Hz.

Volviendo al acumulador de 24bits, si deseara obtener 100Hz, dicho acumulador debería valer:

[LATEX]Cuenta acumulador=\frac{F_{salida}}{Resolucion}= \frac{100Hz}{0,105.... Hz}=943[/LATEX]

Esto significa que c/vez que el bucle complete una vuelta, al acumulador se le sumarán 943 cuentas y siempre la salida del generador estará dado por el vector de datos que índica los últimos 8 bits más significativos de ese acumulador.

Por último recién vas a notar un cambio en el índice después de completar 70 vueltas al bucle (momento en que se le suma uno a los 8 bits más significativo del acumulador, 66010).
 
Última edición:
Cosme, muchas gracias por tu aporte. Yo estaba haciendo algo similar, solo que con una memoria ram para almacenar las formas de onda (ram por la velocidad, sino usaria una eprom), en cuya salida tengo la escalera R2R y un PLD que hace de contador y prescaler a partir de un cristal de 32mhz.

Todo ese lio para poder obtener la mayor frecuencia de salida posible y asi no depender de la velocidad del micro, que incluso a 48mhz se me hacia muy lenta por la cantidad de instrucciones (no recuerdo pero creo que lograba una frecuencia maxima de 200khz o algo asi, encima con pocos samples, 8) al final, con este diseño habia logrado frecuencias de algunos mhz, solamente que no graduales sino con saltos, estilo Fosc / 1, Fosc / 2, Fosc / 4 etc.

Sin embargo, voy a estudiar tu desarrollo a ver si se puede implementar lo mismo en un PIC y que limitaciones me encuentro.

Para el filtro de salida variable, en mi diseño intercambiaba 8 distintos capacitores con un 74HC4051 y selecciono cual de ellos dependiendo de la frecuencia que estoy generando. En simulacion anda muy bien, aclaro que no lo probe en la practica pero puede ser una idea que te sirva.
 
Última edición:
Cosme, muchas gracias por tu aporte. Yo estaba haciendo algo similar, solo que con una memoria ram para almacenar las formas de onda (ram por la velocidad, sino usaria una eprom), en cuya salida tengo la escalera R2R y un PLD que hace de contador y prescaler a partir de un cristal de 32mhz.

Claro yo también decidí usar la RAM del uC, en forma permanente se encuentran en la flash/eeprom, pero una vez que se elige la señal a generar se pasa dicha señal a la RAM y se trabaja directamente desde ella, de esta forma ahorrás 1clk en los AVR.

Todo ese lio para poder obtener la mayor frecuencia de salida posible y asi no depender de la velocidad del micro, que incluso a 48mhz se me hacia muy lenta por la cantidad de instrucciones (no recuerdo pero creo que lograba una frecuencia maxima de 200khz o algo asi, encima con pocos samples, 8) al final, con este diseño habia logrado frecuencias de algunos mhz, solamente que no graduales sino con saltos, estilo Fosc / 1, Fosc / 2, Fosc / 4 etc.

Te recomiendo que leas la rutina de assembler que publiqué o ponete con Xapas para desarrollar esa parte que utiliza el acumulador de 24 bits y generar una resolución bastante alta.

La rutina es muuy simple y universal, es decir la podés emplear con cualquier uC.

Sobre la velocidad del uC, es justamente la base de tiempo que tenés que tomar y resulta vital, por eso en la generación el código debe estar hecho en assembler, el resto por ej. podés hacerlo en C, como hice yo.

Para darte una idea, con 48MHz y una rutina en assembler similar a la mía que requiere de 9clks, implica una fsampling máxima de 5,3MHz. Si armás bien el filtro, hasta 2MHz podrías llegar.

Para el filtro de salida variable, en mi diseño intercambiaba 8 distintos capacitores con un 74HC4051 y selecciono cual de ellos dependiendo de la frecuencia que estoy generando. En simulacion anda muy bien, aclaro que no lo probe en la practica pero puede ser una idea que te sirva.ccc

Ojo que te puede meter mucha distorsión a la salida. A lo sumo creo que es preferible hacerlo con una llave selectora con manejo manual.
 
cosme,

Me referia a una ram externa, para poder tener cada sample en la salida a una F maxima de 32mhz (dividido la cantidad de samples, que creo que use 8 ahora no recuerdo)

Algo asi:
siggen.png

En la captura no se ven los codigos de componentes pero, de izq a derecha son:
PIC16F873, GAL22v10, y una RAM. Lo de abajo al medio es el 4051

De todos modos, mi anterior comentario era para darte la idea del 4051 pero si, puede que tenga distorsion y sea mas factible un selector manual.

Voy a estudiar tus rutinas porque evidentemente tu sistema es mas simple y efectivo.
 
Aca se ve una diferencia de potencia interesante entre avr y pic, ya que al pic le toma 4 ciclos de reloj cada instruccion, en cambio al avr cada ciclo de reloj = 1 instruccion.

Este es el motivo principal por el cual deje de hacer la sintesis por medio del pic, ya que toma demasiados ciclos de reloj hacer un acumulador, buscar en la tabla de lookup y transferir al puerto (solo la transferencia al puerto ocupa 2 instrucciones, o sea 8 ciclos de reloj!)

Es super interesante el modo de trabajar con ese acumulador, aunque no termino de entenderlo: Para preguntarte voy a simplificarlo a 16 bits:

supongamos que tengo una variable "acum" de esta forma:

H 00000000 L 00000000

y un vector en memoria de 256 posiciones con la señal almacenada

ahora bien, si yo hago:

acum++;

y uso el byte alto como puntero para buscar en la tabla de lookup, esto significaria que cada 256 ciclos, tendria 1 salto en el vector.

Esto actuaria como un divisor de frecuencia bastante grande.

Si acumulo de esta forma:

acum = acum + 256

Esto haria que en cada ciclo, el puntero cambia de "sample", dandome la F maxima en la salida del generador.

¿Asi es mas o menos como funciona tu rutina? (desconozco codigo AVR y se me complica interpretarla bien)

Si fuera asi, entonces tu rutina seria algo como (pseudocodigo):

while(not(interrupcionteclado)) {
acum = acum + divisor_8_bits;
puntero = byte_alto_de(acum);
out = vector[puntero];
escribirpuerto(out);
}

¿Esta mas o menos interpretado o entendi cualquier cosa?

Desde ya, muchas gracias, me interesa mucho ver los limites de esta posibilidad, ya que ese pseudocodigo, si lo hago en ASM debe ocupar un minimo de 16 instrucciones (y creo que me quedo muy corto), o sea 64 ciclos de reloj, que si fuera de 48mhz seria: 0.75mhz, los que en la practica seguro que se reducen a la mitad o menos :(
 
Última edición:
...

Es super interesante el modo de trabajar con ese acumulador, aunque no termino de entenderlo: Para preguntarte voy a simplificarlo a 16 bits:

supongamos que tengo una variable "acum" de esta forma:

H 00000000 L 00000000

y un vector en memoria de 256 posiciones con la señal almacenada

ahora bien, si yo hago:

acum++;

y uso el byte alto como puntero para buscar en la tabla de lookup, esto significaria que cada 256 ciclos, tendria 1 salto en el vector.

Esto actuaria como un divisor de frecuencia bastante grande.

Si acumulo de esta forma:

acum = acum + 256

Esto haria que en cada ciclo, el puntero cambia de "sample", dandome la F maxima en la salida del generador.

¿Asi es mas o menos como funciona tu rutina? (desconozco codigo AVR y se me complica interpretarla bien)

Exacto, así funciona el acumulador, es simplemente un preescaler variable que estará fijado por el paso que le des.

...Si fuera asi, entonces tu rutina seria algo como (pseudocodigo):

while(not(interrupcionteclado)) {
acum = acum + divisor_8_bits;
puntero = byte_alto_de(acum);
out = vector[puntero];
escribirpuerto(out);
}

¿Esta mas o menos interpretado o entendi cualquier cosa?

Si con la diferencia que el divisor_8_bits será el paso y no será de 8bits sino de 16 bits.

Desde ya, muchas gracias, me interesa mucho ver los limites de esta posibilidad, ya que ese pseudocodigo, si lo hago en ASM debe ocupar un minimo de 16 instrucciones (y creo que me quedo muy corto), o sea 64 ciclos de reloj, que si fuera de 48mhz seria: 0.75mhz, los que en la practica seguro que se reducen a la mitad o menos :(

¿16 instrucciones dentro del bucle? es demasiado.

Yo conseguí 7 instrucciones, en tu caso supongamos que serán 28 clk's dandote un fsampling de 1,7MHz valor similar al mío ;).

Hacé una cosa, fijate si podés encontrar el set de instrucciones del PIC que estas usando con los clk's detallados y yo te ayudo a traducir esa rutina.

Por otro lado, ya que sabes C, te recomiendo que busques como podés pasarle por argumentos a una función hecha en assembler desde C, que es lo que terminé haciendo yo, de esa forma el resto del código (menu, LCD, puerto serie o lo que sea, etc) lo hacés fácilmente en C.
 
Perfecto entonces, ya sabiendo como funciona puedo empezar a hacer algunas pruebitas.

Usando CCS C es trivial usar variables declaradas "en C" desde bloques #ASM, vamos a ver si consigo reducir la cantidad de instrucciones (fue un estimativo rapido nomas) No tengo ningun drama con assembler, e incluso CCS C te genera un assembler donde se puede revisar para optimizaciones.

Voy a hacer un borrador basado en PIC 18F2550 que es lo mas "pulenta" que hay en la gama que puede programar mi pickit. Si hay un interes se puede hacer una version de lo tuyo (que esta excelente) basada en PIC para los que, como yo, nunca tocaron otro micro :(

Muchas gracias!
 
Perfecto entonces, ya sabiendo como funciona puedo empezar a hacer algunas pruebitas.

Usando CCS C es trivial usar variables declaradas "en C" desde bloques #ASM, vamos a ver si consigo reducir la cantidad de instrucciones (fue un estimativo rapido nomas) No tengo ningun drama con assembler, e incluso CCS C te genera un assembler donde se puede revisar para optimizaciones.

No se como será en ese caso, pero trata de asegurarte de no usar RAM sino un registro de datos para que la cantidad de clk's sea menor. En AVR trabajar sobre RAM implican 2 clk's, en cambio sobre un registro de uso genérico solo 1 clk.

Otra cosa que se me pasó, seguramente ese PIC para que trabaje en 48MHz requiere usar el PLL, hay que ver que tan estable es ese clock, ya que en un PLL hay una zona llamada de captura en la cual sigue habiendo una cierta variación, ejemplo:

Cypress%20PLL%20Figure%203%203xx.jpg


Tal vez esa variación te afecte a la salida y obtenés una pequeña variación, será cuestión de hacer la prueba y ver que pasa.
 
Interesante lo que explicas del PLL, jamas lo hubiera pensado porque se usa para USB (sea cual sea el cristal externo, internamente lo divide para llevarlo a 4mhz y desde 4mhz lo levanta hasta 96mhz y de ahi deriva para la CPU y USB a 48mhz)

Entiendo que el USB no es muy tolerante a derivas pero no estoy seguro.

Sobre lo de no usar RAM, supongo que por "registro de datos" te referis a una tabla de lookup en memoria de programa? Comento que hice 3 pruebas distintas (todavia a 16 bits):

Lo siguiente toma 2.75us x sample:

Código:
// Esto es una tabla en la ROM de programa, accesible con la tecnica de hacer saltar el program counter
const unsigned int8 sine[256] = {
   128,131,134,137,140,143,146,149,152,155,
   158,162,165,167,170,173,176,179,182,185, etc.etcetc }

   while(TRUE) {
      prescaler = prescaler + paso;
      
      // Hacer obtencion de HIbyte + lookup + salida a puerto en una sola linea hace que el compilador optimize mejor, ahorrando 2 instrucciones.
      output_b(sine[prescaler >> 8]);
   }

Otra forma (en RAM) toma 1.75us x sample:

Código:
// Esto es un vector en RAM, precargado con los valores de la tabla ROM
unsigned int8  vector_senal[256];

   while(TRUE) {
      prescaler = prescaler + paso;
      i = prescaler >> 8;
      output_b(vector_senal[i]);
   }

Y por ultimo, esta forma toma 1.5833us x sample (ahorra 2 instrucciones = 8 clk, cada instruccion son 83.33ns excepto saltos y ciertos MOV)

Código:
// Esto es un vector en RAM, precargado con los valores de la tabla ROM
unsigned int8  vector_senal[256];

   while(TRUE) {
      prescaler = prescaler + paso;
      
      // Hacer obtencion de HIbyte + obtencion dato vector + salida a puerto en una sola linea hace que el compilador optimize mejor, ahorrando 2 instrucciones.
      output_b(vector_senal[prescaler >> 8]);
   }

Por lo tanto, en C "puro" hasta ahora logre 1.5833us x sample, tengo que ver si puedo ajustar mas eso mediante ASM, pero por lo que vi en el assembler generado por el compilador ya es muy optimizado... quiza cambiando de forma de hacer la rutina pueda optimizar mas (se me va a poner mucho mas lento si quiero llevarlo a 24bits)

Aceptando esta limitacion de velocidad, quiza pudiera aumentar la frecuencia maxima que se puede generar haciendo que, en vez de tener un ciclo de senoidal en 256 samples, tenga 2 o 4 o quiza hasta 8 en esa cantidad. De esta forma tendria algo parecido a mi generador de señal original (que era una sintesis de 16 u 8 samples solamente para todos los tipos de señal) y con esto, esos 1.5833us se pueden llevar a /2 /4 o /8. ¿Que opinan?

Otra pregunta: En tu diseño para el OPAMP usas fuente partida. Yo pensaba usar un capacitor de desacople y usar fuente simple. (veo que vos tenes C21-BIS) ¿Hay alguna razon importante que no este pensando para usar fuente partida? Descartando amplitud de señal, ya que no seria impedimento usar fuente simple de, digamos, 15v para tener 10vpp por dar un ejemplo. Esto te lo pregunto por curioso nomas, ya que no cuesta nada poner 2 baterias de 9v en serie con punto medio y listo (si se quiere evitar el trafo, que tampoco tendria mucho sentido evitarlo)
 
Sobre lo de no usar RAM, supongo que por "registro de datos" te referis a una tabla de lookup en memoria de programa?

No, hablo de los registros que poseen los uC en sus ALU para trabajar más rápido los datos, por ej. en un uP 8086 esos registros se llaman ax,bx,cx,dx, etc (eso en 16 bits, en 32 bits es eax).

Por lo que ví muy por arriba, en la familia PIC18, esos registros no se encuentran en la ALU, sino que están en la RAM y son varios. Sin embargo hay un registro que si se encuentra en la ALU y podés manipular que es el WREG y tal vez usando ese registro como indice te podés ahorrar un par de clk's.

Comento que hice 3 pruebas distintas (todavia a 16 bits):

Lo siguiente toma 2.75us x sample:

Código:
// Esto es una tabla en la ROM de programa, accesible con la tecnica de hacer saltar el program counter
const unsigned int8 sine[256] = {
   128,131,134,137,140,143,146,149,152,155,
   158,162,165,167,170,173,176,179,182,185, etc.etcetc }

   while(TRUE) {
      prescaler = prescaler + paso;
      
      // Hacer obtencion de HIbyte + lookup + salida a puerto en una sola linea hace que el compilador optimize mejor, ahorrando 2 instrucciones.
      output_b(sine[prescaler >> 8]);
   }

Otra forma (en RAM) toma 1.75us x sample:

Código:
// Esto es un vector en RAM, precargado con los valores de la tabla ROM
unsigned int8  vector_senal[256];

   while(TRUE) {
      prescaler = prescaler + paso;
      i = prescaler >> 8;
      output_b(vector_senal[i]);
   }

Y por ultimo, esta forma toma 1.5833us x sample (ahorra 2 instrucciones = 8 clk, cada instruccion son 83.33ns excepto saltos y ciertos MOV)

Código:
// Esto es un vector en RAM, precargado con los valores de la tabla ROM
unsigned int8  vector_senal[256];

   while(TRUE) {
      prescaler = prescaler + paso;
      
      // Hacer obtencion de HIbyte + obtencion dato vector + salida a puerto en una sola linea hace que el compilador optimize mejor, ahorrando 2 instrucciones.
      output_b(vector_senal[prescaler >> 8]);
   }

Por lo tanto, en C "puro" hasta ahora logre 1.5833us x sample, tengo que ver si puedo ajustar mas eso mediante ASM, pero por lo que vi en el assembler generado por el compilador ya es muy optimizado... quiza cambiando de forma de hacer la rutina pueda optimizar mas (se me va a poner mucho mas lento si quiero llevarlo a 24bits)

No está tan mal, pero fijate que en assembler y usando un acumulador de 24 bits yo conseguí un fsampling de 1,77MHz (o sea 562 nS).

Si bien los compiladores de C te tratan de optimizar el assembler, yo creo que lo mejor es remangarse y meter uno mismo mano en assembler.

Aceptando esta limitacion de velocidad, quiza pudiera aumentar la frecuencia maxima que se puede generar haciendo que, en vez de tener un ciclo de senoidal en 256 samples, tenga 2 o 4 o quiza hasta 8 en esa cantidad. De esta forma tendria algo parecido a mi generador de señal original (que era una sintesis de 16 u 8 samples solamente para todos los tipos de señal) y con esto, esos 1.5833us se pueden llevar a /2 /4 o /8. ¿Que opinan?

Con el uso del acumulador no tenés que pensar en la cantidad de muestras que tenés almacenadas (bah en realidad si, 256 sería lo ideal por lo que ya mencioné, justo es la cantidad de datos que pueden direccionar 8 bits). Pero debería ser exactamente lo mismo tener 128 muestras que 256, ya que es el acumulador en encargarse de barrer dichas muestras, para que se entienda cuando trabajas a fsampling (máxima frecuencia) de las 256 muestras, solo tomás "1", por eso mi generador está limitado a 500kHz, ya que en esa frecuencia solo tengo 3 muestras y por eso requiero de bruto filtro.

Aunque a la larga decidí hacer una pequeña modificación en el programa, y para frecuencias mayores a 300kHz usar un acumulador de solo 8bits para obtener un fsamplig de 2,28MHz obteniendo así 4 muestras en 500kHz.

Otra pregunta: En tu diseño para el OPAMP usas fuente partida. Yo pensaba usar un capacitor de desacople y usar fuente simple. (veo que vos tenes C21-BIS) ¿Hay alguna razon importante que no este pensando para usar fuente partida? Descartando amplitud de señal, ya que no seria impedimento usar fuente simple de, digamos, 15v para tener 10vpp por dar un ejemplo. Esto te lo pregunto por curioso nomas, ya que no cuesta nada poner 2 baterias de 9v en serie con punto medio y listo (si se quiere evitar el trafo, que tampoco tendria mucho sentido evitarlo)

Ninguna, simplemente tenía un trafo de 6+6 sin uso y decidí utilizarlo para este proyecto.
 
Bueno, optimizando al maximo y usando el pic a 48mhz (serian mas o menos como tu AVR a 12mhz y aun menos optimo por el tipo de instrucciones menos avanzadas que tiene)

Es una mezcla de C con assembler y va asi:

Código:
while(TRUE) {
      #ASM ASIS
// Lo siguiente hace: prescaler24bits = prescaler24bits + paso16bits
      MOVF   paso_lo,W
      ADDWF  prescaler_lo,F
      MOVF   paso_hi,W
      ADDWFC prescaler_mi,F
      MOVLW  00
      ADDWFC prescaler_hi,F
//   En este punto, tengo en prescaler_hi la posicion del vector que tengo que sacar por el puerto.
//   Lo siguiente es comparativo a: output_b(vector_senal[prescaler_hi]); pero un poquito mas optimizado
      MOVF   prescaler_hi,W   //MOVF   prescaler_hi,W
      ADDLW  vector_senal     //ADDLW  vector_senal
      MOVWF  0xFE9              //MOVWF  FSR0L
      MOVLW  00               //MOVLW  vector_senal+-9
      ADDWFC 0x03,W             //ADDWFC @03,W
      MOVWF  0xFEA              //MOVWF  FSR0H
      MOVFF  0xFEF,0x109          //MOVFF  INDF0,@@109
      MOVFF  0x109,0xF8A          //MOVFF  @@109,LATB
      #ENDASM
   }

Todas esas instrucciones usan 1 ciclo de micro (4 ciclos de cristal) excepto las MOVFF y el salto del loop que usan 2 ciclos de micro, totalizando 18 ciclos o 1.5us x sample y esto es lo MINIMO que logre a 24bits. Si el prescaler lo hago de 16bits me ahorro tan solo 2 ciclos del micro, o sea, 1.33us por sample.

Incluso pensando en overclockear el pic a 64mhz (que seria algo mas cercano a tu AVR) el bucle toma 1.125us y encima perderia comunicacion usb con la PC (aunque aun se podria mantener una comunicacion RS232 quiza)

He probado con proteus de que, usando la escalera R2R y solo 1 capacitor de 1nf como filtro, necesito 8 muestras de una senoidal de 256 muestras para poder representar una senoidal con forma en el osciloscopio. Ya con 4 puntos se ve muy triangular. Si no me equivoco esto vos lo solucionaste con un filtro muy bien diseñado.

Aun si usara 4 muestras, a 1.5us por muestra, tengo una senoidal de 166khz como maximo.

Entonces, me puse a pensar que pasaria si voy por una solucion hardware, de la siguiente forma:

- Un reloj base de alta frecuencia, no se, 80mhz
- Un micro chico, cuya unica funcion sera manejar interfaz y transferir una forma de onda a una SRAM externa
- La misma red R2R y filtro aplicados a esa SRAM
- Y (aca viene la clave) una serie de contadores que hagan por hardware exactamente el mismo prescaler que estoy intentando hacer por soft.

Si pudiera hacer esto, el limite ya no estaria en la velocidad del micro, puesto que este no hace nada mas que transferir los samples a la SRAM cuando quiero cambiar de señal.

Sin embargo aca se me queman los libros: Si pongo 3 contadores (digamos 74HC4040 aunque es ripple no sync) en cascada, usando 8 bits de cada uno y unos selectores como el 74HC151.

Los contadores serian prescaler_lo, prescaler_mi, prescaler_hi y la SRAM seria el vector, algo asi:

prescaler.png

¿Lo que obtengo con esto no es el prescaler que hacemos por soft, sino simplemente un divisor de mucha menor resolucion? No hice una prueba completa pero me da la sensacion de que este esquematico no esta formando un acumulador de 24bits

Si esto tuviera sentido, entonces se complica el hard, pero da la posibilidad de llegar a frecuencias mucho mayores. ¿Que opinan?

Y por ultimo, la 3ra alternativa:

Misma mecanica que la solucion hardware, pero sin contatores y usando el micro como prescaler unicamente, es decir, sacar por el puerto B la direccion de la SRAM externa donde esta el dato. De esta forma, el bucle en el micro usa 666.66ns lo que me da una Fsample de 1.5 MHZ (a 24 bits) o de 500ns - 2MHZ con prescaler de 16bits.

La verdad, bastante lio debido a las limitaciones de los PIC :(

Edito: el esquema que puse con los contadores es un divisor 8x8, o sea, podria dividir la frecuencia de reloj en 64 nada mas... muy pobre
 
Última edición:
Atrás
Arriba