Ir a contenido
AVISO IMPORTANTE: El martes 24 y miércoles 25 de diciembre no habrá servicio ni se realizarán entregas y envíos. ¡Gracias por tu comprensión!
AVISO IMPORTANTE: El martes 24 y miércoles 25 de diciembre no habrá servicio ni se realizarán entregas y envíos. ¡Gracias por tu comprensión!
Construye un generador de señales con la Raspberry Pi Pico - 330ohms

Construye un generador de señales con la Raspberry Pi Pico - 330ohms

Un generador de señales es un instrumento muy útil en el laboratorio para probar diferentes circuitos. Comprar un instrumento como este puede ser bastante caro, pero esto no tiene que ser así. Con una Raspberry Pi Pico y un puñado de resistores, es posible armar un generador de frecuencias, con diferentes ondas, de frecuencia configurable y de hasta 20MHz, sin muchas complicaciones.

Funcionamiento

El proyecto es bastante simple, consiste en generar una señal dada en un array, dentro del código en MicroPython e ir enviando los datos a través del puerto GPIO de la Pico. Los valores se convertirán en una señal analógica posteriormente a través de un arreglo R-2R, es decir, estaremos convirtiendo una señal digital en analógica.

Algunas pruebas con el generador de ondas. Vía: rgc o

Señales fundamentales

El código configura 6 señales elementales que puedes usar y combinar entre sí para formar otras formas de onda que te pueden servir. Estas funciones son sine, pulse, gaussian, sinc, exponential y noise. En el ejemplo que incluimos al final puedes encontrar unas porciones de código que puedes descomentar para activar cada función. Los parámetros de cada onda están comentados para que puedas ver cómo reaccionan a distintos valores.

#Señal senoidal, los valores van de 0 a 1.0 wave1=wave() wave1.amplitude=0.1 wave1.offset=-0.4 wave1.phase=0.0 #Este parametro determina cuantas veces se replica la señal #sobre el mismo periodo; i.e si replicate=3 f_final=3*f_base #Si es 0, no hay señal wave1.replicate=1  wave1.func=sine wave1.pars=[]  # #Señal pulso, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=pulse # wave1.pars=[0.1,0.5,0.1]  # #Señal gaussiana, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=gaussian # wave1.pars=[2] #Usa valores entre 0.5 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal sinc, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=sinc # wave1.pars=[2] #Usa valores entre 0.5 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal exponencial, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=exponential # wave1.pars=[4] #Usa valores entre 1 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal de ruido, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=noise # wave1.pars=[10] #Usa valores entre 1 y 10, ajusta la amplitud acorde al parámetro (determina la longitud)

Código en MicroPython

El código es sencillo y para seleccionar la onda de salida, solo tienes que modificar algunas líneas del código. El código completo lo incluimos al final de la entrada. Al final del código encontrarás una función en donde puedes pasar como parámetros la frecuencia y las señales que quieres mezclar. Un ejemplo de como ajustar la frecuencia y las ondas es este ejemplo que suma una señal seno con una gaussiana a 1 kHz:

#Puedes controlar la amplitud, el offset, la fase de la señal y el tipo de señal, en este caso usamos una señal seno wave1=wave() wave1.amplitude=1.0 wave1.offset=0.0 wave1.phase=0.0 wave1.replicate=1 wave1.func=sine wave1.pars=[0]  #En esta otra señal usamos una señal wave2=wave() wave2.amplitude=1.0 wave2.offset=0.0 wave2.phase=0.0 wave2.replicate=0 #Si ponemos 1 aqui la señal se llena de ondas cuadradas wave2.func=gaussian wave2.pars=[1]  #Aqui configuramos las señales de salida y la frecuencia, en este caso juntamos un seno con una gaussiana a una frecuencia de 1000 setupwaves(wavbuf[ibuf],1e3,wave1,wave2); ibuf=(ibuf+1)%2

Actualización 16/02/2022: Al realizar algunas pruebas se encontró que debes desactivar la señal wave2 ya sea configurando un valor 0 en replicate o en la amplitud. Esto se debe a que el autor del programa añadió un segundo canal a su ultima revisión, pero se sobreponen las señales.

Diagrama esquemático

Para armar el circuito solo necesitas resistores del mismo valor. Se trata de un arreglo R-2R para 8 bits, que en este caso usa resistores de precisión de 2kOhms. Puedes hacer los arreglos con otros resistores, pero este valor te dará un buen balance de impedancia de salida.

Solo necesitas algunas resistencias y una placa de cobre para hacer este proyecto. Vía: rgco en Instructables

Conclusiones:

Si quieres llegar al rango de frecuencia más alto que puede producir este generador, debes usar una tarjeta de cobre con pistas más anchas. Coloca tu proyecto en un gabinete metálico y usa terminales BNC. Este proyecto se puede mejorar más con un poco de acondicionamiento de señal, usando amplificadores operacionales para aumentar el voltaje de salida. ¡Sigue nuestro blog para más ideas de proyecto con la Raspberry Pi Pico!

