Arbitrary Wave Generator With the Raspberry Pi Pico

27,169

148

80

Introduction: Arbitrary Wave Generator With the Raspberry Pi Pico

Just two weeks ago, the pico, a new microcontroller, the pico, was released by the Raspberry Pi Foundation, well known for the incredibly successful series of Raspberry Pi single-board computers. The new microcontroller uses a brand new chip, designed in-house, the RP2040. It has two 32-bit cores running by default at 125MHz. It has been criticised for not having Wifi or Bluetooth, and no hardware floating point math. But it has a very fast internal bus and powerful peripherals. It has been designed for makers and has very strong support: it was released with 6 detailed datasheets and a beginner’s guide book, which is available free of charge as pdf. Best of all, it is cheap at $4. I got 5 for under 30EUR including shipping.

As a test I wanted to see if any of my previous projects based on the Arduino Uno/Nano could benefit from a remake with this much more powerful board: After all it has 4x the bus width, 8x the clock frequency, 130x the RAM, and is more than a decade more modern. My choice fell on the Arbitrary waveform generator (AWG). With the Arduino, I managed to squeeze out 381ksps, since every sample update took 42 instruction cycles, mostly because updating a 32-bit phase counter takes a quadruple loop with an 8-bit CPU. My expectation was that it should be possible to improve this by a factor 8 just from clock speed and maybe another factor 2 because the new board is 32-bit. However, after reading selected parts of the 637-page datasheet of the new RP2040 chip, I realised it might be possible to update every single clock cycle! Just by initialising 2 peripherals, the DMA (Direct Memory Access) and the PIO (programmable Input/Output), an array can be cyclically streamed to the output pins.

Indeed, it works, and the increase in speed with the Arduino is more than a factor 300, from 381ksps to 125Msps. That is similar to serious lab-AWGs, which cost ~100EUR for budget models. There is no attempt here to provide a buffer or amplifier for the produced signal, it is beyond my skills and my equipment to come up with a buffer/amplifier beyond the 10MHz range. The produced signal is thus rather weak, with an output impedance of ~1kOhm, and a maximum current draw of ~1mA. Suggestions for a buffer/amplifier are welcome in the comments! There is no attempt either to provide a dedicated user interface in terms of a screen, buttons, rotary encoders etc. That adds cost and complexity. I found it is much more convenient and much more powerful to set the requested waveform in the micropython code itself!

For comparison, several instructables (e.g. here, here and here) describe how to make a function generator based on the dedicated AD9833 chip. This chip runs at 25Msps and can generate only 3 predefined waveforms: sine, triangle and square. The pico is 5x faster and can generate any possible wave that fits in an array, up to many thousands of points.

Supplies

Required materials:

  • Raspberry pi pico microcontroller with male pin headers
  • 1 5x7cm prototype board
  • 2 20-pin female pin headers
  • 23 resistors of identical value, near 2kOhm

Step 1: Construction of the R2R DAC

The pico itself cannot produce analog signals, it does not have a digital-to-analog converter (DAC). But they are simple to build from resistors. We use here the classic R2R DAC. An 8-bit DAC requires 7 resistors of value R and 9 resistors of value 2R. The actual value of R is not critical, but it is essential that all ‘R’ resistors have the same value and that all 2R resistors have double that value. In practice, this is best achieved by using 23 resistors of 2R, and putting 7 pairs of them in parallel to create the 7 ‘R’ resistors. Resistors with a power rating of 0.25W and a tolerance of 1% are cheap in packs of 100, and that is what I recommend to use. I had an unused pack of 2kOhm resistors. I think any value of R in the range 1kOhm-10kOmh will be fine. Smaller values will draw more current than the Pico can provide and larger values result in poor performance because there is too little current to counteract parasitic capacitance and/or inductance at high frequencies.

