Profiles / Engineering & data
ES

Electronics & signal engineer

Filter design, spectra, peak detection and SNR on sampled signals.

10 recipes on this page · open in the interactive view

Recipes
  1. Butterworth filter order from a template
  2. Dominant frequencies of a signal
  3. SNR gain of a low-pass filter
  4. Response of a Chebyshev low-pass filter
  5. Pulse compression with a Barker 13 code
  6. Chebyshev type I low-pass: order and poles
  7. First-order digital low-pass by the bilinear transform with prewarping
  8. Quantization noise of an 8-bit ADC
  9. PRBS7 test pattern from a linear feedback shift register
  10. GMSK frequency pulse with BT = 0.3
executed on MentDB

Butterworth filter order from a template

A low-pass filter must keep 1 kHz within 1 dB and attenuate 4 kHz by 40 dB. The script computes the minimum Butterworth order, the -3 dB cutoff and the frequency where 40 dB is reached.

Pipeline
Script
admin
#1. Specs: passband 1 kHz at 1 dB max, stopband 4 kHz at 40 dB min;
-> "[fp]" 1000; -> "[fs]" 4000; -> "[Ap]" 1; -> "[As]" 40;
#2. Minimum Butterworth order;
-> "[n]" (math ceil (math calc "log10((10^([As]/10)-1)/(10^([Ap]/10)-1))/(2*log10([fs]/[fp]))"));
#3. -3 dB cutoff that meets the passband exactly;
-> "[fc]" (math calc "[fp]/(10^([Ap]/10)-1)^(1/(2*[n]))");
#4. Frequency where the attenuation reaches 40 dB;
-> "[f40]" (num solve (concat "10*log10(1+(f/" [fc] ")^(2*" [n] "))-40") "f" [fp] 100000);
concat "Order " [n] ", fc = " (math round [fc] 1) " Hz, 40 dB reached at " (math round [f40] 0) " Hz, attenuation at 4 kHz = " (math round (math calc "10*log10(1+([fs]/[fc])^(2*[n]))") 1) " dB"
Result
mentdb
Order 4, fc = 1184 Hz, 40 dB reached at 3744 Hz, attenuation at 4 kHz = 42.3 dB
Go further
  • Apply the designed filter to a test signal with dsp filter
  • Compare with a Chebyshev template by changing the order formula
  • Plot the gain curve with num table on 10 log10(1 + (f/fc)^2n)
executed on MentDB

Dominant frequencies of a signal

A 10 Hz signal sampled at 500 Hz is polluted by a 50 Hz mains hum. The script computes the amplitude spectrum and detects its peaks to give each frequency and amplitude.

Pipeline
Script
admin
#1. 100 samples at 500 Hz: 10 Hz tone + 50 Hz hum;
-> "[x]" "["; -> "[sep]" "";
for (-> "[i]" 0) (< [i] 100) (++ "[i]") {
  -> "[x]" (concat [x] [sep] (math calc "sin(2*pi*10*[i]/500)+0.3*sin(2*pi*50*[i]/500)"));
  -> "[sep]" ",";
};
-> "[x]" (concat [x] "]");
#2. One-sided amplitude spectrum;
json load "rx_sig_spec" (dsp spectrum [x] 500);
#3. Peaks of the amplitude above 0.1;
json load "rx_sig_pk" (dsp peaks (json select "rx_sig_spec" /amplitude) "{\"height\": 0.1}");
-> "[k1]" (json select "rx_sig_pk" "/indices[0]");
-> "[k2]" (json select "rx_sig_pk" "/indices[1]");
concat "Peak 1: " (json select "rx_sig_spec" (concat "/f[" [k1] "]")) " Hz, A = " (math round (json select "rx_sig_pk" "/values[0]") 3) "; peak 2: " (json select "rx_sig_spec" (concat "/f[" [k2] "]")) " Hz, A = " (math round (json select "rx_sig_pk" "/values[1]") 3)
Result
mentdb
Peak 1: 10 Hz, A = 1; peak 2: 50 Hz, A = 0.3
Go further
executed on MentDB

SNR gain of a low-pass filter

A 5 Hz measurement is disturbed by 60 Hz and 110 Hz interference. The script filters it with a zero-phase Butterworth low-pass and computes the SNR before and after.

