High order DSP filters in assembler on normal micro  - Page 1
Forum section: electronics / general

author: Circlotron
author_rank: Super Contributor
timestamp: November 23, 2020, 12:13:32 pm
content: A little project that I am playing with writes A/D samples to 32 consecutive memory locations in a loop, and after every write it adds them all up then does five right shifts to divide by 32 to get a running average. Classical boxcar averaging. No big deal. But it’s only a second order filter. Is there a simple way to get higher orders using this same basic method? I want to keep away from dedicated DSP chips and just see what I can do with a 9S08PA16VL.

author: oPossum
author_rank: Super Contributor
timestamp: November 23, 2020, 12:24:14 pm
content: The simplest way I know of to get a proper higher order response at an arbitrary frequency would be a cascade of biquad filters.https://en.wikipedia.org/wiki/Digital_biquad_filter

author: Siwastaja
author_rank: Super Contributor
timestamp: November 23, 2020, 12:48:14 pm
content: What's wrong with classic approach doing a FIR or an IIR filter? Design the filter in Matlab or Octave (free), a lot of easy-to-follow tutorials available. Also javascript/java based web designers which output the coefficients, if you don't want to install Octave or buy Matlab. Then just implement it, this is just a series of multiplications and additions for each sample. You can get very high filter orders and you know exactly the amplitude and phase response from the design software. By choosing number of coeffs and adjusting design parameters, you can balance between performance, steepness and stop-band attenuation of the filter."9S08PA16VL" gives 0 results in Google, this must be a very exotic chip? Is it a 8-bit, 16-bit, 32-bit?What samplerate?

author: Fungus
author_rank: Super Contributor
timestamp: November 23, 2020, 01:52:57 pm
content: Quote from: Circlotron on November 23, 2020, 12:13:32 pmIf you have a multiply instruction then a FIR filter is really easy. I've done it on an 8-bit Arduino.

author: snarkysparky
author_rank: Frequent Contributor
timestamp: November 23, 2020, 02:05:22 pm
content: You will have to add 32 multiplies to improve the moving average with a FIR filter.there are many online FIR filter designers. As for Octave their help page for the 'butter' command. ( Funny name but it generates a butterworth filter )https://octave.sourceforge.io/signal/function/butter.htmlsf = 800; sf2 = sf/2; % this sets the cutoff frequencydata=[[1;zeros(sf-1,1)],sinetone(25,sf,1,1),sinetone(50,sf,1,1),sinetone(100,sf,1,1)]; % generates test signal[b,a]=butter ( 1, 50 / sf2 ); % b and a are coefficient vectors for the filterfiltered = filter(b,a,data); % filter output of the test data.reallly simple and worth trying out.

author: newbrain
author_rank: Super Contributor
timestamp: November 23, 2020, 02:21:34 pm
content: Quote from: Siwastaja on November 23, 2020, 12:48:14 pmDon't know aboutexotic, it's an HCS08 version.It only has 8 × 8 = 16 unsigned multiply, if I am correct, so any DSP algorithm will perforce be limited in sampling rate and precision.

author: tszaboo
author_rank: Super Contributor
timestamp: November 23, 2020, 03:17:14 pm
content: I suggest doing the filter design forwards, instead of backwards. Design the digital filter properly, with some software, set up the bandwidth the coeffs and simulate it. And only worry about the number of samples and -shift instead of divide- calculations later.Maybe you dont even need 32 numbers, maybe you only need 5. Dont approach this as a programmer, do it as an EE or a mathematician. Dont worry about speed and performance in the beginning. And dont forget that with these 8 bitters whenever you calculate, the noise from those calculations can really upset any filters.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 23, 2020, 08:51:33 pm
content: About the only useful integer-coefficient (and also happening to be powers of 2) FIR filter, is the boxcar, and the 1-2-2-1. I haven't searched a lot of coefficients in coming to this conclusion but it does seem unlikely that longer filters will have any more powers of 2, and among integer coefficients you don't get much of a shortcut (known constants can be synthesized from a static combination of shifts and adds, at least).IIR filters have much wider range (useful at lower frequencies) than FIR filters for the same order. Guessing you have little choice but to do that, if such filtering is necessary.The next best alternative to that is a decimating filter, where you reduce the sample rate, making the FIR more tractable at the low frequency; the downside is the decimation itself, which needs some filtering process to avoid aliasing. An FIR filter there would be redundant*, so it should be IIR of some sort,*Except for the boxcar CIC form, i.e., add the new sample, put it into a circular buffer, take the oldest sample out of the buffer and subtract it. This still requires O(N) memory, but O(1) arithmetic.After that, general CIC filters may be attractive. I haven't designed with these much, I'm not sure that they're as general as, say, IIR biquad, or high-order FIR. But they're easy on CPU cycles.Does that CPU have a hardware multiply, or no? (Ed: yes.) That's really the important part. If all you have is shifts and adds, and if they're slow at that (8 bit machine? not single cycle instructions?) you're pretty much screwed, in terms of anything of modest sample rate (e.g. audio).I did a reverb project on XMEGA, which is only very slightly more powerful than your bog standard ATmega328P for example. 12 bit ADC and DAC, 25kSps. The quality is just good enough to be usable -- if it were any less it would be just too obnoxiously noisy. Using external RAM (GPIO driven), I got 2 mixer channels, 6 reverb taps (simple delay type, not the better allpass type) and 2 biquad filters, with just a few CPU cycles to spare (32MHz). 16 bit signed (fixed point) representation for the most part.The breakdown is something like, a RAM cycle takes about as much time as a reverb tap or mixer channel ((8 * 16) >> 8 = 16 MAC operation*), and a biquad (six 16*16 = 32 MACs) takes about as much time as... uh what was it, four or six reverb taps? And all of that divides into the just-shy-of-40us duration available.*That is, the operands are 8 and 16 bits, the result is shifted right 8 (effectively, the 8-bit operand is 0.8 fixed point) and the result is stored in a 16-bit accumulator. MAC = Multiply ACcumulate.Tim