I numbered all the resistors, measured their values and put them in a spreadsheet. None differed by more than 1% from the nominal value, but by selecting I could reduce the effective spread from 1 percent to 1 per mille. For the ‘2R’ resistors I picked a value near the mean of which there were at least 9, which happened to be the value 2000. Then I picked 7 pairs that were equally distant from the value 2000, for example 3 pairs of 1998+2002 Ohm and 4 pairs of 1997+2003 Ohm. In parallel, equal but opposite deviations cancel!

Solder the female headers to the prototype board such that the pico fits comfortably. Now solder the resistors according to the provided schematics and pictures. Note that the resistors are mounted vertically only to save space. Feel free to mount them horizontally if space allows. The board layout is done with KiCad. I did not make a PCB, but a PCB layout helps to solder effectively on the 5x7cm prototype board. Note that my build differs slightly from the KiCad design, I made some improvements to the design after soldering it up.

At the end I added to two male header pins: one for the signal and one for ground. Probe clips and crocodile clips attach well to them.

Step 2: Uploading the Software and Run

The pico can be programmed in C or micropython. Using micropython is much easier since you don’t have to install the ‘C-SDK’. Micropython may be 100x slower than C, but here it doesn’t matter. All the code does is making an array with a waveform and then instructing the peripherals to stream that array to the output pins. The CPU is idle and free to perform other tasks.

Make sure your pico has the micropython UF2 file uploaded. I used version rp2-pico-20210205-unstable-v1.14-8-g1f800cac3.uf2. Version 1.13, which was originally provided on release day, misses the 'uctypes' module. I used the Thonny IDE to upload the python script, I had never heard of that before but it works well, at least for small scripts like this.

The code as is will run though a few hundred example waves (see video at step 1). To modify this, scroll to line 155 and set up your own wave.

There are 6 basic pulse shapes: sine, pulse, gaussian, sinc, exponential and noise. The sine has no extra parameters, but the pulse has 3: risetime, uptime and falltime. Gaussian, sinc and exponential have one parameter, which determines their width, and noise has one parameter, which determines the pdf of the output values, from 1 (flat) to 8 or larger (nearly gaussian).

A wave will have a shape, but also amplitude and offset. The shape can be replicated within a period, which increases its frequency. That way, operations like summing or multiplying can be done with waves of different frequencies. A non-zero phase shift can also be set, but its effect is only noticeable when combining waves.

Waves can be combined by either summing, multiplying or applying a phase modulation with another wave. The resulting combined wave can then be added, multiplied or modulated with another basic or complex wave. The screenshots and video show just a few examples of how complex shapes can be made.

There is only one value for the frequency, even when combining waves. Operations are not performed on-the-fly by the CPU, but stored in a buffer and played by the DMA/PIO. The 'duplication' keyword however does allow for a shape to fill at double, tripe, quadruple etc values of the (base) frequency.

The buffer size (value of maxnsamp) determines how complex the shape can be made, and how accurate the frequency can be set. For simple waves, like single sine or wide pulses, 1024 samples may be sufficient. A buffer size of 65536 (64kB) is the practical maximum. Filling the buffer may take 20-60s at that size!

Step 3: ​Comments About the Code

I admit the code looks obscure: most of it is based on direct register access, and require studying the 637-page datasheet of the RP2040 chip to understand. But I’ll try to explain the thought behind it.

The crucial peripheral is the so-called Direct Memory Access (DMA) module of the chip. The DMA can be instructed to perform block copies between memory and peripherals without requiring the attention of the CPU. Really, I had no clue this existed just 2 weeks go either! It is like having an assistant whom you tell to do shopping for you!

Anyway, this DMA is being told to transfer the contents of the array with the waveform to another peripheral (the PIO) which will put the values on the output pins. One complication is that this DMA needs to do this cyclically, and without interruption. For that, a second DMA channel is instructed to reconfigure and restart the first DMA channel as soon as it is done. This is called ‘chaining’. So channel 0 does the transfer, and passes the stick on to channel 1. But channel 1 immediately tells channel 0 to restart. You might expect a delay in this swapping between the channels, but that delay is absorbed in buffers: the DMA transfers 32-bit words, while the PIO only ‘eats’ 8-bit bytes. So the PIO still has some snacks in its buffer while the DMA is losing 1 or 2 cycles to start over again. I am amazed by the engineers and scientists who came up with such intricate hardware!