Pipeline
Script
admin
#1. 200 samples at 500 Hz: 5 Hz useful signal, disturbances at 60 and 110 Hz;
-> "[s]" "["; -> "[x]" "["; -> "[sep]" "";
for (-> "[i]" 0) (< [i] 200) (++ "[i]") {
  -> "[s]" (concat [s] [sep] (math calc "sin(2*pi*5*[i]/500)"));
  -> "[x]" (concat [x] [sep] (math calc "sin(2*pi*5*[i]/500)+0.5*sin(2*pi*60*[i]/500)+0.3*sin(2*pi*110*[i]/500)"));
  -> "[sep]" ",";
};
-> "[s]" (concat [s] "]"); -> "[x]" (concat [x] "]");
#2. Zero-phase Butterworth low-pass, 15 Hz, order 4;
-> "[y]" (dsp filter [x] 500 "lowpass" 15 4);
#3. SNR = 10 log10 of signal power over noise power, before and after;
-> "[ps]" (stat sum_sq [s]);
-> "[in]" (math calc (concat "10*log10([ps]/" (stat sum_sq (vector sub [x] [s])) ")"));
-> "[out]" (math calc (concat "10*log10([ps]/" (stat sum_sq (vector sub [y] [s])) ")"));
concat "SNR in = " (math round [in] 1) " dB, SNR out = " (math round [out] 1) " dB, gain = " (math round (math calc "[out]-[in]") 1) " dB"
Result
mentdb
SNR in = 4.7 dB, SNR out = 19.7 dB, gain = 15 dB
Go further
executed on MentDB

Response of a Chebyshev low-pass filter

A type I Chebyshev filter of order 5 with 0.5 dB ripple: the gain is 1/(1 + eps^2 T5(f/fc)^2). The script computes eps, the attenuation at twice the cutoff, the slope at the band edge (n U4(1)), and draws the gain in dB.

Pipeline
Script
admin
#1. Chebyshev type I low-pass: order 5, ripple 0.5 dB, cutoff 1 kHz;
-> "[n]" 5;
-> "[eps2]" (math calc "10^(0.5/10)-1");
#2. Attenuation at 2 kHz: 10 log of 1 + eps^2 T5 of 2 ^2;
-> "[A2k]" (math calc "10*log10(1+[eps2]*chebyshev_t(5, 2)^2)");
#3. Slope of T5 at the band edge, dT/dx = n U4 of 1;
-> "[slope]" (math calc "[n]*chebyshev_u(4, 1)");
#4. Gain in dB from 0 to 3 kHz, x = f / 1 kHz;
plot function "-10*log10(1+[eps2]*chebyshev_t(5, x)^2)" "x" 0 3
Result
mentdb
The chart is shown in the Plot tab of the editor.
Go further
  • Find the order needed for 40 dB at 2 fc with num solve on n.
  • Compare with a Butterworth filter of the same order: 1/(1 + x^10).
executed on MentDB

Pulse compression with a Barker 13 code

A pulse radar transmits a Barker 13 code with 1 us chips. Compute the peak sidelobe ratio of the code from its autocorrelation, then find the echo in a noisy received signal with a matched filter (cross-correlation), its delay and the target range c tau / 2.