author: Circlotron
author_rank: Super Contributor
timestamp: November 23, 2020, 10:46:27 pm
content: Quote from: newbrain on November 23, 2020, 02:21:34 pmSorry about the part number. The more generic 9S08PA16 gets 4260 hits. Just a packaging and number of pins thing,

author: Circlotron
author_rank: Super Contributor
timestamp: November 23, 2020, 10:50:33 pm
content: Quote from: NANDBlog on November 23, 2020, 03:17:14 pmOh yeah... on my scope I can make a third trace a function of the other two, and seeing they are both 8-bit resolution the third trace can have pretty ordinary resolution sometimes.

author: Circlotron
author_rank: Super Contributor
timestamp: November 23, 2020, 10:58:57 pm
content: I'll just have to start reading some more about DSP I think. I only want to do about 100 samples / sec and get an 8-bit result. I don't even need a high order filter really, I just want to know how to do it. I know about sampling and Nyquist but just how to make taps to apply coefficients to is a mystery. The trouble is, every explanation I've seen so far explains it in terms I am not familiar with. I need an assembler listing, not letters that have fallen on their side, squiggles, and inscriptions that belong on the side of a UFO.Nothing like a whole new discipline to put you back in your place.

author: langwadt
author_rank: Super Contributor
timestamp: November 23, 2020, 11:20:46 pm
content: Quote from: T3sl4co1l on November 23, 2020, 08:51:33 pmthe combination of fir and decimation has the advantage that you only need to calculate the outputs you actually use, with IIR you need to calculate all the outputsanother special case is the Half-band filter, the coefficients are symmetric about the center so you only need half a many multiplies (with some extra adds)a nd every other coefficient is zero so you can skip those

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 24, 2020, 12:19:26 am
content: If you don't mind that it's not in HC08?https://github.com/T3sl4co1l/Reverb/blob/master/asmdsp.SRelevant parts, in order of complexity?dspHighpass: this is a simple, very low cutoff (like 10Hz) single pole IIR filter. Very simple, easy to understand: the gain factor means previous signals gradually bleed away (weighted towards zero, in a geometric series -- hence exponential decay). Note "hp" is 16.16 fixed point: the top half (the "DC" bias) is subtracted from the sample, then a (in this case very small) gain factor is applied, and accumulated into "hp". This is stored back into RAM for next time. The #if...#else is just an optimization to take advantage of an 8-bit-sized coefficient. (In both cases, the MAC operation is far more compact than avr-gcc's verbose library-driven solution.)These are signed operations by the way, for the most part. It may be helpful to note that AVR MUL instructions return their result in r0:r1, and also return a carry bit depending on operand signs so that sign extension can be done instantly with a subsequent carry chain.See also dspMix at the bottom: a 8x16 MAC operation, setting the gain of "clean" and "reverb" channels. Another quirk: because reverb is by feedback only, it's always at least 50% "clean". These settings are signed, so you can cancel out the direct term without messing up the reverb loop, and make it very "dirty" if you like. Or just plain old delayed, if you set a single tap.More advanced, dspReverbTaps:Specifically, the .LdspRT_loop section. (".L" is a local label in GCC / GAS.)Two functions are inlined here, which appear earlier (either unused or commented out), to remove call overhead and register shuffling (GCC's ABI isn't very convenient here; instead, we can set up all the pointers and just crank away). This inlines all the GPIO register access; the port functions are descriptively named. (The external RAM is faster than the CPU-GPIO propagation, so a few NOPs are needed. A loop INC is also inlined here (see lines 445, 475, 494). RAM access is two bytes at a time, effectively a 32kWORD memory space, giving a maximum delay of 1.31 seconds.)The sample retrieved from RAM, is then multiplied by the tap coefficient (loaded from  aka [r30:r31], which was also inlined at (479)). The 8 * 16 multiply should be familiar from above.(Below the loop, a shift adjusts the 16.8-bit fixed point accumulator once to account for the signed operands (gains are -128 to +127), with a check for saturating arithmetic, then an implicit byte shift for output, truncating the fractional byte.)Advanced: dspBiquadFilter:This is a bit of juggling to get everything into a useful form. dsp.c gives a clearer high-level view. The canonical form is a bunch of coefficients, three source samples and two output samples. I've moved that around for easier access, so that the core loop can just let it rip over five consecutive pairs of coefficients and samples (no matter where they came from, source or past output!). This is done at 16x16 = 32 bit precision to stave off rounding errors; in practice, I can set a pretty narrow bandpass filter and it's still stable. (Or, I'm not actually sure where a biquad filter is least stable; if it's more in terms of proximity to Fs/2 rather than bandwidth, maybe I just haven't checked the right end?) This also causes some adjustments in magnitude, so the coefficients are a bit non-traditional, and there are additional shifts at the end to make up the difference.In any case, you can see the core loop .LdspBQ_for: is, even more compact than the tap loop; it's just that there's five passes, so it takes a fair chunk of time to compute. (The outer loop is over the two selectable filter taps.)The most important operation isconvolution.This has many general applications in signals, and mathematical analysis, but the discrete-time, finite window implementation is simple:Take two arrays, and an accumulator (initialized to zero every time step).For every time step:Input a new sample to the one array, and discard the oldest sample (circular buffer or queue).Take the product of each pair of elements a and b in the arrays.Add this product to the accumulator.Multiply the accumulator by an output coefficient, and output it.Congratulations, you have an FIR filter. If a is a list of past input samples, then the coefficients b are the filter coefficients, effectively setting the gains on a very dense array of reverb taps. By setting these judiciously, any desired frequency response can be had.(And for closure with signals, we can treat the input and output series as functions of time, and express the operation for a general time step n, instead of iterating through the process. In that case, the array a is a partial sequence of the total input sequence. Or for continuous-time, the functions are also continuous, and a very similar integral (in the sense of the equivalence between Riemann summation and integration) is used.)The core operation is also the dot product from linear algebra: treat the arrays as vectors of numbers, and take the sum of pairwise products. It should be no surprise at all that DSP processors have been used for graphics accelerators time and time again (3D geometry calculations involve lots of dot, cross and matrix products); or that "multimedia extensions" are really just short* vectorized operations (SIMD).*Relatively speaking; the original MMX was two and four elements wide, IIRC. Nowadays with AVX512 and friends, and likely even bigger ones on the horizon -- the amount of numerical power available in single instructions is immense.Back to signals and DSP, the frequency response is given by the Z-transform, the discrete-time equivalent of the Fourier transform. Indeed, the equivalence is so direct, there exists a fairly simple conformal mapping between them.We can also record output states into a queue c, and produce the general sum:c = c[n-1] * d + c[n-2] * d + ...+ a * b + a[n-1] * b + ...where the array d is the set of coefficients of feedback terms. (Note I'm numbering arrays in counter-flow order, i.e. n-minus-something is a prior input or output, while the coefficients are just numbered in order, 0, 1, 2, ... They might end up this way in memory, or both along incrementing addresses; whatever works.)Obviously(?), the feedback system is harder to work with -- if you just sit and have a play with this, you'll find that stable coefficients, for some given frequency response, don't make much sense, or start to look random rather quickly, especially for high-order filters. You might not even be able to find stable coefficients at all, depending on numerical precision!The difference is, whereas a continuous-time filter must have its poles on the left half-plane, the discrete-time filter must have its poles* within the unit circle. (The LHP is mapped to the circle interior, RHP exterior, and the vertical axis, the circle itself. Crazy, huh?) Well, solving for poles in very specific locations, tends to need very precise and random-looking coefficients, so that's why these filters tend to need high precision, especially in high orders.*Yup, the roots of the auxiliary polynomial, given by powers of z times the gain coefficients -- same as the coefficients on polynomials in terms of omega.Also, feedback means that a stable response has an impulse response inverse-exponential with time. Hence, infinite impulse response (IIR).So, because high order IIR filters are very easily unstable, we tend not to use them, and stick to cascades of 2nd order filters -- just like active op-amp filters, where we get 2 (sometimes 3) poles per stage, and just stack enough, of whatever Q factor and all that, is needed to get the final overall response.Tim