The DMA cannot control the pins directly, but there is something better: the pico has 2 Programmable Input/Output (PIO) units, which have 4 processing units themselves, (called 'state machines'). They are really 8 tiny microcontrollers inside the microcontroller itself. Here, only 1 state machine is used, and it is programmed with a single command (would this qualify for a Guinness World record of smallest computer program?) The command is ‘out(pins,8)’ which instructs the state machine to pass 8 bits from its buffer to the output pins. Wrapping is implied, so the state machine just keeps doing this single command, every clock cycle. As the 8 bits are shifted to the pins, the buffer will request to be refilled by the DMA when 32 bits have been consumed.

And that’s all. So the code consists of

  • The configuration of the DMA
  • The configuration and programming of the PIO
  • The filling of the array with the waveform
  • Starting up the DMA.

At that moment the waveform is produced and the CPU’s of the pico are free for other tasks. There will be data traffic on the bus, but the pico has a highly parallel bus structure, and I expect no noticeable slow-down.

Step 4: Ideas for Improvement

I posted this instructable a bit prematurely to demonstrate the capabilities of this new microcontroller. I hope it will motivate some of you to get a board and try out its new features.

For this AWG project I have several ideas on how to improve it:

  • It might very well be possible to have higher resolution (10 or 12 bit) and/or a second channel.
  • The RP2040 can be overclocked to 250 MHz or more, resulting in 250Msps AWG
  • Sweeps of frequency or other parameters can be implemented in the python code after setup.
  • A real AWG needs a buffer and/or amplifier to reduce the output impedance and extend the voltage swing.
  • A well-designed PCB and SMD resistors might reduce parasitic capacitance or inductance and improve the bandwidth.
  • A screen and buttons, or a touch-screen could make it into a self-confined apparatus, like the commercial devices.

Step 5: Appendix on Data Throughput

Several other users have extended the AWG with a larger number of output bits, either for higher precision, a second channel, or both. When doing so, it appears the data throughput is not always able to hold up to the sampling speed. I have investigated this a bit and tell here the current status of my findings.


The key quantity is the number of samples that can be stored in a 32-bit word. In the present project that was 4: every 32-bit word contains 4 samples of 8 bits each. Thus, the DMA only has to run at one quarter of the clock speed and it does so without any problem.


For a 10-bit DAC, 3 samples can be stored in a 32-bit word. I found the present setup results in a small hiccup at the restart of the DMA, which, however is resolved by setting fifo_join=PIO.JOIN_TX in the PIO decorator. This enlarges the FIFO that transmits to the PIO from 4 to 8 words, at the cost of the (here unused) FIFO that receives from the PIO.


For an 11-bit DAC, a 12-bit DAC or a 2-channel 8-bit DAC, a 32-bit word can only fit 2 samples. Here I would have expected that the DMA would be able to cope, but it does not. Apparently, in the present setup, a 32-bit word transfer is done at most every other clock cycle. There may be a way to solve this, but at present I have no idea how. In any case, with the DMA just able to follow up with the PIO, there is a substantial delay when the DMA reconfigures. This is solved by slowing down the PIO by a factor two, but of course this results in the sampling frequency now being only half the clock speed.


For a 2-channel DAC beyond 8 bits, only one sample fits in a 32-bit word. Hiccups can be avoided by slowing down the PIO by a factor three.


Of course a given piece of hardware can be configured either for speed (up to 10 bits, with the remainder unused) or for precision/channels (e.g. 2x11 or 12 bits) running at one third of the sampling speed. Best would be to force the unused pins to zero (not done in the attached script).


