Low pass filtering in software - Page 1
Forum section: electronics / general

author: Simon
author_rank: Unknown
timestamp: February 10, 2025, 07:55:05 am
content: I'm currently low pass filtering my ADC signals with the current formula:Code:average = (average * (n-1) + x ) / n ;Does this approximate an RC filter (1st order 3db) ? if it does how would I calculate the frequency response. The values of "x" are delivered at regular intervals.The ADC itself does permit me to accumulate values and right shift the result to perform averaging. this is not the same calculation but does it amount the same thing? I need 2 differently filtered streams of my readings so a hardware filter it out (I actually just took it out!).

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 10, 2025, 08:31:05 am
content: This is an exponential moving average, where alpha = 1/n.You would get much lower exec time if n is a power of 2, as you can just right-shift instead of dividing, which is always expensive for arbitrary values (although the compiler may change it to a multiplication if 'n' is known at compile time).Alternatively, you could use floating point if you have access to a FPU and using fp otherwise makes sense.If you want to know more:https://en.wikipedia.org/wiki/Exponential_smoothingEMA:y = alpha*x + (1 - alpha)*yYour case:y = (1/n)*x + ((n-1)/n)*y = (1/n)*x + (1 - 1/n)*yso it's the same thing with alpha = 1/nThis article gives you the frequency and impulse responses:https://blog.mbedded.ninja/programming/signal-processing/digital-filters/exponential-moving-average-ema-filter/

author: Simon
author_rank: Unknown
timestamp: February 10, 2025, 10:54:58 am
content: aha, thank you. So I am on the right track. Unfortunately no FPU so the best I can do is take my 16 bit ADC value and put it into a 32 bit variable shifted up by 8 bits. so I have 8 bits on the right to act as digits after the decimal place and 7 bits on the left that the multiplication can happen into. unfortunately the values represent a signed quantity with an offset so I have to subtract that on each reading. I don't know how long an abs() in C takes as I really don't need the sign, that would mean that yes bit shifting unsigned values may work but I guess it's all the same with the hardware divider.I do have a hardware divider that will take no more than 32 cycles and I'm running at 48MHz / 21ns cycles.I don't know