author: Circlotron
author_rank: Super Contributor
timestamp: November 24, 2020, 12:55:09 am
content: Quote from: T3sl4co1l on November 24, 2020, 12:19:26 amAt this point, this is what I think I need to know. Now I know what the terms convolution and especially FIR mean I'll use that as a starting point. Thanks Tim.

author: Circlotron
author_rank: Super Contributor
timestamp: November 24, 2020, 02:19:00 am
content: It's just finished lunchtime at work here right now and I just got back from a walk around the block and I had an epiphany while I was out... In the scheme I first mentioned I would take samples and store them sequentially in a 32 byte circular buffer, and after each store I would add them all up, divide the sum by 32 and there's the filtered result. Now it occurred to me, after writing to that circular buffer, each stored sample gets multiplied by a specific value and the product is then stored in a "parallel" circular buffer and these instead get summed and divided by 32. Is that what a MAC is? And I presume the multiplier values are the tap coefficients?So if the coefficient varies depending on the age of the sample, do you have to MAC the entire list every sample time?All these terms I've read about for years and years finally started to make sense!

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 24, 2020, 03:15:29 am
content: The MAC is the single operation:a = a + b*cSo summing an array is:Code:for (i = 0; i < n; i++) {a = MAC(a, b, c);}And again, you can apply a differentiation-integration (well, difference and summation) trick to the boxcar, so you only need 2 MAC for N taps. (It's one of the worst FIR filters, but it's useful for some reason.)Tim

author: Siwastaja
author_rank: Super Contributor
timestamp: November 24, 2020, 10:41:56 am
content: With 100 samples per second, you can do FIR or IIR with even 64-bit fixed point datatypes on a 8-bit micro just fine! This is the most robust and easiest answer. Think something else only if youactuallyhave a performance issue. Premature optimization eats time.Even a software float might work, but that's a bit iffy on a 8-bitter.

author: newbrain
author_rank: Super Contributor
timestamp: November 24, 2020, 11:26:53 am
content: What Siwastaja said!100 S/s should be well within the capabilities of a 20 MHz 8 bit micro, and writing your own IIR/FIR is a priceless learning experience.Unfortunately, progressing much further without resorting to alien scribbles () it's not that easy, the theoretical foundations need some getting used to the relevant maths notations and concepts*.There are many books on DSP, I often go toLyons' Understanding Digital Signal Processing.Not a new text, but the fundamentals are well described and it has covered my needs till now.It has an introductory section, where the various notation are explained - Though I cannot judge on its understandability, it's concise and correct.If you are hesitant to shell out the steep price for one of these books, I am sure that some more or less shady corner of the internet can provide you with a sampler...*A personal tidbit: Writing some SDR SW (for an iMXRT1020) together with a friend (with good RF and programming skills!), I had sometimes to go to lengthy explanation on why I was handling I&Q components as a single complex number (with all the simplification in notation,and code, that comes from that), especially when using FFTs or, e.g. "rotating" the signal spectrum with a single complex multiplication.

