
If you periodically need to see the frequency response of a circuit, this easy, inexpensive project can help you out.
Editor’s note: This is a two-part series on how to create a noise generator with an adjustable bandwidth and a consistent amplitude. The previous entry:
The operation and firmware
As I mentioned last time, I was able to reuse much of the firmware from a previous Design Idea project. The Arduino C code consists of three files. One is the initialization code for the DAC, while another contains code for the LCD/touch screen operations. The third is the main code. Let’s look at these one at a time.
Wow the engineering world with your unique design: Design Ideas Submission Guide
The DAC initialization code does just what it says and is designed to get a DAC output as fast as possible. The LCD/touch screen code is the largest piece of the software puzzle. Before discussing it “under the hood”, let’s take a quick look at the some of the LCD/touch screen display outputs. Figure 1 shows most of the screens used in the BANG.
![]() |
![]() |
![]() |
![]() |
Figure 1 The BANG LCD screens are designed to be both intuitive and informative.
The first screen you see after the power-up splash screen is what I call the main screen. It allows you to select an output, but let’s hold off discussing this implementation aspect in detail until later. For now, just understand that on power-up, it will default to the noise output on the AC and DC BNC connectors.
Also on the main screen is the “Change Bandwidth” selection that will allow you to set the bandwidth for the noise (noise bandwidth is measured from 0 Hz). When you press “Change Bandwidth”, the screen will change to the keypad and allow you to enter your desired number. Note that if you exceed the maximum 225 kHz it will default to 225 kHz. Similarly, if you enter a number less than the minimum of 500 Hz it will default to 500 Hz. After hitting “ENTER” you will return to the main screen.
On the main screen, selecting “About” will take you to a screen showing lots of interesting information such as your selected bandwidth and the gain it will apply to the noise during filtering. You’ll also see the sample rate (which is fixed), firmware version, and (for those that are interested) your current IIR filter’s coefficients. Next, it shows the battery voltage and charge level. (If you do not have a battery installed you may see fully charged numbers as it is instead reporting the charger voltage. There is a #define in the top portion of the main code that you can set to “false” instead, in which case this line won’t be displayed if you don’t have a battery installed.) The last item shown is the incoming USB voltage.
The last screen shown in Figure 1 is the one displayed when “RUN” is selected on the main screen. If you see this screen, the noise signal is being generated and is being output to the BNC connectors.
Let’s talk a little about the code for creating these screens. It’s a bit long and mostly involves setting colors, drawing boxes, selecting fonts, aligning text in the box, and capturing positions of key presses. Almost all of this is done using higher level calls to the downloadable “Adafruit GFX Graphics Library”. Here’s a short example of the code showing how to display the word “BANG” in red against a grey background:
tft.fillScreen(tft.color565(0xe0, 0xe0, 0xe0)); // Grey
tft.setFont(&FreeSansBoldOblique50pt7b);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(13, 100);
tft.print("BANG");
The third C file is the main code, which mostly directs calls to the correct LCD screen, executes miscellaneous housekeeping operations, and (of course) generates the noise signal, the latter starting with the bandwidth selected from the touchscreen. Using this value, we generate the coefficients for a digital 2-pole low-pass Butterworth IIR filter. The next step is to get a value for the gain we will be using on the noise signal. This is done by calling a function that has the bandwidth as an input and returns a gain number. Here is the code for that function:
//******************************************************
// AGC *
// Does an automatic gain adjust to the *
// random number amplitude. Run once after *
// startup or a change in the LP filter. *
//******************************************************
float AGC(float cutoff_freq) {
float agcGain = 1;
// Calculate agc gain based on the set bandwidth
if (cutoff_freq >= 50000) agcGain = 31.0 * pow(cutoff_freq, -0.292); // for 225kHz to 50kHz
else agcGain = 393.769851 * pow((cutoff_freq - 97.8961702), -0.524598029); // Curve fit of freq vs. amplitude data
gainOffset = 1024.0f * (2.0f - agcGain); // Adjustment for shift in DC level
return agcGain;
}
You’ll see that there are two different formulas used for agcGain, based on whether the bandwidth selected is greater than 50 kHz. This dual-equation method makes curve fitting more accurate. These formulas were derived from data I generated by setting a bandwidth and then adjusting the gain in code to get a desired amplitude. The data was then used to generate curve-fitted equations (kudos to Standards Applied Engineering Tools, whose Curve Fitting Online utility gave by far the most accurate curve fit of all the tools I found and tried). Later, I’ll also detail how AI did (or, maybe more accurately, didn’t) with generating the same curve fit equation(s).
You can see from the second equation that the power function is based on -0.52; roughly the square root of 2 as we talked about at the beginning of part 1 of this series. The reason it is not exactly a square root of 2 function is because some noise, beyond the cutoff frequency of the 2-pole digital IIR filter, still exists in this roll-off portion of the filtered signal – i.e., it is not a brick wall filter.
Figure 2 shows a graph of this gain vs. bandwidth selected.

Figure 2 This graph shows the linear gain vs. bandwidth result for the equations used in this design.
With the bandwidth entered and the gain calculated, it is then incorporated into the coefficients of the lowpass IIR filter. This approach optimizes the calculations; we don’t need to add another multiplier inside the speed-optimized output loop.
Ok: we’re now ready to generate the noise signal. When the user selects “RUN”, the code enters a tight loop. In it, we get a random number from the true random number generator (TRNG). Next, we run the number through the IIR filter, which also applies the gain. Then, the lower 12 bits of this number are sent out of the DAC. (A note: the DAC has a slew rate of somewhere around 1 µS per volt to minimize the effect. The number is scaled to keep the signal mean coming from the DAC to around 1/2 Vcc.) This loop continues until the user selects “STOP”.
Those of you following closely may be thinking something along the lines of the following right now: “Another way to generate a noise signal of a given amplitude is to simply generate the random samples at a lower sample rate”. The downside of this alternative approach is that the analog reconstruction filter would need to be adjusted to follow the sample rate, which seems like a much more difficult analog design task. Also, we would still need to perform the digital low-pass filtering for anti-aliasing.
It’s time to look at the output of the BANG. Figure 3’s scope display shows the AC output time domain signal on the left and the FFT on the right. The BANG is set to give an output with a 25 kHz bandwidth.

Figure 3 This scope plot shows the BANG output with a 25 kHz bandwidth setting.
The enclosure
The BANG’s enclosure derives from a custom 3D-printable model (see later for a file-download link). It includes three parts: the main body, the base/PCB mount, and a stylus for the touchscreen. The main body’s download is modeled with two filament colors but can alternatively be printed in one color. If printed in a single color, the text is still readable, as it is also embossed. The base holds a 120 mm x 80 mm PCB. I used a protoboard as there were a minimal number of parts and was faster to build than designing and waiting for a custom PCB.
Wait, there’s more
While TRNGs are common in larger processors, they’re more rare in smaller micros. Most compilers therefore use pseudo-random number generators instead. But since this system was generating 32-bit true random numbers, it occurred to me that such a data stream may also have other uses, such as in cryptography systems, input data for testing code, a “seed” for pseudo-random number generators, or even helping you select “picks” for playing the lottery.
More broadly, it seemed like a waste to not have a way to output these generated numbers. So, I included support for this feature, via USB, in two format options – ASCII data or binary data. The desired format can be chosen from the “Select Output” LCD page shown in Figure 4 (as mentioned earlier, the power-up default is the noise generator output via the analog BNC connectors).

Figure 4 The design includes support for outputting the 32-bit true random numbers generated, over USB and in two format options.
Note that although the data is 32 bits, it can be sliced or appended to form any size random number you require. For example, you can use one bit of the 32-bit source, which will still be random, or you can append two 32-bit output numbers to create a truly random 64-bit number.
Comments on AI use
I only used AI (and then only experimentally) for one part of the project, the curve fitting of test data to create the equation(s) for the AGC. The result was…interesting. I’d already developed the earlier discussed frequency-to-gain equations for the AGC algorithm, but I thought I should also try AI to see what it came up with. I fired up Microsoft Copilot and gave it the frequency vs gain data that I’d already created by iteratively setting a frequency and then adjusting gain in the code until I got the fixed amplitude I was looking for.
Copilot noted that it looked like a power equation – good. Then it gave me a very simple equation: gain = 1.96 * freq-0.52 . Wow, I thought, much simpler than the equations I’d came up with. But it seemed too good to be true, so I got out a calculator. At a frequency of 10 kHz the gain should be around 3. When you make the calculation on the AI’s formula you get around 0.016. When I asked Copilot to use its equation on 10 kHz, it said the gain would be 3.68. Another AI with a case of cognitive dissonance. Perhaps obviously, I used the other formula instead!
Conclusion
This is a fairly easy and inexpensive project to build. If you periodically have the need to see the frequency response of a circuit, it may help you out.
Note that the schematic, code, 3D print files, Arduino software, links related to various parts of the project, and additional notes and pictures on the project’s design and construction can be downloaded for free at the MakerWorld website.
Damian Bonicatto is a consulting engineer with decades of experience in embedded hardware, firmware, and system design. He holds over 30 patents.
Phoenix Bonicatto is a freelance writer.
Related Content
- Making noise with a BANG, part 1: Concept and hardware
- A digital filter system (DFS), Part 1
- A digital filter system (DFS), Part 2
The post Making noise with a BANG, part 2: Software, integration and operating results appeared first on EDN.