author: Siwastaja
author_rank: Super Contributor
timestamp: February 10, 2025, 11:11:10 am
content: Also, if using integers and not floating point, remember to multiply x by value similar to (or same as) n; and divide down theaveragebefore using with the same factor. Otherwise you are getting a lot of quantization loss.Why? Think about what happens with n = 1000 and x = 57 (let's assume average is 0 initially):average = (0 * 999 + 57) / 1000 = 57 / 1000 = 0Repeat this as many times as you want, but result stays 0, when it should be slowly approaching 57.Fix this by:average = (0 * 999 + 57*1000) / 1000 = 57000 / 1000 = 57(use as average/1000 = 57/1000, which still rounds to 0, but hey, now average is at value 57 so next round gets bigger and bigger...And as SiliconWizard says, use powers of two for efficient division-by-shift:average_accumulator = (average_accumulator*255 + 256*x ) / 256;cur_average = average_accumulator/256;

author: gf
author_rank: Super Contributor
timestamp: February 10, 2025, 12:10:14 pm
content: Quote from: Simon on February 10, 2025, 07:55:05 amThe EMAy=alpha*x+(1-alpha)*yis basically aDirect Form Iimplementation of the transfer functionH(z)=alpha/(1+(alpha-1)*z^-1).In Octave (or Matlab), you can plot the frequency response in the following way:Code:pkg load signalalpha = 0.25; % put your alpha herefs = 1000; % put your sample rate hereb = alpha % numerator coefficients of transfer functiona = [ 1 alpha-1 ] % denominator coefficients of transfer function[H,f] = freqz(b, a, 10000, fs);plot(f, 20*log10(abs(H)))grid onxlabel("frequency")ylabel("dB")EDIT: Goggle also revealed this documenthttps://tttapa.github.io/Pages/Mathematics/Systems-and-Control-Theory/Digital-filters/Exponential%20Moving%20Average/Exponential-Moving-Average.htmlwhich even gives closed-form expressions for the frequency response and cutoff frequency.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 10, 2025, 03:55:37 pm
content: Quote from: Simon on February 10, 2025, 10:54:58 amOn ARMv6-m (Cortex-M0/M0+) and ARMv7e-m (Cortex-M3/M4/M7) it's two or three instructions, no jumps.If you have<stdlib.h>or equivalent, just use itsabs().If you use GCC or Clang, use__builtin_abs(). This should yield the fastest integer absolute value the compiler can generate.(Unless there is an even better way, it usually ends up becomingt=xarithmetic right-shifted all the way so that sign bit is copied to all bits, then abs(x) = (x+t)^t= (x^t)-t.)Here's the code I would probably use on a Cortex-M:Code:const int32_t average_scale; // 1 .. 32767, constexpr for C++int32_t average_magnitude_scaled; // 0 .. 1073741823uint32_t average_magnitude; // 0 .. 32768uint32_t update_average_magnitude(const int16_t sample) {const int32_t sample_magnitude = abs(sample);average_magnitude_scaled += (int32_t)average_scale * (sample_magnitude - (int32_t)(average_magnitude_scaled >> 15));average_magnitude = (uint32_t)(average_magnitude_scaled + 16384) >> 15;return average_magnitude;}It compiles to less than two dozen instructions, no conditionals; and uses a single 32×32=32-bit (signed) multiplication. The rest are binary operations, additions, and subtractions. You could definitely use multiple in parallel, too, even on a Cortex-M0.For the mathematical analysis,average_scale= 32768 - 32768 *alpha= 32768 - 32768 /n, or equivalently,alpha= 1 -scale/32768 = 1/n, andn= 32768 / (32768 -scale). Essentially,alphais the weight of the new sample, and (1-alpha) the weight of the average thus far.Note that in my implementation,average_magnitudeisroundedtoaverage_magnitude_scaled. This is mostly because the original sample range is signed, and I want zero samples to have the same unit range as all other magnitudes, i.e. ]-0.5,+0.5[ instead of double the others ]-1.0,+1.0[ that it would be if truncating.(To get the scaled average magnitude to converge to the middle of the integer-valued region, we do need to truncate the scaled average magnitude when dividing it down in the average calculation. Fortunately, on Cortex-M, this generates even better code than using pre-computed value in a separate variable, because adding or subtracting a value shifted by some bits is only one instruction. If a separate variable were used, evenaverage_magnitude, it would first have to be loaded into a register, thus only adding more machine code.)The smallestalphathis can represent is 1/32768 = 0.000030517578125, corresponding ton= 32768, whenaverage_scale= 1. Then, convergence from zero to sample magnitude 32768 takes 359605 consecutive samples of value -32768.average_magnitudedoes reach 32768 after 343220 consecutive samples, but it takes an additional 16385 samples for theaverage_magnitude_scaledto converge and no longer change.Note that you cannot choose an arbitrarynoralpha, because you're limited to integer values ofaverage_scale(and therefore integer values of 32768/n). This, the reduced choice in largernor very smallalpha, is the price we pay by converting the integer division to an integer multiplication.

author: bson
author_rank: Unknown
timestamp: February 11, 2025, 10:58:37 pm
content: Note that the low-pass property comes from the addition, not the magnitude adjustment. If you for example add up 256 8-bit values into a 16-bit sum, then this sum represents a low-pass filter. Any magnitude adjustment is purely to avoid overflow, but if you're fine with a 16-bit sum then there's no reason make a magnitude adjustment. It's the summing, not the division.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 12, 2025, 01:56:02 am
content: Yes, the division is not relevant mathematically. At the core, at sample \$y_i\$, the exponential average is$$A_i = y_i \alpha + y_{i-1} \alpha (1 - \alpha) + y_{i-2} \alpha (1 - \alpha)^2 + \dots + y_0 \alpha (1 - \alpha)^i = \alpha \sum_{k=0}^i y_k (1-\alpha)^{i - k}$$and the division is only used so that we can express the above using integer operations, via \$\alpha = (n - 1) / n\$ or \$\alpha = 1 - \operatorname{scale} / 32768\$.Another often used filter ismoving average, the simple average (sum!) overNconsecutive samples. It isn't useful here, because as a low-pass filter, it has very slow roll-off; it is best for removing noise while keeping a sharp step response. Anyway, to calculate this, you need a (circular) buffer to remember the lastNsamples:uint16_t circular_buffer = { 0 };uint_fast16_t circular_buffer_index = 0;uint32_t circular_buffer_sum = 0;When a new sample is added, you subtract the old magnitude from the sum, add the new magnitude to the sum, and store the new magnitude into the buffer:circular_buffer_sum -= circular_buffer;circular_buffer_sum +=value;circular_buffer =value;if (++circular_buffer_index >=N)circular_buffer_index = 0;It's obviously extremely fast to calculate, just one addition and one subtraction per sample –– although you do need to also keepNlast samples in memory.The downside is thatcircular_buffer_sumis scaled byN, i.e. the average iscircular_buffer_sum/N, or(circular_buffer_sum +N/2)/Nif rounded. For power of twoNthis simplifies to a bit shift, but many compile-time constantNcan be calculated by 32×32=high-32-bit multiplication (by reciprocal). (Use aconstexprin C++ and C23 and later, or a preprocessor macro in ANSI C/C99/C11/C17, forN.)The amplitude frequency response for this filter at sample wavelength \$\lambda\$, \$1 \le \lambda \le N/2\$, is$$H(\lambda) = \left\lvert \frac{\sin(\pi \, \lambda)}{N \sin\left(\frac{\pi \, \lambda}{N}\right)} \right\rvert$$or, in relative frequency form, \$0 \lt f \le 0.5\$,$$H(f) = \left\lvert \frac{\sin(\pi \, f \, N)}{N \sin(\pi \, f)} \right\rvert$$You can use myFIR analysistool page to look at the frequency response; just use as many ones as you haveN.

author: radiolistener
author_rank: Super Contributor
timestamp: February 17, 2025, 02:43:20 pm
content: I have a question regarding FIR filters.I'm using FIR LPF filter with normalized kernel coefficients but it produces overshoot peaks for square wave:The input signal is a square wave with range -1...+1. While output signal has 1.18545 overshoot peaks.I tried to test different test waveforms and it produces different overshoot.Is there a reliable way to estimate max possible peak overshoot on the filter output from FIR kernel coefficients?As I understand, it happens due to Gibbs phenomenon because filter removes some components from signal. But how to estimate its worse case?

author: BrianHG
author_rank: Super Contributor
timestamp: February 17, 2025, 03:31:36 pm
content: I can look for one of my old designs where I created an audio 16 bit in, 16 bit out bass and treble filter if anyone is interested. Basically a 1st order 3db filter. It used only 16bit adds and one 16x8 bit input multiplier, 24 bit out. It had 2x8 bit controls, 1 for the series R value in the low pass filter for the treble effect, 1 for the resistor load across the output series capacitor for the high pass filter, or DC filter. As a bonus, it had 6 channel mixer inputs.Though it was written in Verilog, so a little decoding of the language would be needed.Oh, a cheap way to see the response of your filter is to feed it random noise, like a few kb worth and run the output through an FFT. You should see a nice response curve. No filter should give you a flat line so long as your random number generator is truly white.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2025, 05:28:45 pm
content: Quote from: radiolistener on February 17, 2025, 02:43:20 pmSimple. Ask an LLM. You know how to do that since you have posted LLM generated C code elsewhere.Whether you can trust the response is a separate issue. I suppose you could - as some lawyers have done - ask the LLM if its response is correct

author: Simon
author_rank: Unknown
timestamp: February 17, 2025, 06:03:24 pm
content: Quote from: radiolistener on February 17, 2025, 02:43:20 pmI have seen waveforms like that before when I was playing around constructing a square wave from sine waves. My conclusion at the time was that there is no such thing as a true square wave in nature by the rules of Fourier transforms as they have a massive peak on each edge, the more sines you add the higher the peaks but the less the oscillation.The other thing to look at is what is the sampling frequency compared to the signal frequency. I knocked up a spreadsheet to do some filtering experiments, I just put a sine wave with an offset in. When I set the sine frequency to be just off the sampling frequency the output started to go right down to 0 slowly and then came back up despite the main amplitude being 1 with a up to 0.1 sine wave added to it.

author: radiolistener
author_rank: Super Contributor
timestamp: February 17, 2025, 09:17:38 pm
content: Quote from: tggzzz on February 17, 2025, 05:28:45 pmI already did it on different AI models, but they all have no idea how to fix it.Quote from: Simon on February 17, 2025, 06:03:24 pmIn my case, this happens because the original signal is an ideal square wave, and the FIR filter removes some high-frequency harmonics from it. The waveform is correct and acceptable, without these overshoots, the signal would be incorrect. However, the question is how to estimate this overshoot from the FIR coefficients in order to avoid sample out-of-range issues.As I can see, well-known audio converters like sox also struggle with this issue and require manual gain correction before applying the filter.

author: tszaboo
author_rank: Super Contributor
timestamp: February 17, 2025, 09:27:48 pm
content: Quote from: Simon on February 10, 2025, 07:55:05 amI seriously suggest coming up with the filter response first, and programming it second. I've seen it too many times, where this naiive programming method is used, with random coefficients, random length, without thinking what you are actually trying to resolve. New microcontrollers are fast enough to run very long FIR or IIR filters, and they have hardware accelerators and optimized libraries to do it. When you write it yourself, it's not optimal, and it can lead to very nasty things like excessive noise or unstable IIR filters.And then you either don't know why it's doing it, or no way of inspecting it with standard evaluation methods.

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2025, 10:06:25 pm
content: Quote from: tszaboo on February 17, 2025, 09:27:48 pmYes and no.First determine the system's requirements.Second determine whether a conventional analogue filter or its digital equivalent isnecessary, as opposed to being one possibility.If not, consider alternatives that are easy to implement in the digital domain, even if they have no analogue domain equivalent. Then work out whether they aresufficient.Obvious examples are adding random noise where the noise generation mechanism is probably not important, or N-path filters for exceptionally high Q bandpass or bandstop filters or mixers.Thus while I agree that twiddling coefficients is suboptimal, I would ask whether Simon needs to worry about how closely it needs to approximate an RC filter.

author: benseno
author_rank: Regular Contributor
timestamp: February 17, 2025, 10:10:36 pm
content: (i am not an expert in this field)In analog design the overshoots are controlled through choice of filter type, order .... The same or similar means should be available in the digital counterpart:https://www.engineersgarage.com/types-of-filter-responses/

author: tggzzz
author_rank: Super Contributor
timestamp: February 17, 2025, 10:19:56 pm
content: Quote from: Simon on February 17, 2025, 06:03:24 pmAnd that is the rather obvious way for radiolistener to determine the maximum "overshoot".Simple Fourier formulae state that a square wave is the sum of an infinite series of sine waves. Is that reliable "in nature"? Consider that the dual question is whether it is possible to have a single frequency. If the sine wave is not infinite duration, then there will be different frequency components due to it being turned and off.

author: benseno
author_rank: Regular Contributor
timestamp: February 17, 2025, 10:37:42 pm
content: Quote from: tggzzz on February 17, 2025, 10:19:56 pmIn analog circuitry just using RC lpf to filter square wave will not result in any overshoot, aka simple averaging filter in the digital domain.

author: radiogeek381
author_rank: Regular Contributor
timestamp: February 17, 2025, 11:25:11 pm
content: QuoteCan you provide some details?Like1. what was the sample rate vs. the square wave frequency? (Especially, how many samples per cycle of your square wave?)2. How many taps are in the FIR filter?3. Was this implemented as a streaming (time domain, sequential sample) filter?4. If you made multiple calls to the filter routine, did it save any samples between calls?5. How were the FIR coefficients calculated?6. Is it *really* an FIR filter?

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 18, 2025, 01:39:40 am
content: If you want to avoid "overshoots", yes one kind of low-pass filtering that guarantees that is an averaging filter (like moving average or EMA).This all boils down to the step response of the filter.For the basics:https://en.wikipedia.org/wiki/Step_response

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 18, 2025, 02:07:05 am
content: Quote from: radiolistener on February 17, 2025, 02:43:20 pmTo examine the effect of Gibbs' phenomenon, just apply the step function.The definition of output sample \$i\$ with a finite impulse response filter with \$N\$ coefficients \$c_0\$ through \$c_{n-1}\$ applied to input samples \$x\$ is$$y_i = \sum_{k=0}^{N-1} x_{i-k} c_k$$A step function is a single edge of a pulse, being zero for all negative arguments, and exactly 1 for all nonnegative arguments. Thus, when \$m\$ samples of the step function are within the filter window,$$y_i = \sum_{k=0}^{m-1} x_{i-k} c_k = \sum_{k=0}^{m-1} c_k, \quad 0 \le m \le N$$since those \$x\$ are all 1, and all others zero (or corresponding filter coefficient would be zero).The peak in this response is simply \$\max \lvert y_i \rvert\$ for all \$m\$ and \$i\$.If we consider also the Gibbs' phenomenon on trailing edges, it turns out that we want to find out the maximum magnitude of the sum in any consecutive subsequence of the filter coefficients, i.e.$$\max \lvert \sum_{k=i_0}^{i_1-1} c_k \rvert, \quad 0 \le i_0 \lt i_1 \le N$$Thus, if you have \$N\$ coefficients in your FIR filter, you can find the maximum peak by calculating the \$(N+1)N/2\$ sums, and picking the one with the largest magnitude. This is the peak value relative to an edge from 0 to 1 or 1 to 0 you can get.

author: radiolistener
author_rank: Super Contributor
timestamp: February 18, 2025, 08:21:13 am
content: Quote from: radiogeek381 on February 17, 2025, 11:25:11 pmsample rate is 1536 kHz, square wave frequency is 800 Hz. It's about 1920 samples per square wave period.Quote from: radiogeek381 on February 17, 2025, 11:25:11 pmFIR LPF is designed in Octave using b = fir1(N, Wp, kaiser(N + 1, 6.181877));Here is FIR response:Quote from: radiogeek381 on February 17, 2025, 11:25:11 pmit is implemented as streaming filter and has internal buffer to store previous samples.

author: radiolistener
author_rank: Super Contributor
timestamp: February 18, 2025, 08:36:38 am
content: Quote from: Nominal Animal on February 18, 2025, 02:07:05 amThat was my first attempt to estimate it.But when I use classic pulse response [1,0,0,0,...], I get Amax = 0.0285229364172188 which is close to zero and don't exceed -1...+1 range.When I use pulse response for [1,-1,-1,-1,...] it gives Amax = 1.17101652962875, which is more close to 1.18 overshoot value which I get with square wave, but still smaller than it.I also tried to experiment with different square waves on the input and found that overshoot depends on the phase and period of square wave and can be a little bit higher, up to Amax=1.21 or something like that.

author: radiolistener
author_rank: Super Contributor
timestamp: February 18, 2025, 09:55:16 am
content: Here is a small test code in Octave/Matlab which shows issue with signal overshoot on the FIR output:Code:pkg load signal;Fs = 48000*32; % Sample rate Fpass = 22000; % Passband Wp = Fpass / (Fs / 2); % Normalized passbandbeta = 6.181877; % Kaiser window beta (60 dB stopband attenuation, 0.01 dB passband ripple)N = 3040; % FIR order (estimated with ceil((A - 8) / (2.285 * pi * Wt)))kernel = fir1(N, Wp, kaiser(N + 1, beta));% Normalize FIR kernelkernel = kernel / sum(kernel);fprintf('sum(kernel): %g\n', sum(kernel));fprintf('sum(abs(kernel)): %g\n', sum(abs(kernel)));% Show FIR responsefreqz(kernel, 1, 2^16, Fs);N = 2^14; % Signal length%signal = cos(800 * 2*pi*(0:N-1)/Fs); % sine wavesignal = square(800 * 2*pi*(0:N-1)/Fs); % square wave% Apply zero padding to the signal to see details include filter phase delaysignal = [signal zeros(1, length(kernel))];N = length(signal);% Apply FIR filter to the signalresponse = filter(kernel, 1, signal);Amax = max(abs(response));fprintf("Amax = %g\n", Amax);% ===Plot waveform===% X axis in samplestime_min = 0;time_max = N;time_step = (time_max-time_min) / 5; % grid time step (5 divs per figure)if time_step >= 1000000000xscale = 1/1000000000;xunit = 'GigaSample';elseif time_step >= 1000000xscale = 1/1000000;xunit = 'MegaSample';elseif time_step >= 1000xscale = 1/1000;xunit = 'kiloSample';elsexscale = 1;xunit = 'Sample';endxmult = 1;amp_pkpk = range(signal); % Amplitude peak-to-peakscale = 10^(ceil(-log10(amp_pkpk)) + 5); % 5 digits for roundingampl_min = round((min(signal) - amp_pkpk * (sqrt(2) - 1) / 2) * scale) / scale;ampl_max = round((max(signal) + amp_pkpk * (sqrt(2) - 1) / 2) * scale) / scale;figure;plot((0:N-1)*xmult, signal, 'b', 'LineWidth', 2, 'DisplayName', 'Signal');hold on;plot((0:N-1)*xmult, response, 'r', 'LineWidth', 2, 'DisplayName', 'Response');xlabel(xunit);ylabel('Amplitude');ylim([ampl_min ampl_max]);xlim([time_min time_max])grid on;grid minor;xticks = get(gca, 'XTick');xticklabels = arrayfun(@(x) sprintf('%.0f', x*xscale), xticks, 'UniformOutput', false);set(gca, 'XTickLabel', xticklabels);legend('show');title(sprintf('signal: min=%g, max=%g\nresponse: min=%g, max=%g', min(signal), max(signal), min(response), max(response)));

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 18, 2025, 09:59:16 am
content: If you want the maximum response from any input consisting only of ±1, construct one that matches the signs of the input filter in reverse order. Then, the maximum amplitude is the sum of the magnitudes of the filter coefficients,$$\sum_{k=0}^{N-1} \lvert c_k \rvert$$and occurs when the input signs match the corresponding coefficient signs.This has basically nothing to do with Gibbs' phenomena, and everything to do with maximal filter response amplitude, which for FIR filters is the sum of the magnitudes of the filter coefficients.

author: gf
author_rank: Super Contributor
timestamp: February 18, 2025, 10:11:07 am
content: Quote from: radiolistener on February 17, 2025, 02:43:20 pmThe Gibbs Phenomenon basically says that a square wave cannot be represented exactly by its Fourier series, not even with an an infinite number of harmonics. With increasing number of included harmonics, the area between the approximation and the true square wave does indeed converge to zero, but the maximum absolute deviation does not. An overshoot always remains - it just becomes narrower and narrower on the time axis.There is a closed-form solution for the asymptotic limit of the overshoot amplitude, and it is theWilbraham-Gibbs Constant: 1.17897974447216 ...

author: radiolistener
author_rank: Super Contributor
timestamp: February 18, 2025, 10:22:41 am
content: Quote from: Nominal Animal on February 18, 2025, 09:59:16 amCool, it workssum(abs(kernel)): 2.34858But 2.34858 overshoot amplitude looks too much. Is there a way to minimize it during filter design?

author: gf
author_rank: Super Contributor
timestamp: February 18, 2025, 12:14:55 pm
content: Quote from: radiolistener on February 18, 2025, 09:55:16 amI assume that you want the passband extend up to 22kHz (within the desired passband ripple).However, the fir1 parameter Wn is not the end of the passband, but approx. the center of the transition band.I suggest to use kaiserord() to calculate Wn, beta and N, e.g.Code:passband_ripple = 0.001 % 20*log10(1+0.001) = 0.00868 dBstopband_atten = 0.001 % 60 dBFpass = 22000 % end of passbandFstop = 24000 % begin of stopband[n,Wn,beta,ftype] = kaiserord([Fpass Fstop], [1 0], [passband_ripple stopband_atten], Fs)kernel = fir1(n,Wn,ftype,kaiser(n+1,beta));

author: radiolistener
author_rank: Super Contributor
timestamp: February 18, 2025, 03:01:17 pm
content: Quote from: gf on February 18, 2025, 12:14:55 pmI simplified parameters selection for this test code, so it don't include its estimation.Regarding the kaiserord function, I know about it, I was started to design FIR with Kaiser window with using it. But it appears that it is very problematic. I don't remember all details, but I even wanted to open Octave issue about mistakes in this function. As I remember it has many issues, include different results with Matlab and its code don't corresponds with classic filter design using Kaiser window explained in science articles. As I remember there is some mistakes in its code (or may be improvements, but they are missing in original article). So I'm avoid to use it.

author: Tation
author_rank: Regular Contributor
timestamp: February 18, 2025, 09:34:24 pm
content: No matter what "classical" method you use to design this FIR filter (windowed with any window, Parks-McLellan,...) it will show overshot because of the Gibbs phenomenon.But there are other design methods that allow the specification of both frequency and tine domain constraints, being impulse overshot a common constraint. Remember seeing some IEEE papers on that some years ago, but do not know if Octave/Matlab or any other software tool for digital filter design implement any of this methods.

author: tggzzz
author_rank: Super Contributor
timestamp: February 18, 2025, 09:49:59 pm
content: In order to preserve waveform shape, obviously a critical point for an oscilloscope, decent analogue scopes' front ends have a Gaussian response.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2025, 12:29:52 am
content: Quote from: Tation on February 18, 2025, 09:34:24 pmFor instance, averaging will not overshoot by nature, and a simple moving average is actually a particular type of FIR where all coefficients are equal, positive and with a 1/N value (with N the number of coefficients = size of the moving average window). A moving average has characteristics that may not fit a particular use case though, like relatively large side-lobes past Fs / N (where Fs = sampling frequency).

author: Psi
author_rank: Super Contributor
timestamp: February 19, 2025, 12:35:53 am
content: And if you want to get fancy, have a moving average that dynamically changes its depth based on a rate of change calculation.I did that once for some accelerometer stuff and it worked amazingly well. Not saying it was the best choice, but it was easy to understand and didn't need any complex/slow math. It responded fast to big changes but also smoothed out idle noise very well

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2025, 12:43:53 am
content: For preserving signal extrema while still low-pass filtering, I've otherwise used Savitzky-Golay filters. (Note that the moving average is a simple form of Savitzky-Golay filter.)

author: fourfathom
author_rank: Super Contributor
timestamp: February 19, 2025, 12:53:28 am
content: Yes, a FIR does not necessarily have overshoot. I needed to implement a Gaussian-response FIR for a frequency shift keying (8GFSK) design and someone here pointed me to a nice implementation. This one doesn't use the classical FIR with coefficients design, but instead a chained array of 2-stage boxcar filters. Here's a link to my description of it:http://wb6cxc.com/?p=126]http://wb6cxc.com/?p=126] http://wb6cxc.com/?p=126Anyway, it's a FIR without ringing. It uses more delay registers than a classical implementation, but it doesn't need the coefficient storage and multiply stages, and lets me easily adjust the filter length when changing GFSK modes.

author: gf
author_rank: Super Contributor
timestamp: February 19, 2025, 07:52:08 am
content: Quote from: radiolistener on February 18, 2025, 03:01:17 pmBasically it appears to work, but to be honest I have not tested it thoroughly. Based on your experience, it seems I should use it with care, not trusting the results blindly.

author: benseno
author_rank: Regular Contributor
timestamp: February 19, 2025, 11:54:15 am
content: I saw that Gibbs Phenomenon was mentioned. Isn't it true only for cases when signal is converted to the frequency domain and back to the time domain? FIR doesn't do that indeed.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 19, 2025, 11:03:14 pm
content: No. Consider a pulse train of 2π wavelength, first half of each wave at +1 and latter half at -1. Any periodic signal can be represented by an infinite series$$y(t) = a_0 + \sum_{n=0}^{\infty} \biggl( a_n \cos( n t ) + b_n \sin( n t ) \biggr)$$where$$\left\lbrace ~ \begin{aligned}a_0 &= \frac{1}{P} \int_{0}^{P} y(t) \, d t \\a_n &= \frac{2}{P} \int_{0}^{P} y(t) \cos (n t) \, d t, \quad n \ge 1 \\b_n &= \frac{2}{P} \int_{0}^{P} y(t) \sin (n t) \, d t, \quad n \ge 1 \\\end{aligned} \right .$$where \$P\$ is the signal period. If you work out the above, you find that for this signal \$P = 2 \pi\$ and$$y(t) = \frac{4}{\pi} \sum_{k=0}^{\infty} \frac{1}{2 k + 1} \sin\bigl((2 k + 1) t\bigr)$$Essentially, it can be represented as a sine series with only odd \$n\$ of the base frequency have a nonzero amplitude, \$b_n = 4/(\pi n)\$, with all others zero (\$a_0 = a_n = 0\$):$$y(t) = \frac{4}{\pi} \sin(t) + \frac{4}{3 \pi} \sin(2 t) + \frac{4}{5 \pi} \sin(3 t) + \frac{4}{7 \pi} \sin(5 t) + \dots$$In simple terms, each higher frequency component cancels out most of the overshoot of the previous frequencies, moving the overshoot closer to the edge. (In case you wonder, with \$N\$ first terms of the sine series, the peaks occur when \$\sum_{n=0}^{N-1} \cos \bigl((2 n + 1) x\bigr) = 0\$.)This means that any filter with a rectangular frequency response will suffer from Gibbs' Phenomenon.This is because you just chop off theequivalentseries representation at some \$n\$, when you use a rectangular frequency response filter. As shown above, that will result in overshoot (Gibbs' Phenomenon) at maximal slew rate edges. This is physically real, too: you see this in practical analog filters that only remove highest frequencies and do not amplify any frequencies, as well.(Moreover, as the amplitude of the \$n\$'th term of the example pulse train's Fourier transform is \$4/((2 n + 1) \pi)\$, it means the amplitude of the component sine waves droplinearly: if you want to double the precision of your approximation, you need double the number of sine waves! The convergence is annoyingly slow. For real life, it means you need a surprisingly wide spectrum response to reproduce rectangular pulse trains at high fidelity. Or, more simply, that those pesky high-slewrate edges have surprisingly high frequency components in them.)To avoid Gibbs' Phenomenon, you cannot have a rectangular frequency response: you need to attenuate the highest frequencies.