author: Circlotron
author_rank: Super Contributor
timestamp: November 24, 2020, 11:31:11 am
content: Been reading up a bit. A FIR filter looks like it most often wants an odd number of taps but it would be easier to have an even 2^n taps to make it easy dividing the sum after addition. That's a bit pesky.Also, why are some of the coefficients negative? Is there a simple answer?

author: snarkysparky
author_rank: Frequent Contributor
timestamp: November 24, 2020, 01:06:09 pm
content: This looks like good infohttp://ww1.microchip.com/downloads/en/AppNotes/doc3713.pdf

author: langwadt
author_rank: Super Contributor
timestamp: November 24, 2020, 02:25:29 pm
content: Quote from: Circlotron on November 24, 2020, 11:31:11 amnormally you wouldn't have to divide anything*, each of the taps are multiplied with a coefficient and the sums is the resultand if you had to you'd use a multiply and shift because divides are slow*except maybe a shift to account for your number formatQuote from: Circlotron on November 24, 2020, 11:31:11 amthe coefficient are the impulse response, the Fourier Transform of the impulse response is the frequency response, that's just the way it is

author: snarkysparky
author_rank: Frequent Contributor
timestamp: November 24, 2020, 02:35:10 pm
content: Circlotron,For an FIR filter the data in the buffer is multiplied by each corresponding coefficient and the results summed.The properly scaled output is to divide the accumulated value by the sum of the FIR coefficients. This makes it so that if all the data is 1.00 and the sum is divided by the sum of the FIR coefficients the result is 1.00The coefficients for a FIR filter can be derived by taking the inverse discrete transform of the desired window shape ( has to be mirrored about the middle sample ).As for why some coef are negative. The best I can explain is that the inverse DFT can produce negative return values.https://www.allaboutcircuits.com/technical-articles/design-of-fir-filters-using-frequency-sampling-method/

author: Siwastaja
author_rank: Super Contributor
timestamp: November 24, 2020, 03:00:38 pm
content: FIR is almost too simple to implement. With high sample rate and high number of taps, there is a lot of operations, but this isn't a problem for you I'd say.To generate one output sample, multiple n latest samples with the array of coeffs. Sum all multiplication results. This is the output sample.Example:Input sequence: 1,2,3,4,5,6taps = 3coeffs = a,b,cOutput sequence:1*a+2*b+3*c2*a+3*b+4*c3*a+4*b+5*c4*a+5*b+6*c...There is nothing to "divide". The coefficients are scaled (by the design tool like Octave/Matlab) so the output amplitude is correct.IIR also uses the calculated outputs as inputs for another set of coeffs. Look up the block diagram on Wikipedia. More to calculate per tap, and more numerical accuracy needed, but the number of taps for a steep filter ismuchless.The only complication is how to implement the fixed-point multiplication. In most usual filters, the coeffs range from slightly negative (say -0.1) to around +1.0.For example, you can use int32_t or int64_t so that half of its numerical range corresponds to 1.0. If your inputs (coeff and input signal) are int32_t, you cast them to int64_t, then do int64 multiplication, the result will be int64. For example, if 1073741824 (half of the numerical range of int32_t) corresponds to 1.0, then 1.0*1.0 => 1073741824 * 1073741824 = 1152921504606846976, which is again half of the numerical range of int64_t, and would correspond to 1.0; and if you want to convert it back to the 32-bit version, just ignore 32 least significant bits and you are back to 1073741824. Generating the coeffs is then just an operation of outputting the coeff vector from Matlab/whatever multiplying each coeff by 1073741824.

author: langwadt
author_rank: Super Contributor
timestamp: November 24, 2020, 03:24:21 pm
content: Quote from: Siwastaja on November 24, 2020, 03:00:38 pmsorta kinda,https://en.wikipedia.org/wiki/Q_(number_format)

author: newbrain
author_rank: Super Contributor
timestamp: November 24, 2020, 03:31:21 pm
content: Quote from: Siwastaja on November 24, 2020, 03:00:38 pmQuote from: langwadt on November 24, 2020, 02:25:29 pmThese two statements go together:Most often the filters are low-pass or high-pass or band-passSo, they resemble a rectangular shape in the frequency domainIn very general terms, the Fourier transform of a rectangular shape is a sin(x)/x function (commonly called sinc)A sinc function is bound between a maximum of 1 and a minimum of about -0.2Hence the normal range of filter coefficients...

author: ejeffrey
author_rank: Super Contributor
timestamp: November 25, 2020, 12:26:45 am
content: Quote from: Circlotron on November 24, 2020, 11:31:11 amNo. For unity DC gain you want the sum of the tap coefficients to be 1. Assuming you are using integer coefficients with a trivial shift at the end, then the coefficients should sum to 2^N --but you can have an odd number of taps that still meet this criteria. For example the filter [1/4, 1/2, 1/4] could be implemented with multiplication coefficients [1, 2, 1] and a 2 bit shift.QuoteThe short answer is that the FIR window function is the impulse response which in turn is the Fourier transform of the frequency response. Lowpass filters with sharp transitions if frequency space will tend to have ripples in the impulse response because that is the way Fourier transforms work. Other types of filters such as highpass and bandpass will always have negative coefficients since the sum of the coefficients is zero.

