Digital BIQUAD filter calculations
TC
Posts: 1,019
in Propeller 1
Hello all,
I am building my own bluetooth headphones. I am currently working on the DSP for a 5 band EQ. I was going to make a database for the biquad coefficients. Then just look up the coefficient value. But I then realized the amount of data that would be. I am going with the tlv320aic3268 from Texas Instruments. Each coefficient is 24bits wide, and each BIQUAD need 5 coefficients. That would be 5 longs for each band of the EQ (assuming left and right use same values).
5 longs * 5 bands = 25 longs
That's not a big deal, but I want to have the option to change the gain of each EQ band from +12db to -12db, in 0.1db steps. That is about 240(ish) steps.
5 coefficients (24bits each) * 5 bands * 240 steps = 6000 longs
That's a lot of data. So I thought, maybe the Prop can do the coefficient calculations on the fly. I just put in the EQ frequency, the "Q", and the gain, and the Prop could perform the calculation, and load the coefficients. I found papers that explains the biquad calculations (EQ cookbook), and one the gives Matlab equations (Texas). But when I do the equations in excel, I get incorrect results. I am using Texas's coefficients calculator to verify my results.
I am wondering if anyone would have some advice, or if anyone would be able to help me make the prop do coefficient calculations on the fly.
Thanks
TC
I am building my own bluetooth headphones. I am currently working on the DSP for a 5 band EQ. I was going to make a database for the biquad coefficients. Then just look up the coefficient value. But I then realized the amount of data that would be. I am going with the tlv320aic3268 from Texas Instruments. Each coefficient is 24bits wide, and each BIQUAD need 5 coefficients. That would be 5 longs for each band of the EQ (assuming left and right use same values).
5 longs * 5 bands = 25 longs
That's not a big deal, but I want to have the option to change the gain of each EQ band from +12db to -12db, in 0.1db steps. That is about 240(ish) steps.
5 coefficients (24bits each) * 5 bands * 240 steps = 6000 longs
That's a lot of data. So I thought, maybe the Prop can do the coefficient calculations on the fly. I just put in the EQ frequency, the "Q", and the gain, and the Prop could perform the calculation, and load the coefficients. I found papers that explains the biquad calculations (EQ cookbook), and one the gives Matlab equations (Texas). But when I do the equations in excel, I get incorrect results. I am using Texas's coefficients calculator to verify my results.
I am wondering if anyone would have some advice, or if anyone would be able to help me make the prop do coefficient calculations on the fly.
Thanks
TC