An updated script is attached here, which allows to demonstrate and test these scenarios. It is set up for a 2-channel 11-bit DAC, running from pin 0 to pin 21, with the first channel going from pin 0 to pin 10 and the second channel from pin 11 to pin 21. The second channel however has inverted bit order: the MSB is on pin 11 and the LSB on pin 21. This was done to reduce cross-talk, but it has the additional advantage that the most significant bits are in the middle, which makes it possible to run it as a 2-channel 8-bit DAC with consecutive pins. The script now also allows to set the clock speed anywhere between 100 and 250 MHz: many have reported that overclocking by up to a factor two from the nominal 125MHz is OK. In addition, matching the clock frequency with the output frequency can result in a particularly stable output wave. 


Microcontroller Contest

Participated in the
Microcontroller Contest

Be the First to Share

    Recommendations

    • Science Fair Challenge

      Science Fair Challenge
    • Trash to Treasure Contest

      Trash to Treasure Contest
    • Electronics Contest

      Electronics Contest

    80 Comments

    0
    lighty
    lighty

    18 days ago

    Great project! I was surprised to see what small Pico Pi can do due to its fast DMA/PIO transfers (too bad that most ARM processors have much slower GPIOs).

    I'm trying to understand pros/cons of this approach and I have a few questions that you may be able to address.

    1. Firstly, I'm curious about the relationship between sampling rate and data buffer size (I'm accustomed to C and Python is a bit confusing for me). If I understand your code correctly, it calculates optimal number samples in the LUT and adjusts clock to match it (at least to some extent). Then you store samples for 360° of waveform period. That's just my guess considering that in your scope shots I didn't see any obvious continuous waveform truncation.

    However, considering the slight mismatch between sampling rate (clock) and the number of samples in LUT I guess that there could be introduced some slight mismatch between the set frequency of the waveform and the real waveform produced by R2R.

    Did you perhaps check out if there is indeed a frequency mismatch of set wave and really produced and how big it is?


    2. Secondly, I'm curious to understand how you handle low frequency waveforms. I mean if you have high MSPS then LUT size for the low frequency signal period would greatly exceed the number of samples in LUT. For example, 1 Hz waveform would require at a few million discrete samples. The way I understand it, the only seeming way to use LUT size of 65536 samples (8-bit) would be to repeat sending a number of samples in the LUT . In essence one would have to skip reading each value in each clock and re-use old values a few times before reading and using next ones in the LUT.

    For example, for 12 MSPS and waveform of 1 Hz one would need 12'000'000 discrete samples. With LUT size of 65536 bytes it would mean that you would have to re-send one value 183 times (150000000 / 65536 = 183.1) before sending the next value in the LUT. In reality it would lower waveform resolution to 183 values for 1 Hz waveform. However, you're using automatic DMA/PIO transfers so repeating same sample several times is obviously not the case.

    So, how are you pulling off 1 Hz waveform with very high sampling rate? I'm obviously missing something here.


    In any case, you've done beautiful work and I thank you for sharing your work.

    0
    rgco
    rgco

    Reply 17 days ago

    You've been having a very detailed look indeed!

    Concerning 1) the frequency mismatch: it is approximately inversely proportional to the buffer size. So for a 1k buffer size the frequency mismatch is O( 10^-3) The RAM can be pushed to ~100k samples to 10^-5 frequency mismatch (or frequency steps) are possible but better not. To get more precise requires a phase-accumulator approach as was done in my previous (linked in the intro) Arduino-based project. It is my understanding that serious AWG's indeed implement a phase accumulator in FPGA, I don't think it's possible with the PIO but can be done in the CPU, but the sample rate will go down a lot.

    Concerning 2) Low frequencies are achieved with prescaling the PIO clocks. It's true I did not comment on that in the description. The pio clock can be lowered by up to a factor 2^16, so a period of 1 second can be reached even with a 2k buffer.

    0
    LIC
    LIC

    Reply 16 days ago

    Thank you for your answers. I was analyzing your code in order to better understand what you did with small Pico Pi. I'm interested in making AWG with sampling rate of at least 50 MSPS but so far there were a few snags with that idea.

    1. FPGA approach of calculating each sample on the run by using phase accumulator is, of course, the best and it is usually used considering their speed of math and IO. However, from what I see, currently FPGA market is dry with hardly any reasonably capable model available.

    Phase accumulator approach of calculating sample to sample could be done by faster ARM by using fixed point math (even on processors with FPU there is some speed gain by using FPM) and approximation based sin(). However, I was surprised to see that great majority of ARM processors have rather slow GPIO, which would inevitably negate processing speed. There is a new ARM by Nexperia that boasts 600 MHz core and fast GPIO but cost is high and availability is even worse than FPGA

    Also, SBC like Raspberry Pi could be used in real time phase accumulator sample to sample calculation but, surprisingly enough, it also has very slow GPIO and on top of that it has variable latency could be problem (no way to precisely time output). Using PCIe port on RPI and aAdding external FIFO would most likely solve latency issues but then again one still needs fast device to read FIFO and time samples precisely and we're back to square one.

    2. Prescaling the clock is quite good solution considering that RP2040 is capable in that department. However that limits capabilities of any AM modulation. My reasoning with implementation of "skipping" frames (in fact a kind of software prescaler) was that in that case one could generate two LUTs and use timer interrupts to do just a simple math (multiplication) of samples from two LUTs. There would still be present inevitable frequency mismatch but at least AM modulation would be possible without waveform truncation.

    That solution would require implementing header (a few bytes) at the beginning of LUT to store size of LUT, counter of present position in LUT, number of samples to be repeated and counter of repeated samples. Considering that all is done in integer math the time spent in the interrupt would be short.


    Very crude pseudo code example in C would be something like this for uint16_t LUT. It likely contain errors and is far from optimal working code but I'm just trying to convey the basic idea. Obviously, it would be much better to use uint8_t array to increase number of available samples in SRAM and in that case global variables or better yet global struct could be used to uint16_t header values (counters). All of this would take some time but it would still be much faster than dynamically calculating each sample in ISR (consider it a "hybrid" of LUT and dynamic calculation).


    //two values for multiplication
    uint16_t WavesToMultiply[2] = {0, 0};

    //initializing LUT0 (malloc() could be used if careful)
    //four words in header to hold counters
    //this wave is low frequency modulating
    uint16_t Wave0 [CalculatedSizeOfLUT0 + 4] =
    {CalculatedSizeOfLUT + 4,
    NoOfSamplesToSkip,
    CounterOfSkippedSamples,
    CounterOfPositionInTheLUT,
    0, 0, 0...};

    //initializing LUT1 (malloc() could be used if careful)
    //four words in header to hold counters
    //this wave is high frequency carrier
    uint16_t Wave1 [FixedSizeOfLUT1+ 4] =
    {CalculatedSizeOfLUT1 + 4,
    NoOfSamplesToSkip,
    CounterOfSkippedSamples,
    CounterOfPositionInTheLUT,
    0, 0, 0...};

    //fill LUT0 with the calculated samples
    for
    (uint16_t n = CalculatedSizeOfLUT0 + 4; i <
    CalculatedSizeOfLUT0; n++){

    Wave0[n] = CalculateSingleSample();
    }
    //fill LUT1 with the calculated samples
    for (uint16_t n = CalculatedSizeOfLUT1 + 4; i < CalculatedSizeOfLUT1; n++){

    Wave1[n] = CalculateSingleSample();
    }
    //timer ISR
    ISR (...){

    //go through LUT0 and duplicate samples if necessary
    if (Wave0)
    [NoOfSamplesToSkip] != 0 {Wave0[CounterOfSkippedSamples] += 1;
    if (Wave0[CounterOfSkippedSamples] > Wave0) [NoOfSamplesToSkip]){
    Wave0[CounterOfSkippedSamples] = 0;
    Wave0[CounterOfPositionInTheLUT] += 1;
    SamplesToMultiply[0] = Wave0 [CounterOfPositionInTheArray];
    }
    else
    SamplesToMultiply[0] = Wave0 [CounterOfPositionInTheLUT];

    //go through LUT1 and duplicate samples if necessary
    Wave1
    [CounterOfSkippedSamples] += 1;
    if (Wave1[CounterOfSkippedSamples] > Wave1 [NoOfSamplesToSkip]){
    Wave1[CounterOfSkippedSamples] = 0;
    Wave1[CounterOfPositionInTheLUT] += 1;
    SamplesToMultiply [1] = Wave1 [CounterOfPositionInTheLUT];
    }
    else
    SamplesToMultiply[1] = Wave1 [CounterOfPositionInTheLUT];

    //multiply current samples and send it to GPIO (perhaps by using DMA if faster)
    SendToGPIO(SamplesToMultiply[0] * SamplesToMultiply[1]);

    }
    0
    ViralP12
    ViralP12

    Question 6 weeks ago

    How to generate sweep frequency using pi pico ?

    0
    rgco
    rgco

    Answer 17 days ago

    No, this method is not suitable for sweeps.

    0
    Denis FPV
    Denis FPV

    2 months ago

    Hello, I have a power part of the amplifier in the form of a Full-Bridge and maybe this is a stupid question, I'm not good at electronics. Is it possible with this code to somehow use all this PWM to modulate the sine on the Full-Bridge?

    0
    rgco
    rgco

    Reply 2 months ago

    In principle yes. Here, I output a bit stream to 8 pins, but you can also stream it to 1 pin and codify a PWM sine to it. There is a more direct way, I recall seeing examples where the DMA feeds a stream directly to the PWM, thus avoiding the need for PIO and requiring a much smaller buffer.

    0
    Denis FPV
    Denis FPV

    Reply 2 months ago

    Thank you for your answer, I just want to make a tesla coil and it needs a powerful alternating current source, so I asked

    0
    Denis FPV
    Denis FPV

    Reply 2 months ago

    More precisely, if it is correct to say whether this PWM of the microcontroller can be used immediately by applying to the driver, without the binding side and the DAC

    0
    gtdevo
    gtdevo

    Question 2 months ago

    Thanks so much for posting this. I've been looking for a solution to being able to make a sine wave signal from the pico and I'm new to all of this so your post is a learning opportunity too. I've made the circuit including the R2R DAC but for some reason, the amplitude of my sine wave does not seem to change when I run the code for the included amplitude step. I'm sending the signal to a speaker and hear no difference when the amplitude changes (also using an oscilloscope app that shows no change in wave amplitude either). Could it be a problem with my R2R circuit? Or do I need to make larger changes in the amplitude code to hear a difference? Lastly, I have to reset my pico after running the code, every time, in order for the code to work again the next time that I run the code...if not, there will be no signal when running the code. Any idea how I can change the code to prevent the need to manually reset my hardware after every time that I stop the code?

    0
    Luchete
    Luchete

    5 months ago

    @rgco thanks for such an incredible project with the raspberry pi pico, based on the @wolf2018 circuit I added feedback to maintain a stable voltage at the output with an attiny 85. I have not finished the program yet but the idea is to take the peak of the envelope and make it go to the ADC input of the attiny85 and then vary the value of the digital potentiometer(x9c103) so that the gain of the AD8055 goes up or down depending on the peak value read, if it is above a certain trigger, the gain goes down and if the signal is too small raise the gain.
    Some notes:
    -Since the swing is at most (in ideal rail-rail) 5Vp, I've decided to put the program's (not done yet XD) trigger points in 3.5V as the upper limit and 3.2V as the lower limit
    -The SR5100 diodes and the bridge rectifier are totally overkill, but I put them in since those are the ones I have at home.
    -The feedback attenuation is not necessary since the signal at the output will not exceed 5v of the rail, so it will not exceed the 5v of supply of the attiny either. It's there in case the circuit evolves and I get bigger swings, the attenuation level is a factor of 0.7 ~

    0
    rgco
    rgco

    Reply 5 months ago

    Glad you like it! The use of a digital potentiometer to regulate the gain of the amplifier stage is very interesting. But adding a second microcontroller seems a bit of an overkill! Can't it be done by the PICO itself? Also, the need to stabilize is not obvious to me, there should't be any drifts no? If anything, I would be very interested in a digitally (preferably I2C) controlled variable gain, so that the full resolution of the R2R ladder would be available also for small signals.

    0
    Luchete
    Luchete

    Reply 5 months ago

    The PICO is probably more than enough for this application, the intention of using the attiny85 is to avoid using the processing power of the PICO for anything other than the sampling itself (tbh I don't know how those extra lines of code can affect the already extremely fast sampling), there are many I2C/SPI controlled digital potentiometers and with better resolution than the one i used too, those are more than suitable for this aplication (such as the mcp41010), that one was my choice because it was the most affordable and available in local stores. What I was referring to about the stable output voltage is the fact that as the PICO output signal increases in frequency to values that pass 100KHz and beyond the MHz, the signal output from the DAC R2R is attenuated by various things like parasitic capacitances and inductances, open loop gain loss in op amps, etc.To avoid that the voltage at the output also falls along with the input due to these things, in the amplification stage the gain is raised so that the peak voltage of the output remains stable in the range of 3.2V-3.5V

    0
    rgco
    rgco

    Reply 5 months ago

    OK, understood, thanks! Yes, the bandwidth of the output stage is finite. I think I quoted 20MHz as the -3dB point for my prototype. It should be possible to raise it with SMD components or a dedicated DAC chip. A filter could flatten the response for higher frequencies. Regulating the amplification based on the output envelope may work for sine waves, but arbitrary waves have higher harmonics, so the feedback will not correct for the distortion, I think it is more common to use high-bandwidth elements all over.
    Concerning processing power, the current AWG code does not use any CPU cycles, it is based purely on DMA and PIO, so both(!) CPUs are free for other tasks. One only has to watch out for competing on the bus for access to RAM.

    0
    Galopago
    Galopago

    6 months ago on Step 4

    Excellent project. Took note of various ideas! thanks

    0
    wolf2018
    wolf2018

    10 months ago

    @rgco, thanks for that great instructable!
    I built it and it works nicely. I have done some work on a "poor man's" buffer amplifier using readily available components of discrete video amplifiers. The AD8055 for decoupling and 2x amplifier and NTE2633/34 transistors for a class AB buffer stage.
    Frequency response is approx. 20Hz to 20 MHz. Probably higher, but output voltage of the Pico's R2R network begins to drop above 5 MHz due to stray capacitance of my (bread) board (though it still works up to 35MHz with decreasing amplitude).
    Few remarks on the design:
    - the video transistors have a "high" UCEsat, thus I opted for +/-12V for the output stage for a 6.2V pp output swing.
    - the buffer stage is not short circuit prove unless you add a 27R resistor in series (R11).
    - R11 determines the output impedance
    - DC coupling of the output is possible, but there is a small DC offset.
    - 2N2219 and 2N2905 are drop in replacements for Q1 and Q2, upper limit is then ~5 MHz

    Input and improvements are always welcome

    AWG_buffer_amp.pngAWG_board.jpg
    0
    rgco
    rgco

    Reply 10 months ago

    Awesome! Thanks for sharing. I ordered some AD8055, it seems a very suitable chip for this.

    0
    wolf2018
    wolf2018

    Reply 10 months ago

    update:
    missed to add one remark:
    - lower end of bandwidth (20Hz) is with output DC coupled to the load (C5 removed / replaced by a wire).

    0
    edwardbrian939
    edwardbrian939

    Question 11 months ago

    have anyone tried working with this project with an external trigger? can the generation of the pulse waveform be triggered externally like how a normal AWG be triggered? and reading through the comments I could see that this board has 8 output pins for waveform generation, can each of the waveform output different frequency?

    0
    rgco
    rgco

    Answer 11 months ago

    Good suggestions! Thinking aloud, an external trigger is probably possible. Another PIO channel could poll an input pin and reset the DMA counter at rising or falling flank. Seems nontrivial but doable and interesting!
    Concerning different frequencies, the 8 output pins are combined into a single analog channel, so there is no point in running them at different frequencies. A second output channel could be implemented and I suppose the frequency can be set independently, but it would need its own PIO and DMA. I think that should work, the bus structure is highly parallel.