Pipeline
Script
admin
#1. Barker 13 code transmitted by a pulse radar, chip time 1 us;
-> "[code]" "[1, 1, 1, 1, 1, -1, -1, 1, 1, -1, 1, -1, 1]";
#2. Compression gain: autocorrelation peak against the highest sidelobe;
json load "rx_ac" (dsp correlate [code] [code]);
-> "[main]" (stat max (json doc "rx_ac"));
json load "rx_pk" (dsp peaks (json doc "rx_ac"));
-> "[psl]" (math calc (concat "20*log10(" [main] "/" (json select "rx_pk" "/values/[1]") ")"));
#3. Received echo: delayed by 9 chips, attenuated, with noise;
-> "[rx]" "[0.1, -0.2, 0.05, 0.1, -0.1, 0.2, 0, -0.1, 0.1, 0.5, 0.6, 0.4, 0.5, 0.6, -0.4, -0.5, 0.6, 0.4, -0.6, 0.5, -0.4, 0.6, 0.1, -0.1, 0.05]";
#4. Matched filter by cross-correlation, lag of the maximum;
json load "rx_mf" (dsp peaks (dsp correlate [rx] [code]) "{\"distance\": 30}");
-> "[lag]" (- (json select "rx_mf" "/indices/[0]") 12);
-> "[range]" (math calc (concat "299792458*" [lag] "e-6/2"));
concat "peak " [main] ", peak sidelobe ratio " (math round [psl] 2) " dB | echo delay " [lag] " us, matched filter output " (math round (json select "rx_mf" "/values/[0]") 2) " | target range " (math round (/ [range] 1000) 3) " km"
Result
mentdb
peak 13, peak sidelobe ratio 22.28 dB | echo delay 9 us, matched filter output 6.6 | target range 1.349 km
Go further
  • Add Doppler: multiply the echo by a phase rotation and see the loss of the matched filter.
  • Smooth the output with dsp moving_average before a CFAR threshold.
executed on MentDB

Chebyshev type I low-pass: order and poles

An anti-aliasing filter must keep 1 dB ripple up to 1 kHz and attenuate 40 dB at 2 kHz. The script computes the minimum Chebyshev order with the inverse hyperbolic cosine, then the normalized poles s_k = j sin(pi/2 - theta_k + j alpha) with alpha = asinh(1/eps)/n.

Pipeline
Script
admin
#1. Template: ripple 1 dB up to 1 kHz, 40 dB at 2 kHz;
-> "[eps]" (math sqrt (- (math power 10 0.1) 1));
-> "[D]" (math sqrt (/ (- (math power 10 4) 1) (* [eps] [eps])));
#2. Minimum order of the Chebyshev filter;
-> "[n]" (math ceil (/ (math acosh [D]) (math acosh 2)));
#3. Pole parameter alpha and the poles of the upper half plane, normalized to wp = 1;
-> "[alpha]" (/ (math asinh (/ 1 [eps])) [n]);
-> "[poles]" "";
for (-> "[k]" 1) (<= [k] (math ceil (/ [n] 2))) (++ "[k]") {
  -> "[x]" (math round (math calc "pi/2-(2*[k]-1)*pi/(2*[n])") 12);
  -> "[s]" (complex mul "i" (complex sin (concat [x] "+" [alpha] "i")));
  -> "[poles]" (concat [poles] " " (math round (complex re [s]) 4) (if (> (math abs (complex im [s])) 0.000001) {concat " +/- j" (math round (complex im [s]) 4)} {""}) ";");
};
concat "eps = " (math round [eps] 4) " | order n = " [n] " | alpha = " (math round [alpha] 4) " | poles:" [poles]
Result
mentdb
eps = 0.5088 | order n = 5 | alpha = 0.2856 | poles: -0.0895 +/- j0.9901; -0.2342 +/- j0.6119; -0.2895;
Go further
  • Scale the poles by 2 pi 1000 and build the Sallen-Key sections, one per conjugate pair
  • Compare the order with the Butterworth one for the same template
executed on MentDB

First-order digital low-pass by the bilinear transform with prewarping

A 1 kHz first-order low-pass is needed at fs = 8 kHz. The script prewarps the cutoff with the tangent, computes the coefficients of H(z) = b0 (1 + z^-1) / (1 + a1 z^-1) and checks on the unit circle that the gain is exactly -3 dB at 1 kHz.