author: gf
author_rank: Super Contributor
timestamp: February 20, 2025, 11:19:45 am
content: Strictly speaking, the Gibbs Phenomenon is concerned with the under/overshoot near the discontinuity points (let me call them "Gibbs points") when adiscontinuousperiodic signal is resynthesised from its Fourier series. It is nicely explained e.g.here.Regarding FIR filter: The samples of a discrete-time signal always represent a bandwidth-limited signal in the continuous-time domain, and a bandwidth-limited signal is never discontinuous. So strictly, I don't see how a discrete-time signal can have any Gibbs points.Nevertheless, similar under/overshoots can, of course, also be observed near smoothed (continuous) edges as long as they are steep enough and if the signal is resynthesised/approximated with truncated Fourier series. But I find it difficult to decide whether we can still call this the Gibbs Phenomenon or not. It's a question of how strictly you interpret the definition. The difference between such edges an true Gibbs points is that the under/overshoot amplitude near smooth edges converges to zero as we include more and more terms of the Fourier series. At true Gibbs points (i.e. discontinuities) the overshoot amplitude does not converge to zero even if we include an infinite number of terms.

author: radiolistener
author_rank: Super Contributor
timestamp: February 20, 2025, 03:38:27 pm
content: Your point about a discrete-time signal always representing a bandwidth-limited continuous-time signal is correct. However, it's important to consider the role of an FIR filter in this context.Although an FIR filter operates on discrete samples, it effectively performs convolution with a discrete-time impulse response, which can be interpreted as processing a continuous-time signal. This means that the output samples of the FIR filter no longer correspond to the aperture of the original discrete-time signal but rather to a newly sampled version of the underlying continuous-time representation.Thus, while a properly band-limited signal does not inherently exhibit Gibbs points, FIR filtering can introduce effects that resemble them, particularly when dealing with signals with sharp edges or when using truncated filter kernels.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 21, 2025, 12:34:26 am
content: Quote from: gf on February 20, 2025, 11:19:45 amYou may be missing one point when it comes to FIR depending on how you design them. The impulse response truncation method is bound to exhibit the Gibbs phenomenon:https://www.gaussianwaves.com/2010/04/gibbs-phenomena-a-demonstration/

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 21, 2025, 12:59:12 am
content: Quote from: gf on February 20, 2025, 11:19:45 amYes; it is just the overshoot width or duration, not amplitude, that tends to zero.However, we don't have a better name for this Gibbs-like phenomenon; it's not exactlyringingor plainovershooteither.Quote from: SiliconWizard on February 21, 2025, 12:34:26 amThis is exactly what I tried to say in #37, using way too many words.