author: Circlotron
author_rank: Super Contributor
timestamp: November 26, 2020, 02:43:21 am
content: Okay, several questions:1/ Seeing the coefficients are floating point values less than 1, I'll have to bump them up to some integer value so I can do integer arithmetic on them. Is it okay to normalise the highest value coefficient to 255 and scale the others accordingly?2/ And a somewhat overlapping question - One set of coefficients I was looking at had a maximum value of 0.4 and the others all lower. What difference would it make if that largest coefficient was 1 and the others scaled accordingly?3/ My micro has an 8x8unsignedmultiply. How do I deal with negative coefficients?

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 26, 2020, 05:25:05 am
content: 1. Yes, or use a floating point library; but fixed point is easy enough.2. Overall gain, of course! (Assuming FIR filter. IIR filter will go unstable of course.) This may overflow the accumulator, which you'll want to check after every sum anyway so you can implement saturating arithmetic. (Overflow is... an especially obnoxious kind of distortion!)3. Don't worry about it, let the compiler handle it. You have more than enough time to spend on poorly optimized code.Hrm, checking... HCS08 doesn't have a compiler? How quaint...Then, depends on the instruction set, Idunno. Like I said, on the AVR I'm familiar with, MULS returns a carry. If you don't have that, at worst you have to extend the input sign bits, XOR, and carry afterward. Maybe PITA enough to do it longhand anyway?HCS08 can't possibly be so quaint that there aren't basic arithmetic libraries or references out there...Tim

author: Circlotron
author_rank: Super Contributor
timestamp: November 26, 2020, 05:52:34 am
content: Also I was looking at various online FIR coefficient generators. This one is easy enough to use ->http://t-filter.engineerjs.com/Is there one that you can plug in your coefficients and it will display the frequency response curve? I want to see how far off the track I might be with non-ideal coefficients.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: November 26, 2020, 08:02:57 am
content: Ahh hmm, this one almost does it,https://www.falstad.com/dfilter/but the impulse response can't be drawn I think?Pretty sure I've seen one that did, or maybe it's still on Falstad I just don't remember which one exactly.Tim