Comments
I made a LPF biquad filter calculator code for the prop. This is just to see if I can get the same values as the Texas Instruments biquad calculator. The values I am getting are a little off from what the texas calculator is giving me. Could someone please take a look at it? I might of missed something, or I have something wrong.
user define values:
Low Pass Filter
sample freq = 48000 Hz
cutoff freq = 600 Hz
Q = 0.707
Values from Texas calculator
N0 = $002FD9
N1 = $002FD9
N2 = $002FD9
D1 = $78E5AB
D2 = $8D7541
Values from prop
N0 = $002FC0
N1 = $002FC0
N2 = $002FC0
D1 = $78E5D1
D2 = $8D755D
Thanks
TC
CON _clkmode = xtal1 + pll16x 'Standard clock mode * crystal frequency = 80 MHz _xinfreq = 5_000_000 CON baud = 250000 sample = 48000 range = 8388608.0 '2^(number of bits - 1)-1 OBJ Math : "Float32Full" pst : "Parallax Serial Terminal" FtoS : "FloatString" pub start math.start pst.start(baud) LPF(600, 0.707, 0) PUB LPF(freq, Q, gain)| w, alpha, cosW, sinW, b0, b1, b2, a0, a1, a2, N0_real, N1_real, N2_real, N0, N1, N2, D1, D2, factor, N0_hex, N1_hex, N2_hex, D1_hex, D2_hex, hex_temp1, hex_temp2, hex_temp3 ' w = 2*PI*f0/Fs w := math.Fmul(math.Fmul(2.0, pi), math.Fdiv(freq, sample)) pst.str(string("w = ")) pst.str(FtoS.FloatToString(w)) pst.newline cosW := Math.Cos(w) pst.str(string("cosW = ")) pst.str(FtoS.FloatToString(cosW)) pst.newline sinW := Math.Sin(w) pst.str(string("sinW = ")) pst.str(FtoS.FloatToString(sinW)) pst.newline 'alpha = sin(w)/(2*Q) alpha := math.Fdiv(sinW, math.Fmul(2.0, Q)) pst.str(string("alpha = ")) pst.str(FtoS.FloatToString(alpha)) pst.newline 'b0 = (1-cos(w))/2 b0 := math.Fdiv(math.Fsub(1.0, cosW), 2.0) pst.str(string("b0 = ")) pst.str(FtoS.FloatToString(b0)) pst.newline 'b1 = 1-cos(w) b1 := math.FSub(1.0, cosW) pst.str(string("b1 = ")) pst.str(FtoS.FloatToString(b1)) pst.newline 'b2 = (1-cos(w))/2 b2 := math.Fdiv(math.Fsub(1.0, cosW), 2.0) pst.str(string("b2 = ")) pst.str(FtoS.FloatToString(b2)) pst.newline 'a0 = 1+alpha a0 := math.Fadd(1.0, alpha) pst.str(string("a0 = ")) pst.str(FtoS.FloatToString(a0)) pst.newline 'a1 = -2*cos(w) a1 := math.Fmul(-2.0, cosW) pst.str(string("a1 = ")) pst.str(FtoS.FloatToString(a1)) pst.newline 'a2 = 1-alpha a2 := math.Fsub(1.0, alpha) pst.str(string("a2 = ")) pst.str(FtoS.FloatToString(a2)) pst.newline 'N0_real = (b0/a0) N0_real := math.Fdiv(b0, a0) pst.str(string("N0_real = ")) pst.str(FtoS.FloatToString(N0_real)) pst.newline 'N1_real = b1/a0/2 N1_real := math.Fdiv(math.Fdiv(b1, a0), 2.0) pst.str(string("N1_real = ")) pst.str(FtoS.FloatToString(N1_real)) pst.newline 'N2_real = (b2/a0) N2_real := math.Fdiv(b2, a0) pst.str(string("N2_real = ")) pst.str(FtoS.FloatToString(N2_real)) pst.newline 'scale the coefficients factor := math.Fmax(math.Fmax(N1_real, N2_real), N0_real) if math.Fcmp(factor, 1.0) == -1 factor := 1.0 pst.str(string("Factor = ")) pst.str(FtoS.FloatToString(factor)) pst.newline 'convert to quantized coefficients 'N0 = ((b0/a0)/factor)*range N0 := math.Fmul(math.Fdiv(math.Fdiv(b0, a0), factor), range) pst.str(string("N0 = ")) pst.str(FtoS.FloatToString(N0)) pst.newline 'N1 = ((b1/a0)/(2*factor))/range N1 := math.Fmul(math.Fdiv(math.Fdiv(b1, a0), math.Fmul(2.0, factor)), range) pst.str(string("N1 = ")) pst.str(FtoS.FloatToString(N1)) pst.newline 'N2 = ((b2/a0)/factor)*range N2 := math.Fmul(math.Fdiv(math.Fdiv(b2, a0), factor), range) pst.str(string("N2 = ")) pst.str(FtoS.FloatToString(N2)) pst.newline 'D1 = (a1/(-2*a0))*range D1 := math.Fmul(math.Fdiv(a1, math.Fmul(-2.0, a0)), range) pst.str(string("D1 = ")) pst.str(FtoS.FloatToString(D1)) pst.newline 'D2 = (a2/a0)*range D2 := math.Fmul(math.Fneg(math.Fdiv(a2, a0)), range) pst.str(string("D2 = ")) pst.str(FtoS.FloatToString(D2)) pst.newline N0_hex := math.Fround(N0) pst.str(string("N0_hex = ")) pst.hex(N0_hex,6) pst.newline N1_hex := math.Fround(N1) pst.str(string("N1_hex = ")) pst.hex(N1_hex,6) pst.newline N2_hex := math.Fround(N2) pst.str(string("N2_hex = ")) pst.hex(N2_hex,6) pst.newline D1_hex := math.Fround(D1) pst.str(string("D1_hex = ")) pst.hex(D1_hex,6) pst.newline D2_hex := math.Fround(D2) pst.str(string("D2_hex = ")) pst.hex(D2_hex,6) pst.newlineI was wondering about the precision of the prop. The only main reason i didn't want to do a table of the values is because I am lazy. But I don't think I really have any real options. At least doing a table would make sure the correct values would be loaded.
I tried this in PropGCC and it worked fine; there are some 1 bit differences from the TI calculator, but the PropGCC values agreed with what I got on my PC. On the Prop I tried with both 32 bit and 64 bit doubles, and got the same results in both cases; 32 bit doubles are a lot faster, so that would be the sensible choice. The code is pretty big though (due to printf) so you'll definitely want to use CMM mode.
values from PropGCC
N0 = $002fda
N1 = $002fda
N2 = $002fda
D1 = $78e5ab
D2 = $8d7541
// // biquad calculations // #include <stdio.h> #include <math.h> #define myround(x) ((int)((x)+0.5)) double sample = 48000; double range = 8388608.0; void LPF(int freq, double Q, int gain) { double w = 2 * M_PI * freq / sample; double cosw = cos(w); double sinw = sin(w); double alpha = sinw / (2.0*Q); double b0 = (1.0-cosw) / 2.0; double b1 = 1.0 - cosw; double b2 = b1 / 2.0; double a0 = 1.0 + alpha; double a1 = -2.0*cosw; double a2 = 1.0-alpha; double N0_real = (b0/a0); double N1_real = (b1/a0)/2.0; double N2_real = (b2/a0); double factor; double N0, N1, N2; double D1, D2; int N0_hex, N1_hex, N2_hex; int D1_hex, D2_hex; factor = fmax(fmax(N1_real, N2_real), N0_real); if (factor < 1.0) factor = 1.0; N0 = ((b0/a0)/factor) * range; N1 = ((b1/a0)/(2*factor)) * range; N2 = ((b2/a0)/factor) * range; D1 = (a1/(-2.0*a0))*range; D2 = (-(a2/a0))*range; N0_hex = myround(N0); N1_hex = myround(N1); N2_hex = myround(N2); D1_hex = myround(D1); D2_hex = myround(D2) & 0x00ffffff; printf("N0 = $%06x\n", N0_hex); printf("N1 = $%06x\n", N1_hex); printf("N2 = $%06x\n", N2_hex); printf("D1 = $%06x\n", D1_hex); printf("D2 = $%06x\n", D2_hex); } int main() { LPF(600, 0.707, 0); return 0; }Thank you ersmith. I never learned trig in school, and I am learning as I go. It makes me feel better knowing that the problem is not with my code, only the library I am using.
I'm going to do what Peter suggested, use ersmith's code and load a eeprom with the values. That would be a lot easier than me hand typing all the values.
Thank you so much for the help.
TC