Referencias:

Arbitrary Wave Generator With the Raspberry Pi Pico

# Arbitrary waveform generator for Rasberry Pi Pico # Requires 1 or 2 R2R DACs. Works for R=1kOhm # Achieves up to 125Msps when running 125MHz clock # Rolf Oldeman, 7/11/2021. CC BY-NC-SA 4.0 licence # tested with rp2-pico-20210902-v1.17.uf2 # Revisado por Abraham Camarillo 16/02/2022  # Probado con rp2-pico-20220117-v1.18.uf2  from machine import Pin,mem32 from rp2 import PIO, StateMachine, asm_pio from array import array from utime import sleep from math import pi,sin,exp,sqrt,floor from uctypes import addressof from random import random  fclock=250000000 #clock frequency to run the pico default 125MHz. Allow 100-250 nbit_ch1=11      #also type sum of nbit_ch1 and nbit_ch1 in 'stream' function !! nbit_ch2=11      inv_ch1=False    #set true if MSB comes first inv_ch2=True sampword=1       #number of samples per 32-bit word pinbase=0        #first active pin maxnword=2048    #maximum number of words per buffer (max 8192 = 128kbyte total)  #set desired clock frequency if fclock<100e6 or fclock>250e6:     print("invalid clock speed",fclock)     exit(1) PLL_SYS_BASE=0x40028000 PLL_SYS_FBDIV_INT=PLL_SYS_BASE+0x8 PLL_SYS_PRIM     =PLL_SYS_BASE+0xc if fclock<=130000000:     FBDIV=int(fclock/1000000)     POSTDIV1=6  #default 6     POSTDIV2=2  #default 2 else:      FBDIV=int(fclock/2000000)     POSTDIV1=3  #default 6     POSTDIV2=2  #default 2 mem32[PLL_SYS_PRIM]=(POSTDIV1<<16)|(POSTDIV2<<12) mem32[PLL_SYS_FBDIV_INT]=FBDIV print('clock speed',FBDIV*12/(POSTDIV1*POSTDIV2),'MHz')   DMA_BASE=0x50000000 CH0_READ_ADDR  =DMA_BASE+0x000 CH0_WRITE_ADDR =DMA_BASE+0x004 CH0_TRANS_COUNT=DMA_BASE+0x008 CH0_CTRL_TRIG  =DMA_BASE+0x00c CH0_AL1_CTRL   =DMA_BASE+0x010 CH1_READ_ADDR  =DMA_BASE+0x040 CH1_WRITE_ADDR =DMA_BASE+0x044 CH1_TRANS_COUNT=DMA_BASE+0x048 CH1_CTRL_TRIG  =DMA_BASE+0x04c CH1_AL1_CTRL   =DMA_BASE+0x050  PIO0_BASE      =0x50200000 PIO0_TXF0      =PIO0_BASE+0x10 PIO0_SM0_CLKDIV=PIO0_BASE+0xc8  #state machine that just pushes bytes to the pinsß @asm_pio(out_init=(PIO.OUT_HIGH,)*(nbit_ch1+nbit_ch2),          out_shiftdir=PIO.SHIFT_RIGHT,          autopull=True,          fifo_join=PIO.JOIN_TX,          pull_thresh=(nbit_ch1+nbit_ch2)*sampword) def stream():     out(pins,22)  #initialize PIO - the frequency setting assumes clock speed of 125MHz sm = StateMachine(0, stream, freq=125000000, out_base=Pin(pinbase)) sm.active(1)  #2-channel chained DMA. channel 0 does the transfer, channel 1 reconfigures p=array('I',[0]) #global 1-element array def startDMA(ar,nword):     #first disable the DMAs to prevent corruption while writing     mem32[CH0_AL1_CTRL]=0     mem32[CH1_AL1_CTRL]=0     #setup first DMA which does the actual transfer     mem32[CH0_READ_ADDR]=addressof(ar)     mem32[CH0_WRITE_ADDR]=PIO0_TXF0     mem32[CH0_TRANS_COUNT]=nword     IRQ_QUIET=0x1 #do not generate an interrupt     TREQ_SEL=0x00 #wait for PIO0_TX0     CHAIN_TO=1    #start channel 1 when done     RING_SEL=0     RING_SIZE=0   #no wrapping     INCR_WRITE=0  #for write to array     INCR_READ=1   #for read from array     DATA_SIZE=2   #32-bit word transfer     HIGH_PRIORITY=1     EN=1     CTRL0=(IRQ_QUIET<<21)|(TREQ_SEL<<15)|(CHAIN_TO<<11)|(RING_SEL<<10)|(RING_SIZE<<9)|(INCR_WRITE<<5)|(INCR_READ<<4)|(DATA_SIZE<<2)|(HIGH_PRIORITY<<1)|(EN<<0)     mem32[CH0_AL1_CTRL]=CTRL0     #setup second DMA which reconfigures the first channel     p[0]=addressof(ar)     mem32[CH1_READ_ADDR]=addressof(p)     mem32[CH1_WRITE_ADDR]=CH0_READ_ADDR     mem32[CH1_TRANS_COUNT]=1     IRQ_QUIET=0x1 #do not generate an interrupt     TREQ_SEL=0x3f #no pacing     CHAIN_TO=0    #start channel 0 when done     RING_SEL=0     RING_SIZE=0   #no wrapping     INCR_WRITE=0  #single write     INCR_READ=0   #single read     DATA_SIZE=2   #32-bit word transfer     HIGH_PRIORITY=1     EN=1     CTRL1=(IRQ_QUIET<<21)|(TREQ_SEL<<15)|(CHAIN_TO<<11)|(RING_SEL<<10)|(RING_SIZE<<9)|(INCR_WRITE<<5)|(INCR_READ<<4)|(DATA_SIZE<<2)|(HIGH_PRIORITY<<1)|(EN<<0)     mem32[CH1_CTRL_TRIG]=CTRL1   def invbits(x,n):     y=0     for i in range(n):         if (x&(1<0 : y+=(1<<(n-1-i))     return y   def setupwaves(buf,f,w1,w2):     if sampword==1: mindiv=3     if sampword==2: mindiv=2     if sampword==3: mindiv=1     if sampword==4: mindiv=1              div=fclock/(f*(maxnword*sampword)) # required clock division for maximum buffer size     if div> 0         buf[iword*4+1]=(word&(255<< 8))>> 8         buf[iword*4+2]=(word&(255<<16))>>16         buf[iword*4+3]=(word&(255<<24))>>24         #print(iword,word)              #set the clock divider     clkdiv_int=min(clkdiv,65535)      clkdiv_frac=0 #fractional clock division results in jitter     mem32[PIO0_SM0_CLKDIV]=(clkdiv_int<<16)|(clkdiv_frac<<8)      #start DMA     startDMA(buf,nword)   #evaluate the content of a wave def eval(w,x):     m,s,p=1.0,0.0,0.0     if 'phasemod' in w.__dict__:         p=eval(w.phasemod,x)     if 'mult' in w.__dict__:         m=eval(w.mult,x)     if 'sum' in w.__dict__:         s=eval(w.sum,x)     x=x*w.replicate-w.phase-p     x=x-floor(x)  #reduce x to 0.0-1.0 range     v=w.func(x,w.pars)     v=v*w.amplitude*m     v=v+w.offset+s     return v  #some common waveforms. combine with sum,mult,phasemod def sine(x,pars):     return sin(x*2*pi) def pulse(x,pars): #risetime,uptime,falltime     if x10=gaussian     return sum([random()-0.5 for _ in range(pars[0])])*sqrt(12/pars[0])       #make buffers for the waveform. #large buffers give better results but are slower to fill wavbuf={} wavbuf[0]=bytearray(maxnword*4) wavbuf[1]=bytearray(maxnword*4) ibuf=0  #empty class just to attach properties to class wave:     pass  #Puedes controlar la amplitud, el offset, la fase de la señal y el tipo de señal, en este caso usamos una señal seno  #Señal senoidal, los valores van de 0 a 1.0 wave1=wave() wave1.amplitude=0.1 wave1.offset=-0.4 wave1.phase=0.0 #Este parametro determina cuantas veces se replica la señal #sobre el mismo periodo; i.e si replicate=3 f_final=3*f_base #Si es 0, no hay señal wave1.replicate=1  wave1.func=sine wave1.pars=[]  # #Señal pulso, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=pulse # wave1.pars=[0.1,0.5,0.1]  # #Señal gaussiana, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=gaussian # wave1.pars=[2] #Usa valores entre 0.5 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal sinc, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=sinc # wave1.pars=[2] #Usa valores entre 0.5 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal exponencial, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=exponential # wave1.pars=[4] #Usa valores entre 1 y 4, ajusta la amplitud acorde al parámetro (determina la longitud)  # #Señal de ruido, los valores van de 0 a 1.0 # wave1=wave() # wave1.amplitude=1.0 # wave1.offset=0.0 # wave1.phase=0.0 # wave1.replicate=1 # wave1.func=noise # wave1.pars=[10] #Usa valores entre 1 y 10, ajusta la amplitud acorde al parámetro (determina la longitud)  #En esta otra señal usamos una señal wave2=wave() wave2.amplitude=1 wave2.offset=0.0 wave2.phase=0.0 wave2.replicate=0 wave2.func=sine wave2.pars=[]  #Aqui configuramos las señales de salida y la frecuencia, en este caso juntamos un seno con una gaussiana a una frecuencia de 1000 setupwaves(wavbuf[ibuf],1000,wave1,wave2); ibuf=(ibuf+1)%2
Artículo anterior Manual Raspberry Pi Pico + Arduino IDE #5 - 330ohms
Artículo siguiente Simula tus proyectos con TinkerCAD Circuits - 330ohms