author: benseno
author_rank: Regular Contributor
timestamp: February 21, 2025, 12:14:46 pm
content: Will the Gibbs phenomenon be appeared in the result of backward Fourier transform of a discontinuous signal (in the frequency domain) after frequency domain equivalent of smoothing window filter is applied to it?

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 21, 2025, 02:01:42 pm
content: Time domain and frequency domain are just two different ways of describing the same thing, they're not separate things.If you take any full-spectrum signal with sharp edges, then just remove all the highest frequencies, you will see the same kind of overshoot as in Gibbs phenomenon. This applies to both the math, and to physical signals.

author: benseno
author_rank: Regular Contributor
timestamp: February 21, 2025, 03:46:50 pm
content: Seems i couldn't formulate my question properly (thanks to my bad English).Let assume 2 methods of filtering:(a) directly by FIR filter(b) forward Fourier transform is done to discontinuous square wave signal, then equivalent of FIR filter in the frequency domain is applied, and last step - backward Fourier transform is done to rebuilt signal on the time domain.The question is - will the Gibbs phenomenon occur in method (b) if (a) is not resulted in it?Thanks

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 21, 2025, 11:48:56 pm
content: Quote from: benseno on February 21, 2025, 03:46:50 pmMethod (b) will give you similar issues if you use a rectangular window.To simplify the concept, in the case of a FIR filter (designed with the impulse response truncation), the discontinuity can be found in the step function.Using convolution (direct Fourier transform, weights in the frequency domain, inverse Fourier transform), the discontinuity will be found in the window if it's rectangular.So method (b) requires using appropriate windows.Note that unless you need to filter only a relatively short buffer (and you don't need to do it in real time), using a single direct and inverse FT on the whole signal becomes unpractical and the usual way of doing it is the overlap-add method (and in particular the WOLA), in which you process a sequence of overlapping sections of signal:https://www.dsprelated.com/freebooks/sasp/Overlap_Add_OLA_STFT_Processing.html

