|
|
View previous topic :: View next topic |
Author |
Message |
Guest
|
|
Posted: Fri Dec 05, 2008 5:39 pm |
|
|
Thanks for the answers. Since increasing the number of period counts isn't as far-fetched as I first thought, I'll modify the program accordingly. It won't be hard to do, and that's why I'd rather leave that for last, and focus on the more demanding filtering process.
I would like to know if the code I put above for the filtering subroutine is correct or if I need to change anything from it (be it code, or method). Some basic information about digital filter implementation on C would be highly appreciated, too. |
|
|
RLScott
Joined: 10 Jul 2007 Posts: 465
|
|
Posted: Fri Dec 05, 2008 10:25 pm |
|
|
Quote: | ...I would like to know if the code I put above for the filtering subroutine is correct |
The problem I see with your code is that it is not structured to be a streaming process. It looks more like what you would do after all there data was collected and you then went back and filtered it all in a batch. As you pointed out, you don't have the RAM to store very much data. So look for implementations of digital filters that use a FIFO instead of an array. Systems and Signals by Oppenheim is a good text on the algorithms. _________________ Robert Scott
Real-Time Specialties
Embedded Systems Consulting |
|
|
PCM programmer
Joined: 06 Sep 2003 Posts: 21708
|
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Sat Dec 06, 2008 4:02 am |
|
|
The said thread is only covering simple moving average filters. The streaming data technique can be adapted however for more complex filter functions.
The previously coded IIR filter Y(n) = 8*X(n) - 6*X(n-1) - Y(n-1) is no usable filter, it shows infinite oscillating output in response to an input impulse. That's not a matter of coefficient quantization, it's a completely wrong filter. You may want to post the original MATLAB coefficient representation to make the design understandable.
Generally, an analog low pass should be used as anti-aliasing filter as long as the input signal isn't band limited. I understand, that the purpose of digital filtering in your design is in extracting the fundamental as a frequency measurement preprocessing. In this case, a bandpass, that also supresses low frequency interferences, may be more suitable.
I already made a comment on the problem of exact frequency determination from a time-discrete signal and still think, that it can't work without some kind of interpolation. I didn't yet recognize an operational algorithm from the discussed code snippets, may be I missed something due to casual reading.
Best regards,
Frank |
|
|
Felipe S.
Joined: 23 Oct 2008 Posts: 10 Location: Vina del Mar, Chile
|
|
Posted: Sat Dec 06, 2008 7:57 pm |
|
|
RLScott wrote: |
The problem I see with your code is that it is not structured to be a streaming process. It looks more like what you would do after all there data was collected and you then went back and filtered it all in a batch.
|
Yes, that is indeed what I did. Like I mentioned in a previous post, I first did a test program that captured a pure sinusoidal through the ADC, counted the number of samples in a fixed number of periods and finally determined if it was "in tune". Once it worked out, I simply added the filtering subroutine to that program, changing a few numbers here and there. Logic told me that it was the correct thing to do, but I was indeed sacrificing a lot of RAM unnecessarily.
Quote: |
As you pointed out, you don't have the RAM to store very much data. So look for implementations of digital filters that use a FIFO instead of an array. Systems and Signals by Oppenheim is a good text on the algorithms. |
I looked for FIFO, but the link posted by PCM Programmer gave me a much better idea of what to do.
FvM wrote: |
The previously coded IIR filter Y(n) = 8*X(n) - 6*X(n-1) - Y(n-1) is no usable filter, it shows infinite oscillating output in response to an input impulse. That's not a matter of coefficient quantization, it's a completely wrong filter. You may want to post the original MATLAB coefficient representation to make the design understandable.
|
Thanks for pointing that out, I should have realized that just by looking at the formula. It was really absurd to expect workable results with it.
I guess the mistake was that I inverted the coefficients MATLAB gave me, so instead of putting the 'b' coeffs. in the numerator and the 'a' in the denominator, I did it the other way around. What a silly mistake on my part.
I figured more mistakes I initially made as I re-calculated the coeffs. Maybe I'm still missing something, so here are the coeffs (sampling frequency = 5000 Hz, corner frequency = 400 Hz - I used the MATLAB function 'butter' to calculate these):
Code: |
b =
0.2043 0.2043
a =
1.0000 -0.5914 |
With this I figured out the following filtering algorythm:
Y(n) = (2*X(n) + 2*X(n-1) + 6Y(n-1))/10
I once again rounded the coeffs. I'm still not sure if this is the right thing to do, or if it is better to use floating point instead.
Quote: |
Generally, an analog low pass should be used as anti-aliasing filter as long as the input signal isn't band limited. I understand, that the purpose of digital filtering in your design is in extracting the fundamental as a frequency measurement preprocessing. In this case, a bandpass, that also supresses low frequency interferences, may be more suitable.
|
I'm already using an analog filter for anti-aliasing on my circuit (fc = 1000 Hz). If necessary, I'll give the bandpass a try.
With these new advices, I rewrote the code to create a subroutine that captures, filters and count samples once for every A/D conversion:
Code: |
int16 sample_buff[522];
long sample;
|
Code: |
void conv_filtro_cuenta() {
int8 in, in_ant, per;
int16 out_ant, buc;
int1 ciclongtvo = false;
buc = 0;
in_ant = 0;
out_ant = 0;
setup_adc_ports(AN0_TO_AN1|VSS_VDD); // Vref+ = Vdd, Vref- = Vss
setup_adc(ADC_CLOCK_DIV_4);
set_adc_channel(1); // RA1 as analog input
printf(lcd_putc, "\fPlay note");
delay_ms(3000);
printf(lcd_putc, "\fProcessing..");
for(per=0;per<=10;++buc) {
in = read_adc();
sample_buff[buc] = ((2l*in)+(2l*in_ant)+(6l*out_ant))/10l;
if (sample_buff[buc]>=32761) {
if (ciclongtvo==true) {
++per;
ciclongtvo=false;
};
}
else ciclongtvo=true;
if (per>=1) {
++sample;
};
in_ant = in;
out_ant = sample_buff[buc];
delay_us(90);
};
printf(lcd_putc, "\fDone");
delay_ms(1000);
muestra();
}
|
muestra() sends to another subroutine that displays the number of samples counted.
This modification made the code more efficient and understandable, as I'm no longer working with pointers, but it still need some working out for RAM efficiency. The readings are still erratic and I blame either the filter algorithm or the sample counting part which may have to be tweaked a bit. I'll continue working on it tomorrow.
Thanks all for the suggestions. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Sun Dec 07, 2008 12:45 am |
|
|
The low pass looks better now
Code: | Y(n) = (2*X(n) + 2*X(n-1) + 6Y(n-1))/10 |
However, it can be simplified. All digital first order low passes can be basically represented by the form
Code: | Y(n) = a*X(n) + (1-a)*Y(n-1) |
The difference when using a sum of X(n) and (X(n-1) is only a phase shift, that doesn't affect the transfer function. Furthermore, it's usually more effective to use a 2^n scaling factor, cause it allows to replace the division by a fast shift operation, at least with a CPU that has no division instruction. For a first order low-pass, a few 10 % frequency deviation usually doesn't matter.
You should be aware, that the filtering effect of the present low-pass function is rather small. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Sun Dec 07, 2008 5:04 am |
|
|
Some additional comments on the overall data processing and frequency measurement.
sample_buff and the respective address counter buc aren't neded in the present code. But buc may overflow and write out of bounds now. If the buffer is intended for additional sample recording, it should be operated as a ring buffer.
I see, that the sample frequency is defined by delay_us(90) and the additional loop processing time. Unfortunately, the latter isn't exactly constant, being somewhat higher in case of a zero crossing. This results in a nonlinear measurement error.
The measurement method itself (counting the number of samples for 10 periods) has limited resolution, as previously discussed. Using analog filtering and a comparator, that feeds a digital input would allow a much higher frequency resolution.
Interpolating the zero crossing time to a higher resolution is another method, but would be only meaningful with a more exact ADC sample timing. |
|
|
Felipe S.
Joined: 23 Oct 2008 Posts: 10 Location: Vina del Mar, Chile
|
|
Posted: Sun Dec 07, 2008 8:51 pm |
|
|
FvM wrote: | The low pass looks better now
However, it can be simplified. All digital first order low passes can be basically represented by the form
Code: | Y(n) = a*X(n) + (1-a)*Y(n-1) |
|
I've seen that form before, and so I simplified my own to:
Y(n) = (0.4086*X(n))+(0.5914*Y(n-1))
Like it shows, I'm now using floating point in the code.
Quote: |
You should be aware, that the filtering effect of the present low-pass function is rather small. |
I know. By what I gathered, if 'a' (a value in between zero and one) is too close to cero, the filter is an all pass, while too close to one, it kills most of the low freqs.
I modified the code in the new subroutine. Now it's like this:
Code: | void conv_filtro_cuenta() {
int1 ciclongtvo = false;
int8 in, per;
float out, out_minus_one;
out_minus_one = 0;
setup_adc_ports(AN0_TO_AN1|VSS_VDD); // Vref+ = Vdd, Vref- = Vss
setup_adc(ADC_CLOCK_DIV_4);
set_adc_channel(1); // RA1 as analog input
printf(lcd_putc, "\fPlay note");
delay_ms(3000);
printf(lcd_putc, "\fProcessing..");
for(per=0;per<=50;) {
in = read_adc();
out = (0.4086*in)+(0.5914*out_minus_one);
if (out>=128) {
if (ciclongtvo==true) {
++per;
ciclongtvo=false;
};
}
else ciclongtvo=true;
if (per>=1) {
++sample;
};
out_minus_one = out;
delay_us(166);
};
printf(lcd_putc, "\fDone");
delay_ms(1000);
muestra();
} |
As you can see, no longer using a RAM array allowed me to increase my period count to 50, just to see how much this improves the resolution. In the end, It did so considerably. I did a table based on the Proteus simulation of this new version of the program, and the results are as follow.
Each group of three frequencies have the fundamental of the string in the middle and on each side the nearest highest or lowest semitone.
Code: | fc = 400 Hz, fs = 5000, period count = 50
frequency sample count range
349.2 Hz ---> 210-212
329.6 Hz ---> 220-222
311.1 Hz ---> 235-329
261.6 Hz ---> 277-279
246.9 Hz ---> 297-299
233.0 Hz ---> 312-317
207.6 Hz ---> 350-351
196.0 Hz ---> 372-373
185.0 Hz ---> 396-400
155.5 Hz ---> 474-475
146.8 Hz ---> 500-501
136.6 Hz ---> 540-545
116.5 Hz ---> 627-628
110.0 Hz ---> 665-666
103.8 Hz ---> 696-701
87.3 Hz ---> 846-848
82.4 Hz ---> 894-899
77.8 Hz ---> 944-948
|
I think the resolution still leaves to be desired, but overall the results are much better than before. Tomorrow, I'll test this in the lab, with the actual guitar signal feeding the PIC's ADC to see how this handles it.
If the value of coeff. 'a' of the filter need to be changed a bit in order to adapt, I'll do so.
FvM wrote: |
I see, that the sample frequency is defined by delay_us(90) and the additional loop processing time. Unfortunately, the latter isn't exactly constant, being somewhat higher in case of a zero crossing. This results in a nonlinear measurement error.
|
Yes, there's a difference of 16 clock cycles (4 microsecs.) or so. I'll see how to work this out.
Quote: |
The measurement method itself (counting the number of samples for 10 periods) has limited resolution, as previously discussed. Using analog filtering and a comparator, that feeds a digital input would allow a much higher frequency resolution.
Interpolating the zero crossing time to a higher resolution is another method, but would be only meaningful with a more exact ADC sample timing. |
I'll consider all of this. Thanks again. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Mon Dec 08, 2008 12:32 am |
|
|
From your results, I calculate an actual sampling frequency of about 1500 Hz. That seems a bit low and implies e. g. that your anti-aliasing filter can't work. The float processing is obviously causing the additional delay. I previously suggested integer arithmetic and replacing the /10 division by a faster shift operation.
I understand, that the results are achieved with a signal generator, the variance should be lower to my opinion.
Regarding sampling period jitter, it may be reduced by synchronizing the processing loop by polling a timer and compensating the residual delay variation. |
|
|
Felipe S.
Joined: 23 Oct 2008 Posts: 10 Location: Vina del Mar, Chile
|
|
Posted: Mon Dec 08, 2008 2:27 pm |
|
|
FvM wrote: | I previously suggested integer arithmetic and replacing the /10 division by a faster shift operation.
|
Applying 'scaling' to replace the floating points for integers? I did this:
Code: | int32 out, out_minus_one; |
Code: | out = (4086*(int32)read_adc())+(5914*out_minus_one);
if (out>=1280000) {
if (ciclongtvo==true) {
++per;
ciclongtvo=false;
};
}
else ciclongtvo=true;
if (per>=1) {
++sample;
};
out_minus_one = out/10000;
|
It turns out, that's just as slow as the operation with floats. Removing the delay at the end of the loop and changing the scale to smaller numbers doesn't improve conversion speed.
Quote: | Regarding sampling period jitter, it may be reduced by synchronizing the processing loop by polling a timer and compensating the residual delay variation. |
Something like a CCP/TMR0 interruption?
Cheers. |
|
|
Ttelmah Guest
|
|
Posted: Mon Dec 08, 2008 3:38 pm |
|
|
You are missing the point slightly.
You can perform /12800, perhaps 50* faster than /10000 for example. _Binary_ divisions are fast.
So what you should do, is chose your scale factors, so that you need a binary division for the result.
Best Wishes |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Tue Dec 09, 2008 1:07 am |
|
|
My suggestion is, to stay within the int16 range, either signed or unsigned, as you prefer. With 10-bit AD data, this would allow for n/64 rational coefficients. As previously said, the frequency resolution of first order filters is rather poor anyway. The below approximation is, by chance, nearly exact, but also a 10 or 20% deviation wouldn't be an issue.
Code: | yn = (13*xn + 19*yn) >> 5; |
When building higher order, higher Q filters, more accurate coefficients and an accumulator resolution overhead may be needed. In this case, int16 can be too restrictive. |
|
|
Felipe S.
Joined: 23 Oct 2008 Posts: 10 Location: Vina del Mar, Chile
|
|
Posted: Sat Jan 10, 2009 10:48 am |
|
|
Hi again.
FvM, thanks to your last post I was able to come up with a really fast math operation for my filter. The difference between regular division and bit shifting is certainly astounding.
Now, I've been trying to capture the guitar signal in the lab. And I've run with a problem: the sample count is way greater than it should be for the converted guitar signal, and what's more, it follows a similar increasing pattern in all cases. Example:
(Sample count)
30657
31465
33055
35767
This keeps increasing no matter what string I play, nor the sequence. I may be wrong, but I think this could be an aliasing problem. I'm using a 2nd order Butterworth filter for anti-aliasing, with corner frequency = 720 Hz. The sampling frequency I'm using for the conversion is 5000 Hz, as I've mentioned before. My question, for those experienced in the subject: is my anti-aliasing filter right for this application, or should I use something with a higher order (say, a 4th order Butterworth)?
According to the formula here, I'm getting a -21.6 dB attenuation for half my sampling frequency. I suppose that isn't good enough, so maybe the problem is aliasing. |
|
|
FvM
Joined: 27 Aug 2008 Posts: 2337 Location: Germany
|
|
Posted: Sun Jan 11, 2009 5:56 am |
|
|
Looks like sample isn't reset before a new measurement. |
|
|
Felipe S.
Joined: 23 Oct 2008 Posts: 10 Location: Vina del Mar, Chile
|
|
Posted: Sun Jan 11, 2009 1:36 pm |
|
|
FvM wrote: | Looks like sample isn't reset before a new measurement. |
I forgot to mention that I reset the whole program before taking any new measurement. Isn't that supposed to reset the "sample" variable, too?
Greetings. |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|