Pipeline
Script
admin
#1. Cutoff 1 kHz, sampling 8 kHz: prewarped analog cutoff;
-> "[fc]" 1000; -> "[fs]" 8000;
-> "[K]" (math tan (/ (* (math pi) [fc]) [fs]));
-> "[fa]" (/ (* 2 (* [fs] [K])) (* 2 (math pi)));
#2. Coefficients of the digital filter;
-> "[b0]" (/ [K] (+ 1 [K])); -> "[a1]" (/ (- [K] 1) (+ [K] 1));
#3. Frequency response at 1 kHz and 3 kHz;
-> "[g1]" (complex abs (complex div (complex mul [b0] (complex add 1 (complex from_polar 1 (math calc "-2*pi*1000/8000")))) (complex add 1 (complex mul [a1] (complex from_polar 1 (math calc "-2*pi*1000/8000"))))));
-> "[g3]" (complex abs (complex div (complex mul [b0] (complex add 1 (complex from_polar 1 (math calc "-2*pi*3000/8000")))) (complex add 1 (complex mul [a1] (complex from_polar 1 (math calc "-2*pi*3000/8000"))))));
concat "prewarped analog cutoff " (math round [fa] 1) " Hz | b0 = b1 = " (math round [b0] 6) ", a1 = " (math round [a1] 6) " | 1 kHz: " (math round (* 20 (math log10 [g1])) 3) " dB | 3 kHz: " (math round (* 20 (math log10 [g3])) 2) " dB, analog RC: " (math round (* -10 (math log10 (+ 1 9))) 2) " dB"
Result
mentdb
prewarped analog cutoff 1054.8 Hz | b0 = b1 = 0.292893, a1 = -0.414214 | 1 kHz: -3.01 dB | 3 kHz: -15.44 dB, analog RC: -10 dB
Go further
  • Filter a test signal with dsp filter using these b and a coefficients
  • Do the same for a second-order Butterworth section with the bilinear transform
executed on MentDB

Quantization noise of an 8-bit ADC

A 0.95 V sine is sampled by an 8-bit ADC with a +/-1 V range. The script quantizes 200 samples with rint, measures the variance of the quantization error, compares it with the classic LSB^2/12 model and the SQNR with 6.02 N + 1.76 dB, and shows the resolution of a double for comparison.

Pipeline
Script
admin
#1. 8-bit ADC, range +/-1 V, sine 0.95 V at a non-coherent frequency;
-> "[lsb]" (/ 2 256); -> "[A]" 0.95;
json load "rx_p4f_err" "[]";
#2. Quantize 200 samples and keep the errors;
for (-> "[n]" 0) (< [n] 200) (++ "[n]") {
  -> "[x]" (* [A] (math sin (* 0.4593 [n])));
  -> "[q]" (* [lsb] (math rint (/ [x] [lsb])));
  json iarray "rx_p4f_err" / (- [q] [x]) NUM;
};
#3. Measured noise power vs the LSB^2/12 model;
-> "[pn]" (stat pvariance (json doc "rx_p4f_err"));
json unload "rx_p4f_err";
-> "[sqnr]" (* 10 (math log10 (/ (/ (* [A] [A]) 2) [pn])));
concat "noise " (math round_sig [pn] 4) " V2 vs LSB2/12 = " (math round_sig (/ (* [lsb] [lsb]) 12) 4) " | SQNR = " (math round [sqnr] 2) " dB vs theory " (math round (+ (+ (* 6.02 8) 1.76) (* 20 (math log10 [A]))) 2) " dB | ENOB " (math round (/ (- [sqnr] 1.76) 6.02) 2) " bits | double ulp at 0.95: " (math ulp 0.95)
Result
mentdb
noise 0.000005521 V2 vs LSB2/12 = 0.000005086 | SQNR = 49.12 dB vs theory 49.47 dB | ENOB 7.87 bits | double ulp at 0.95: 1.1102230246251565E-16
Go further
  • Add a dither of 1 LSB and look at the harmonics with dsp spectrum
  • Repeat for 10, 12 and 16 bits and fit the SQNR line with stat fit
executed on MentDB

PRBS7 test pattern from a linear feedback shift register

Serial links are tested with the PRBS7 pattern x^7 + x^6 + 1. The script runs the 7-bit LFSR with bitwise operations, checks that the period is the maximal 127 bits and that the pattern is balanced (64 ones, 63 zeros), and prints the first 24 bits.

