A digital filter system (DFS), Part 2

Editor’s note: In this Design Idea (DI), contributor Bonicatto designs a digital filter system (DFS). This is a benchtop filtering system that can apply various filter types to an incoming signal. The filtering range is up to 120 kHz.
In Part 1 of this DI, the DFS’s function and hardware implementation are discussed.
In Part 2 of this DI, the DFS’s firmware and performance are discussed.
Firmware
The firmware, although a bit large, is not too complicated. It is broken into six files to make browsing the code a bit easier. Let’s start with the code for the LCD screen, and its attached touch detector.
Wow the engineering world with your unique design: Design Ideas Submission Guide
LCD and touchscreen code
Here might be a good place to show the LCD screens, as they will be discussed below.
The LCD screen in the DFS. From the top left: Splash Screen, Main Screen, Filter Selection Screen, Cutoff or Center Frequency Screen, Digital Gain Screen, About Screen, and Running Screen.
A quick discussion of the screens will give you a good overview of the operation and capabilities of the DFS. The first screen is the opening or Splash Screen—not much here to see, but there is a battery charge icon on the upper right.
Touching this screen anywhere will move you to the Main Screen. The Main Screen shows you the current settings and allows you to jump to the appropriate screen to adjust the filter type, cutoff, or center frequency of the filter, and the digital gain. It also has the “RUN” button that starts the filtering function based on the current settings.
If you touch the “Change Filter Type” on the Main Screen, you will jump to the Filter Selection Screen. This lets you select the filter type you would like to use and if you want 2-pole or 4-pole filtering. (Selecting a 2-pole filter will give you a roll-off of 24 dB per octave or 80 dB per decade, while the 4-pole will provide you with a roll-off of 12 dB per octave or 40 dB per decade.) When you touch the “APPLY” button, your settings (in green) will be applied to your filter, and you will jump back to the Main Screen.
In the Main Screen, if you want to change the cutoff/center frequency. You can set any frequency up to 120 kHz. Hitting “Enter” will bring you back to the Main Screen.
If you want to change the digital gain (gain applied when the filter is run on a sample, between the ADC and the DAC, touch “Change Output Gain” on the Main Screen. The range for this gain is from 0.01 to 100. Again, “Enter” will take you back to the Main Screen.
On the Main Screen, selecting “About” will take you to a screen showing lots of interesting information, such as your filter settings, your current filter’s coefficients, battery voltage, and charge level. (If you do not have a battery installed, you may see fully charged numbers as it is seeing the charger voltage. There is a #define at the top of the main code you can set to false if you don’t have a battery installed, and this line won’t show.) The last item shown is the incoming USB voltage.
In the main code, hitting “Run” will start the system to take in your signal at the input BNC, filter it, and send it out the output BNC. The Running Screen also shows you the current filter settings.
Now, let’s take a quick look at the mechanics of creating the screens. The LCD uses a few libraries to assist in the display of primitive shapes and colors, text, and to provide some functions for reading the selected touchscreen position. When opening the Arduino code, you will find a file labeled “TFT_for_adjustable_filter_7.ino”. This file contains the code for most of the screen displays and touchscreen functions. The code is too long to list here, but here are a few lines of code setting the background color and displaying the letters “DFS”:
tft.fillScreen(tft.color565(0xe0, 0xe0, 0xe0)); // Grey
tft.setFont(&FreeSansBoldOblique50pt7b);
tft.setTextColor(ILI9341_RED);
tft.setTextSize(1);
tft.setCursor(13, 100);
tft.print("DFS");
A lot of the functions look like this, as they are used to present the data and create the keypads for filter selection, frequency setting, and digital gain setting. In this file are also the functions for monitoring and logically connecting the touch to the appropriate function.
The touchscreen, although included on the LCD, is essentially independent from it. One issue that needed to be solved was that the alignment of the touchscreen over the LCD screen is not accurate, and pixel (0,0) is not aligned with the touch position (0,0) on the touchscreen.
Also, the LCD has 240×320 pixels, and the touch screen has, roughly, 3900×900 positions. So there needs to be some calibration and then a conversion function to convert the touch point to the LCD’s pixel coordinates. You’ll see in the code that, after calibration, the conversion function is very easy using the Arduino map() function.
Other files
There is also a file that contains the initialization code for the ADC. This sets up the sample rate, resolution, and other needed items. Note that this is run at the start of the code, but also when changing the number of poles in the filter. This is due to the need to change the sample rate. Another file contains the code to initialize the DAC, and is also called when the program is started.
Also included is a file with code to pull in factory calibration data for linearizing the ADC. This calibration data is created at the factory and is stored in the microcontroller (micro)—nice feature.
Filtering code
Now to the more interesting code. There is code to route you to the screens and their selections, but let’s skip that and look at how the filtering is accomplished. After you have selected a filter type (low-pass, high-pass, band-pass, or band-stop), the number of poles of the filter (2 or 4), the cutoff or center frequency of the filter, or adjusted the internal digital output gain, the code to calculate the Butterworth IIR filter coefficients is called.
These coefficients are calculated for a direct form II filter algorithm. The coefficients for the filters are floats, which allow the system to create filters accurately throughout a 1 Hz to 120 kHz range. After the coefficients are calculated, the filter can be run. When you touch the “RUN” button, the code begins the execution of the filter.
The first thing that happens in the filter routine is that the ADC is started in a free-running mode. That means it initiates a new ADC reading, waits for the reading to complete, signals (through a register bit) that there is a new reading available, and then starts the cycle again. So, the code must monitor this register bit, and when it is set, the code must quickly get the new ADC reading and filter it, and then send that filtered value to the DAC for output.
After this, it checks for clipping in the ADC or DAC. The input to the DFS is AC-coupled. Then, when it enters the analog front-end (AFE), it is recentered at about 1.65 V. If any ADC sample gets too close to 0 or too close to 4095 (it’s a 12-bit ADC), it is flagged as clipping, and the front panel input clipping LED will light for ~ 0.5 seconds. This could happen if the input signal is too large or the input gain dial has been turned up too far.
Similarly, the DAC output is checked to see if it gets too close to 0 or to 2047 (we’ll explain why the DAC is 11 bits in a moment). If it is, it is flagged as clipping, and the output clipping LED will light for ~0.5 seconds. Clipping of the DAC could happen because the digital gain has been set too high. Note that the output signal could be set too high, using the output gain dial, and it could be clipping the signal, but this would not be detected as the amplified analog back-end (ABE) signal is not monitored in the micro.
Now to why the DAC is 11 bits. In the micro, it is actually a 12-bit DAC, but in an effort to get the sample rate as fast as possible, I discovered that the DAC had an unspec’d slew rate limit of somewhere around 1 volt/microsecond.
To work around this, I divide the signal being passed to the DAC by two so the DAC’s output voltage doesn’t have to slew so far. This is compensated for in the ABE as the actual gain of the output gain adjust is actually 2 to 10, but represented as 1 to 5. [To those of you who are questioning why I didn’t set the reference to 1.65 (instead of 3.3 V) and use the full 12 bits, the answer is this micro has a couple of issues on its analog reference system that precluded using 1.65 V.]
One last task in the filter “running” loop is to check if the “Stop” has been pressed. This a simple input bit read so it only takes few cycles.
A couple of notes on filtering: You may have noticed that there is an extra filter you can select – the “Pass-Thru”. This takes in the ADC reading, “filters” it by only multiplying the sample by the selected digital gain. It then outputs it to the DAC. I find this useful for checking the system and setup.
Another note on filtering is that you will see, in the code, that a gain offset is added when filtering. The reason for this is that, when using a high-pass or band-pass filter, the DC level is removed from the samples. We need to recenter the result at around 2048 counts. But we can’t just add 2048, as the digital gain also comes into play. You’ll see this compensation calculated using the gain and applied in the filtering routines.
Performance
I managed to get the ADC and DAC to work at 923,645 samples per second (sps) when using any of the 2-pole filters. This is a Nyquist frequency around 462 kHz. Since the selectable upper rate for a filter is 120 kHz, we get an oversample rate of almost 4. For 4-pole filters, the system runs at 795,250 sps, for an oversample rate of around 3.3.
The 4-pole filters are slower as they are created by cascading two 2-pole filters. [To check these sample rates and see if the loop has enough time to complete before the following sample is read, I toggle the D4 digital output as I pass through the loop. I then monitor this on a scope.]
The noise floor of the output signal is fairly good, but as I built this on a protoboard with no ground planes, I think it could be lower if this were built on a PCB with a good ground plane. The limiting factor on my particular board, though, is the spurious free dynamic range (SFDR).
I do get harmonic spurs at various frequencies, but I can get up to a 63 dB SFDR. This is not far from the SFDR spec of the ADC. I did notice that the amplitude of these harmonic spurs changed when I moved cables inside the enclosure, so good cable management may improve this.
Use of AI
Recently, I’ve been using AI in design, and I like to include quick information describing if AI helped in the design—here’s how it worked out. The code to initialize the ADC was quite difficult, mostly because there was a lot of misinformation about this online.
I used Microsoft Copilot and ChatGPT to assist. Both fell victim to some of the online misinformation, but by pointing out some of the errors, I got these AI systems close enough that I could complete the ADC initialization myself.
To design the battery charge indicator on the splash screen, I used the same AI. It was a pure waste of time as it produced something that didn’t look like a battery – it was easier and faster to design it myself.
The code for the filter coefficients was difficult to track down online, and ChatGPT did a much better job of searching than Google.
For circuit design, I tried to use AI for the Sallen-Key filter designs, but it was a failure, just a waste of time. Stick to the IC supplier tools for filter design.
Tried to use AI to design a nice splash screen, but I wasted more time iterating with the chatbot than it took to design the screen.
I also tried to get AI to design an enclosure with a viewable LCD, BNC connectors, etc. To be honest, I didn’t give it a lot of chances, but it couldn’t get it to stop focusing on a flat rectangular box.
Modifications
The code is actually easy to read, and you may want to modify it to add features (like a circular buffer sample delay) or modify things like the filter coefficient calculations. I think you’ll find this DFS has a number of uses in your lab/shop.
The schematic, code, 3D print files, links to various parts, and more information and notes on the design and construction can be downloaded at: https://makerworld.com/en/@user_1242957023/upload
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
- A digital filter system (DFS), Part 1
- How much help are free AI tools for electronic design?
- Non-linear digital filters: Use cases and sample code
- A beginner’s guide to power of IQ data and beauty of negative frequencies – Part 1
- A Sallen-Key low-pass filter design toolkit
- Designing second order Sallen-Key low pass filters with minimal sensitivity to component tolerances
- Toward better behaved Sallen-Key low pass filters
- Design second- and third-order Sallen-Key filters with one op amp
The post A digital filter system (DFS), Part 2 appeared first on EDN.