author: gf
author_rank: Super Contributor
timestamp: February 22, 2025, 01:08:06 pm
content: Quote from: benseno on February 21, 2025, 03:46:50 pmNo, if the resulting signal (after convolution with the FIR kernel) has no discontinuities.Yes, if the filtered signal still has discontinuities (but the width/duration of the "Gibbs ears" near the discontinuity points is zero). Discontinuities in the filtered signal can occur, for example, when a high-pass filter retains the edges of a square wave.Of course, I'm referring to acontinuous-timeconvolution and a continuous-time/continuous-frequency Fourier transform and inverse Fourier transform.Your question does not apply to the discrete-time domain, since an ideal square wave is not bandwidth-limited and cannot be represented in the discrete-time domain. If you sample a square wave "as is", then you suffer from aliasing and a unique exact reconstruction from the samples is not possible. And if you apply a (continuous-time) anti-aliasing filter to the square wave before sampling, then you are no longer sampling a square wave, and the discrete-time FIR filter is not applied to a square wave either.

author: gf
author_rank: Super Contributor
timestamp: February 22, 2025, 03:32:50 pm
content: Quote from: SiliconWizard on February 21, 2025, 11:48:56 pmIn order to do a simple FFT convolution with overlap-add1), you must not use a synthesis window (WOLA), and a specific (non-rectangular) analysis window function is not required either. Only if time-dependent filtering or non-linear modifications in the frequency domain are desired, then an appropriate window function and/or WOLA are a good idea.1)which is mathematically equivalent to a linear convolution of a (constant) FIR kernel with a (possibly infinite) stream of samples[ By the way, with the STFT we are of course no longer working in the pure time domain or in the pure frequency domain, but in the time-frequency domain (i.e. we don't have a single spectrum for the whole infinite signal, but we are working with a sequence of time-dependent short-time spectra). ]

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 24, 2025, 10:31:16 am
content: Just for fun, I created a CC0-1.0/Public Domain tool page,here, that works even if copied to your own computer, with no internet access), to experiment with the frequency spectrum needed to represent pulse trains, and the "Gibbsy"-like overshoot issues related to filters constructed using frequency response truncation.The base pulse train is described by the infinite Fourier series with \$b_n = 4/(\pi\,n)\$ for odd \$n \ge 1\$, \$b_n = 0\$ for even \$n\$, and \$a_n = 0\$ for all \$n\$. You can supply different constants in the input boxes, one per line, and when you tab away or click on theCalculateat top, it'll redraw the signal represented by the Fourier series coefficients.At the background, you'll see a digital pulse with period 2.If you usebn=4/(n*PI) 0 4/(n*PI) 0 4/(n*PI) 0 4/(n*PI) 0 4/(n*PI),an= 0you can see what the background pulse would look like, when filtered by a low-pass filter that passes all frequencies at and below nine times the pulse frequency as-is, but blocks all frequencies at and above eleven times the pulse frequency completely. The overshoot is the maximum, and pretty horrible.The default3.9/(n*PI) 0 3.4/(n*PI) 0 2.4/(n*PI) 0 1.5/(n*PI) 0 0.5/(n*PI)shows the effect of a theoretical filter that only affects amplitude, causes no phase shift, attenuating the pulse frequency by 1-3.9/4.0=2.5%, by 15% at triple the frequency, by 40% at five times the frequency, by 62.5% at seven times the frequency, by 87.5% at nine time the frequency, and blocks all frequencies above eleven times the pulse frequency. The slew rate isn't high, but there is very little overshoot.The idea here is to show that even if you had a perfect filter, to avoid overshoot you need to attenuate the higher frequencies at or above your signal frequency: you cannot have a steep stopband and no attenuation below it. You could limit the slew rate instead –– corresponding to a filter whose frequency response varies depending on the signal. And, that something like 10× the signal base frequency seems sufficient width for the attenuation band to recreate the original digital pulses (with very high input slew rate) with little to no overshoot, as an initial guess based on the default example. I think you can construct a filter with narrower attenuation band by allowing phase distortion, but didn't delve into this that deep.Finally, that devices with slew-rate limited outputs don't necessarily need more than say 10× the original digital signal frequency to be reproduced well, as shown by the frequency-limited default synthesized pulse shows.

author: hamster_nz
author_rank: Super Contributor
timestamp: February 26, 2025, 02:22:32 am
content: "The idea here is to show that even if you had a perfect filter, to avoid overshoot you need to attenuate the higher frequencies..."Perhaps more concisely if you have a square wave (of any frequency) the range of fundamental will always overshoot the range of the square wave. Where the overshoots are, and by how much, depends what higher harmonics you filter, and the overall gain.The 3rd harmonic pushes the overshoot to the towards the edges, the 5th harmonic does the same again. Eventually the overshoot ends up only at the edges.You either have to have a gain less than 1 at your frequency of interest, or allow for overshoot.(Graphic snipped from Wikipedia below)

author: gf
author_rank: Super Contributor
timestamp: February 26, 2025, 08:19:49 am
content: Quote from: hamster_nz on February 26, 2025, 02:22:32 amIt is also my subjective observation that all low pass filters that do not overshoot seem to have a frequency response that starts to roll off at DC, i.e. gain(f) < 1 for f > 0. Even a flat frequency response with a soft roll-off beyond the flat top (e.g. Butterworth) is not possible without overshoot in the step response.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 26, 2025, 11:23:09 am
content: Quote from: gf on February 26, 2025, 08:19:49 amDon't you mean \$\text{gain}(f_1) > \text{gain}(f_2)\$ for all frequencies \$0 \lt f_1 \lt f_2\$? At least, this is how I understand the no-overshoot requirement. The actual maximum gain doesn't matter, only that it has to lower as frequency increases.I do not have an expression for the minimum necessary drop in gain, though, because I do not know all the ways one can avoid the overshoot. I've only shown one example of when it is more or less achieved.When the pulse width and interval has a minimum, like in pulse density modulation limited to some middle duty range –– this is used to push the quantisation noise up in frequency, making filtering the PDM signal to an analog one much easier; for example, limiting to 16..240 in 8-bit PDM means you'll always have an edge within any sixteen consecutive cycles –– then I do believe the gain below that fundamental frequency \$f_0\$ can be flat without causing overshoot; i.e. \$f_0 \lt f_1 \lt f_2\$ in such specific circumstances, but generally \$f_0 = 0\$.It's just a belief, though; I haven't verified it mathematically. It could be that \$f_0 = 0\$ for the absolutely-no-overshoot case, because PDM patterns can always hold lower frequency content than the frequency corresponding to the minimum interval between signal edges –– that's what it's used for, after all. I do not know if that is sufficient to cause overshoot (at least more than in statistically insignificant corner cases).

author: gf
author_rank: Super Contributor
timestamp: February 26, 2025, 12:58:12 pm
content: Quote from: Nominal Animal on February 26, 2025, 11:23:09 amThis is definitively not a requirement. The FIR in the attached image is all-positive, so its integral (step response) won't overshoot. Still the corresponding frequency response has "ripples", i.e. your condition is not met. [ Figure2 shows, of course, only a finite section of the infinte frequency axis. ]

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 26, 2025, 01:24:16 pm
content: Right, thanks. Makes specifying it precisely quite hard; possibly something along the lines of "The maximum gain in any lower frequency band with at least one local gain maxima must be greater than the maximum gain in any higher frequency band with at least one local gain maxima, or overshoot will occur with digital inputs with fundamental frequency in the lower frequency band and harmonics in the higher frequency band."

author: paulca
author_rank: Super Contributor
timestamp: March 27, 2025, 10:37:48 am
content: Oddly asides... I was looking into doing some rudimentary filtering in assembler yesterday. Well, me not being the mathematician type, I called it "Rolling averages" or "Windowed means".Take a value which you can read every time you are executed, then write it to a peripheral register to display and return.Now that value is hard to read as it just jumps around all over the place. 32.4 29.2 24.5 38.2 ... to the human eye it's nearly meaningless updating every 0.5 seconds.So what is the simpliest way to address this I wondered?If I take the "running average", add the next value to it and divide the result by two, it's still an "average" right? Don't be mean about "mean" now!A quick thought experiment makes you think it's not though.10, 20, 30 avg 20However,10, 20 avg 1515, 30 avg 22.5Yet looking more closely 22.5 is a valid value for a "window size" or "set" of 2. (EDIT: I know it's not now... now I do) The value "20" is for a window of 3. Both are correct in there own way.The thing is though. (15+30) / 2. The "15" is the output of the previous averaging. Therefore this is not a finite response "averaging function" at all, it's a decay function. A single datum pulse will result in an exponential decay response.I'm sure there are correct mathematical terms, but it's just amazing how much of a can of worms TWO single ASM instructions can get you into.Anyway. For it's purpose, to smooth out a "jumping" datum so it makes sense to a human and will allow them to spot trends.... for that I need to spend some memory and some iteration and use a proper rolling window and average it. Not with 2 values, but with a dozen maybe.Other janks not becoming of someone who did any maths study.... If I don't display the value itself, but it's mid point between itself and it's previous, that will give me an intregal? Where it will approach the real value faster if its changing quickly, but slower if its not. But never reaching the actual value. "If the sales man travels half the distance to the destination each day, how long does it take?" (EDIT: It's the same thing as avg+current/2.)