author: langwadt
author_rank: Super Contributor
timestamp: November 26, 2020, 10:06:28 am
content: Quote from: T3sl4co1l on November 26, 2020, 08:02:57 amhttps://iowegian.com/scopefir/?

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 26, 2020, 01:11:08 pm
content: The following HTML document should be able to show the frequency response (relative magnitude and phase) of any FIR filter.Code:<!DOCTYPE html><html><head><title> Finite Impulse Response Filter Frequency Response </title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><style type="text/css">html, body {outline: 0px none;margin: 0 0 0 0;border: 0px none;padding: 0 0 0 0;width: 100%;height: 100%;background: #f7f7f7;color: #000000;font-family: Times New Roman, serif;font-size: 100%;font-weight: normal;}form {outline: 0px none;margin: 0 0 0 0;border: 0px none;padding: 0 0 0 0;width: 100%;height: 100%;}table {outline: 0px none;margin: 0 0 0 0;border: 0px none;border-collapse: collapse;padding: 0 0 0 0;width: 95%;height: 95%;}td, tr {outline: 0px none;margin: 0 0 0 0;border: 0px none;padding: 0 0 0 0;}td.top {vertical-align: top;}td.bottom {vertical-align: bottom;}td.left {text-align: left;}td.right {text-align: right;}td.full {width: 100%;height: 100%;}canvas {outline: 0px none;margin: 0 0 0 0;border: 0px none;padding: 0 0 0 0;width: 100%;height: 100%;background: #ffffff;color: #000000;}.fullwidth {width: 100%;}</style><script type="text/javascript">function recalculate() {var coeffs = ((document.getElementById("coefficients")).value).replace(/\s*[,|]\s*/g, " ").replace(/\s\s*/g, " ").replace(/^\s*/, "").replace(/\s*$/, "").split(" ");var plotcell = document.getElementById("plotcell");var plot = document.getElementById("plot");var ctx = plot.getContext("2d");var width = plotcell.clientWidth;var widthpadding = 0;var height = plotcell.clientHeight;var heightpadding = 0;while (1) {plot.setAttribute("width", width);plot.setAttribute("height", height);ctx.canvas.width = width;ctx.canvas.height = height;if (plotcell.clientHeight > height heightpadding) {heightpadding ;height--;} elseif (plotcell.clientWidth > width widthpadding) {widthpadding ;width--;} elsebreak;}ctx.fillStyle = "#FFFFFF";ctx.fillRect(0, 0, width, height);var C = [];for (i = 0; i < coeffs.length; i )C.push(parseFloat(coeffs));if (C.length < 2)return false;var dbs = Math.abs(parseFloat((document.getElementById("maxdb")).value));if (dbs <= 0)return false;var samples = parseInt((document.getElementById("samples")).value);if (samples <= 1)return false;if (samples > width - 32)samples = width - 32;ctx.beginPath();ctx.lineWidth = "1";ctx.strokeStyle = "#cccccc";ctx.moveTo(0, 16);ctx.lineTo(width, 16);ctx.moveTo(0, height-16);ctx.lineTo(32, height-16);ctx.moveTo(16, 0);ctx.lineTo(16, height);ctx.moveTo(width-16, height-32);ctx.lineTo(width-16, height);if (dbs <= 20) {for (d = 1; d < dbs; d ) {if (d % 10) {y = 16 (height - 32) * d / dbs;ctx.moveTo(8, y);ctx.lineTo(24, y);}}}for (d = 10; d < dbs; d = 10) {y = 16 (height - 32) * d / dbs;ctx.moveTo(4, y);ctx.lineTo(28, y);}ctx.stroke();ctx.beginPath();ctx.lineWidth = "1";ctx.strokeStyle = "#990000";ctx.moveTo(width - 16, 16); ctx.lineTo(width, 16);ctx.moveTo(width - 16, height/2); ctx.lineTo(width, height/2);ctx.moveTo(width - 16, height-16); ctx.lineTo(width, height-16);ctx.stroke();max_magnitude = 0.0;for (i = 0; i < C.length; i )max_magnitude = Math.abs(C);var Sample = function(w) {var result = { re:C, im:0.0, magnitude:0.0, phase:0.0 };for (n = 1; n < C.length; n ) {result.re = C * Math.cos(w*n);result.im = C * Math.sin(w*n);}result.magnitude = Math.sqrt(result.re*result.re result.im*result.im);result.phase = Math.atan2(result.im, result.re);return result;}var yscale = 20 * (height - 32 - 16) / dbs;var magnitude_y = [];var phase_y = [];for (i = 0; i <= samples; i ) {s = Sample(Math.PI * i / samples);phase_y.push(height*0.5 0.5*(height-32)*s.phase / Math.PI);magnitude_y.push(16 - yscale * Math.log10(s.magnitude / max_magnitude));}/* Phase in Red. */ctx.beginPath();ctx.lineWidth = "1";ctx.strokeStyle = "#990000";ctx.moveTo(16, phase_y);for (i = 1; i <= samples; i ) {ctx.lineTo(16 i * (width - 32) / samples, phase_y);}ctx.stroke();/* Magnitude in Black. */ctx.beginPath();ctx.lineWidth = "1";ctx.strokeStyle = "#000000";ctx.moveTo(16, magnitude_y);for (i = 1; i <= samples; i ) {ctx.lineTo(16 i * (width - 32) / samples, magnitude_y);}ctx.stroke();return false;}</script></head><body><form method="GET" action="#" onsubmit="return recalculate();"><table><tr><td class="right"><input type="submit" value="Calculate"></td><td class="full" colspan="2"><input class="fullwidth" id="coefficients" name="coefficients" type="text" value="" title="FIR coefficients"></td></tr><tr><td class="top right"><input id="samples" name="samples" value="512" size="4" title="Number of frequency samples"></td><td class="full" colspan="2" rowspan="2" id="plotcell"><canvas id="plot"></canvas></td></tr><tr><td class="bottom right"><input id="maxdb" name="maxdb" value="-100" size="4">db</td></tr><tr><td> </td><td class="top left">0</td><td class="top right">&pi;</td></tr></table></form></body></html>Save the above in a text file ending with .html, or save the attached frequency-response.txt renaming it as e.g frequency-response.html, and open it in your browser. It does not need a network connection.Put the FIR coefficients on the top text box, separated by spaces and/or commas; the number of samples in the frequency domain in the top left box, the bottom decibel value, and press Enter on any box, or click the Calculate button, and you'll get the frequency response curve (magnitude in black, logarithmic vertical scale; phase in read).I consider this to be in Public Domain, so feel free to publish, use, modify, etc. any way you wish.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 26, 2020, 04:38:52 pm
content: Choose a microcontroller with (edit: C99 standard-compliant) C compiler available. Unless you really want to make your life really hard.

author: newbrain
author_rank: Super Contributor
timestamp: November 26, 2020, 08:27:24 pm
content: As it seems that you are doing this out of interest and self study, I would also advise to get a good book and/or a tutorial that could lead you along the road of a fuller understanding of what DSP is (as a maths/engineering field, not as a specialized HW).It's fun to experiment and part of it, but some basis is needed for any real progress*.As for all this kind of things, IMHO, YMMV etc. etc.*e.g.: the quest for something showing the frequency response of a filter given the coefficients had an answer in this:Quote from: ejeffrey on November 25, 2020, 12:26:45 am(and partly in my post about coefficients).

author: langwadt
author_rank: Super Contributor
timestamp: November 26, 2020, 10:18:15 pm
content: can learn much from these even if they are for a specific (old) DSP familyhttps://www.analog.com/media/en/dsp-documentation/processor-manuals/2127342adsp2100vol1.ziphttps://www.analog.com/media/en/dsp-documentation/processor-manuals/60899921adsp2100vol2.zip

author: Circlotron
author_rank: Super Contributor
timestamp: November 27, 2020, 01:05:25 am
content: Quote from: Nominal Animal on November 26, 2020, 01:11:08 pmThat's pretty impressive!You can certainly see the difference when you have more resolution in the coefficients, e.g. 8-bit, 16 bit and floating. Could you add a option to turn the phase response on and off so the FR trace is a little clearer?

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 27, 2020, 01:00:50 pm
content: Quote from: Circlotron on November 27, 2020, 01:05:25 amSure. I edited it a bit so it'd look nicer and scale better for different display sizes. Attached as fir-anasysis.txt; rename as e.g. fir-analysis.html and open in a browser. It is still fully offline page, and in Public Domain.Note the newTap sum:field in the form. It is simply a sum of the filter coefficient magnitudes. Below that, you have it expressed as a power of two (optionally minus a constant). Basically, if you use integer arithmetic, the exponent tells you how many bits you need to shift the result sum down. (Note that then, you should also use integer coefficients.) Optimal coefficients have a tap sum that is a power of two. Temporary variables (registers) in your FIR filter need (that exponent) + (the number of bits in the samples) bits.Those who'd just like to try it out, I've uploaded this versionhere. It is the same file as in the attachment.

author: Nominal Animal
author_rank: Super Contributor
timestamp: November 27, 2020, 02:59:46 pm
content: There is now a variantinteger-fir-analysis.html, which is intended to help in implementing integer FIR filters on microcontrollers.You put the coefficients (as obtained from e.g. Octave) in the topmost input.On the left, you have an additional input,Bits. You set this to the number of bits you can spare in your temporary FIR filter registers. For example, if you have 20 bit samples, and 32-bit registers, put12here (because 32 - 20 = 12). You can also use a fractional number, if you wish to finesse the coefficients.Below the topmost input you see the actual integer coefficients used. (The frequency response is computed for these coefficients, not the coefficients you input.)For example, Octave suggests using0.020725 0.065594 0.166414 0.247266 0.247266 0.166414 0.065594 0.020725for an 8-tap lowpass filter with minimum cutoff frequency. If we use these, and have6bits to use (say, because we have 10-bit data and a fast multiply with 16-bit result), the page says that coefficients1, 4, 11, 16, 16, 11, 4, 1should produce a pretty good lowpass filter (with -32.4dB attenuation at high frequencies). Changing theBits, especially to just below the desired value (say, to5.9or5.8) changes the integer coefficients, and therefore the frequency response.At5.8bits, the coefficients are1, 4, 9, 13, 13, 9, 4, 1, and the filter is even better; -47dB attenuation at high frequencies. The downside is that since the tap sum is 54 = 2<sup>64</sup>-10, shifting the filtered result right by 6 bits means the result dynamic range is a tiny bit limited; it will only reach 100%·54/64 = 84.375% of the dynamic range. For 10-bit data, instead of 0..1023, the filtered result will be 0..863.I do believe some additional enhancements could be made. Showing the dynamic range of the filtered result is one; except I don't know if others think it as useful as I think it is. The other is adding mouseover for the canvas, so that the mouse cursor point relative to the canvas would be shown in frequency, wavelength and attenuation, sayf= 0.43478, λ = 2.3, χ = -30.2 dB.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 27, 2020, 04:15:35 pm
content: Thanks Nominal, this is a very good contribution. I quickly ran through some 120-tap bandpass FIR of my latest project involving DSP, where the stopband is around -50dB, to find out some 12 bits are actually needed, below that the performance will be significantly degraded. This is surprisingly low amount of bits needed, so clearly you can see that a 32-bit MCU with just 32-bit multiply operations would handle even quite some high-precision stuff (say 16-bit input from ADC, 16-bit FIR coeffs).Even on a 8-bitter, 32 bit operations are quite efficient.In IIR, you need to be a lot more careful with the numerical accuracy, especially storing the output values.As shown earlier by some other poster, with decimating FIR you don't need to calculate every output value, which saves processing time, so a 200-tap FIR working with 32-bit data types might not be slower than a 20-tap IIR working with 64-bit types, in the end.Last time I did a decimating FIR, I utilized the large number of single-precision float registers in Cortex M7 FPU to work as accumulators; for example, the 120-tap FIR which decimates by 1:8 requires 15 accumulators. No input data buffer; memory load from the ADC data register directly, and the FIR coeffs also need to be loaded. No memory stores except the actual decimated output data! This is likely two orders of magnitude more efficient than running the data to input buffer then use CMSIS DSP library to do FIR, then decimate the result. But such optimizations cannot be utilized in IIR filters, hence even if you are able to go with one tenth of the taps, the performance of FIR might still be better, and you can get the perfectly linear phase response (constant group delay) of the FIR, if that is of any value to you.

author: newbrain
author_rank: Super Contributor
timestamp: November 27, 2020, 05:42:16 pm
content: Just one minor nitpick in an (as usual) interesting and well argumented post:Quote from: Siwastaja on November 27, 2020, 04:15:35 pmThe CMSIS DSP library includes aFIR decimate family of functionsthat, albeit generic, exploits the optimization of only calculating the needed output values.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 27, 2020, 06:23:21 pm
content: Quote from: newbrain on November 27, 2020, 05:42:16 pmInteresting, because I did look at it back about a year ago and didn't see the decimating functions. Either it's a fairly new addition, or I somehow missed its existence!In any case, the problem with said DSP library is you are quite limited what comes to datatypes and data flow, i.e., it's batch processing from existing buffers while in reality you often want to process samples when they come in.

author: newbrain
author_rank: Super Contributor
timestamp: November 27, 2020, 06:49:50 pm
content: Yes, very true.But I'm guilty of using it as it's quite convenient for my friend's and my SDR project, wothout reinventing too many wheels.We started just about one year ago, and I'm sure those functions were already there.With an iMX RT 1020 we still have a max load of about 30% with 192 kHz sample rate and 512 samples buffers (I+Q, through I2S DMA) and that includes FreeRTOS and the UI overheads.Q15 in and out, float 32 for processing (the dual issue M7 makes that better than using Q15 throughout).

author: Siwastaja
author_rank: Super Contributor
timestamp: November 28, 2020, 09:20:27 am
content: I think my CPU utilization for M7 at 400MHz was around 30-40% for 120 taps, 1:8 decimation, 2MSPS in (250 kSPS out). Written in assembly, dynamic branch prediction turned off and most loops unrolled to get cycle-accurate (jitter-free) timing because the same code toggles nCS which controls sampling of the (simple) external ADC. Dual issue is great, as is the possibility to command load of memory range to multiple floating point registers, then run integer stuff in parallel. Coeffs in DTCM, obviously, and (mostly unrolled) code in ITCM.

author: Silenos
author_rank: Regular Contributor
timestamp: November 29, 2020, 03:55:00 pm
content: Quote from: Siwastaja on November 28, 2020, 09:20:27 amMay I ask, slightly off topic:What exactly are the effects/reasons of decimation? I have issues grasping it..As I understand, only every 8th sampled input is fed into filter, which is run also 250k times/s?So, say your fir does passband around 200 kHz, hence say 2 Msps input, but your whole design is ok with just 250 ksps output, right?The only thing I can see here is the input bandwitch "extension" - being further away from aliasing. But that's independent effect from any discrete filter, isn't it?But how such decimation is any different from just input sampling with 250 kHz? Is that some Nyquist sorcery involved or am I just plain wrong?

author: langwadt
author_rank: Super Contributor
timestamp: November 29, 2020, 04:04:02 pm
content: Quote from: Silenos on November 29, 2020, 03:55:00 pmall the inputs are feed into the filter but only every 8th output is calculatedQuote from: Silenos on November 29, 2020, 03:55:00 pmit means the analog anti-aliazing filter can be quite mild and you can do the steep filter digitally. And ideally the 1:8 decimation also gain about 4 bits

author: newbrain
author_rank: Super Contributor
timestamp: November 29, 2020, 04:11:55 pm
content: (langwadt ninjaed me with the anti-aliasing reason)Another reason for using decimation is "carving out" a part of the input spectrum for further processing, about equivalent to tuning a real radio on a specific station:One starts with a full bandwidth (complex) spectrum, a single (cheap) complex multiplication per sample "rotates" it, bringing the section of interest around the origin.A decimating FIR now extracts that section, and any further processing can be done at the decimated sample rate.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 29, 2020, 04:12:26 pm
content: Quote from: Silenos on November 29, 2020, 03:55:00 pmNo, decimating is doneafterfiltering, the filter processes all inputs at 2MSPS, but only one every eight outputs is actually passed forward to whatever uses the data. With IIR, you have to calculate all the outputs because they are used as inputs for the filter as well, but with FIR, you can decide not to calculate something which is just thrown away.The reason is, simply reducing the amount of unnecessary data.If the frequencies of interest are only up to 100kHz, then by the Nyquist theorem, I only need fs = 200kHz. Let's say 250kHz for some headroom. So content up to 125kHz is allowed.But in order not to alias unwanted noises over 125kHz, I need to filter them out before sampling. Let's first try to choose ADC fs = 250kHz. Now we need to build a gazillionth order analog filter to make 100kHz (our required signal) to pass but everything above 125kHz (which would alias) attenuate to insignificant levels. Obviously, building a gazillionth order analog filter is going to be expensive, or near-impossible, especially if stability and phase response are important.So that's not going to work. Let's do what we can, and just build a simple 2nd or maybe 4th order analog filter. Pass band ending at 100kHz, it's going to be at 1000kHz until the response has attenuated enough. What to do? Simple: Just sample at 2MSPS! No aliasing of components up to 1000kHz.Now, when we change todigitaldomain, we have no problem whatsoever building a gazillionth-order near-perfect filter. Our analog filter prevented aliasing from signals over 1000kHz; we still have some leftovers between 100kHz and 1000kHz we need to get rid of.So design a digital FIR or IIR with near brickwall response. Make it pass <100kHz; make it block >125kHz. And magic happens, now there are no components above 125kHz; so fs=250kHz will be enough to store this data!Now decimate; simply remove 7 out of 8 samples. fs becomes 2000kHz/8 = 250kHz, and nothing is lost in process. Except of course the signals over 100kHz we filtered out, because we wanted to filter them out.A side effect of this is that effective ADC resolution increases because each output sample uses data fromseveralADC samples, so a 14-bit ADC that has ENOB of say 12.5 bits might be closer to 13.5 bits ENOB. This is roughly equivalent to what happens when you just simply average ADC values together.Of course we could choose not to decimate, but then we would have a lot more data to store, which does not represent actual information. If you plot the filtered data in frequency domain, you just see empty plot most of the right side. If you look at it in time domain, you'll see that the values go smoothly and slowly; if you just remove in-between values, you can always later interpolate them back (because there is no actual information there).

author: SiliconWizard
author_rank: Super Contributor
timestamp: November 29, 2020, 05:11:24 pm
content: Yes. Problem with a "near brickwall" FIR is that it will require a pretty high order, thus be very computationally expensive. IIRs would be less expensive, but would possibly exhibit problematic phase issues.

author: langwadt
author_rank: Super Contributor
timestamp: November 29, 2020, 06:10:35 pm
content: Quote from: SiliconWizard on November 29, 2020, 05:11:24 pma FIR filter has the advantage that you only need to calculate the outputs you actually need, so the filter is effectively running at the lower output rate and not the full input rate.also instead of doing the decimation in one stage you can do multiple stages, each running slower and slower. often withthe final slowest running filter used to clean up the responsehttps://en.wikipedia.org/wiki/Half-band_filter

author: Siwastaja
author_rank: Super Contributor
timestamp: November 29, 2020, 06:50:05 pm
content: Yes, and finally, a FIR could run at lower bit depth because of no error accumulation, enabling fewer instructions per multiply-accumulate operation (or utilization of SIMD instructions).

author: Silenos
author_rank: Regular Contributor
timestamp: November 29, 2020, 08:52:34 pm
content: Thanks all of you, now it is crystal clear