Pipeline
Script
admin
#1. Seed 1111111, polynomial x^7 + x^6 + 1;
-> "[seed]" 127; -> "[s]" [seed]; -> "[ones]" 0; -> "[period]" 0; -> "[bits]" "";
#2. 127 shifts: feedback = bit 6 XOR bit 5, then shift left and keep 7 bits;
for (-> "[n]" 1) (<= [n] 127) (++ "[n]") {
  -> "[nb]" (math bit_and (math bit_xor (math trunc (/ [s] 64)) (math trunc (/ [s] 32))) 1);
  -> "[s]" (math bit_and (math bit_or (* [s] 2) [nb]) 127);
  -> "[ones]" (+ [ones] [nb]);
  -> "[bits]" (concat [bits] [nb]);
  if (and (== [s] [seed]) (== [period] 0)) {-> "[period]" [n];};
};
#3. Period and balance of a maximal length sequence;
concat "period " [period] " bits" (if (== [period] 127) {" = 2^7 - 1, maximal"} {" not maximal"}) " | ones " [ones] ", zeros " (- 127 [ones]) " | first bits " (string left [bits] 24)
Result
mentdb
period 127 bits = 2^7 - 1, maximal | ones 64, zeros 63 | first bits 000000100000110000101000
Go further
  • Map the bits to +/-1 and check the circular autocorrelation, -1 outside the peak, with dsp correlate
  • Count the runs: a PRBS7 has 32 runs of length 1
executed on MentDB

GMSK frequency pulse with BT = 0.3

GSM and Bluetooth use GMSK, an MSK whose frequency pulse is shaped by a Gaussian filter. For BT = 0.3 the script computes the frequency pulse g(t) with the error function, the inter-symbol interference on the neighbour symbols and checks by summation that the phase change of one symbol is pi/2.

Pipeline
Script
admin
#1. BT = 0.3, symbol time T = 1, constant pi B sqrt of 2 over ln 2;
-> "[B]" 0.3;
-> "[k]" (* (* (math pi) [B]) (math sqrt (/ 2 (math log 2))));
#2. Frequency pulse at 0, T and 2T;
-> "[g0]" (/ (- (math erf (* [k] 0.5)) (math erf (* [k] -0.5))) 4);
-> "[g1]" (/ (- (math erf (* [k] 1.5)) (math erf (* [k] 0.5))) 4);
-> "[g2]" (/ (- (math erf (* [k] 2.5)) (math erf (* [k] 1.5))) 4);
#3. Area of the pulse from -3T to 3T, step T/20: the phase change is pi times the area;
-> "[area]" 0;
for (-> "[i]" -60) (<= [i] 60) (++ "[i]") {
  -> "[t]" (/ [i] 20);
  -> "[area]" (+ [area] (/ (- (math erf (* [k] (+ [t] 0.5))) (math erf (* [k] (- [t] 0.5)))) 80));
};
concat "g(0) = " (math round [g0] 4) " (MSK 0.5) | ISI g(T)/g(0) = " (math round (/ [g1] [g0]) 4) ", g(2T)/g(0) = " (math round (/ [g2] [g0]) 6) " | phase per symbol = " (math round (* 180 [area]) 3) " deg"
Result
mentdb
g(0) = 0.3712 (MSK 0.5) | ISI g(T)/g(0) = 0.1731, g(2T)/g(0) = 4.6E-4 | phase per symbol = 90 deg
Go further
  • Plot g(t) for BT = 0.3 and 0.5 with plot xy to see the spreading
  • Integrate g(t) to get the phase trajectory of a bit sequence

Also in Engineering & data

TR
Telecom & radar engineer
Link budgets in dB, radar equation, detection thresholds and spectra of modulated signals.
10 recipes
CE
Control engineer
Transfer functions, margins, PID and state feedback: complex numbers, CAS, ODE and matrices.
10 recipes
RE
Robotics engineer
Kinematics, Jacobians and trajectories: matrices, vectors, non-linear systems and splines.
10 recipes
EE
Electrical engineer
Impedances, three-phase power and cable sizing with complex numbers and units.
10 recipes
IS
IoT & sensor engineer
Sensor calibration, smoothing, drift detection and measurement uncertainty.
10 recipes
MC
Mechanical & civil engineer
Deflection, stresses, safety factors and concrete strength statistics.
10 recipes
IQ
Industrial quality engineer
SPC control limits, process capability and tests on machines.
10 recipes
DS
Data scientist
Regression, correlation, feature scaling and drift tests.
10 recipes
SB
Statistician & biostatistician
Confidence intervals, sample size, ANOVA, chi-square and non-parametric tests.
10 recipes
DQ
Data quality analyst
Outliers, format checks, normalization and duplicate detection.
10 recipes