CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to support@ccsinfo.com

Guitar tuner project
Goto page 1, 2, 3  Next
 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
Felipe S.



Joined: 23 Oct 2008
Posts: 10
Location: Vina del Mar, Chile

View user's profile Send private message

Guitar tuner project
PostPosted: Thu Oct 23, 2008 7:19 pm     Reply with quote

Hi all. For my grade project in college, I decided to build a digital guitar tuner based on a PIC18F2550.

The phase I'm currently on is testing the sampling capabilities of the PIC's ADC converter. The sampling frequency is 4.3 KHz and for the test I'm going to use a signal generator to "feed" the A0 pin with a sinusoidal signal with a frequency on the 80-330 Hz range. Each digital sample is sent to a register in the RAM data memory where it is stored. Then an algorithm counts the number of samples based on a fixed number of periods (10, in this case) and shows the number on a LCD display.

I've done the test in Proteus and works fairly well, but when transposing to the field, it does the weirdest readings. For example, if the frequency of the analog signal is 300 Hz, the Proteus simulation shows a sample count in the 135-137 range, which is fairly good. But in the real world, the sample count is off, even showing an absurd -7347, for example! Sometimes it even resets the program during the conversion.

So, I probably must be dealing with noise issues. The problem is, I don't know how to fix this. I've done the following:

- Passed the circuit from breadboard to etched prototyping PCB

- Included a low pass filter for anti-aliasing, with fc = 2 KHz. The op-amp used for the filter is a low noise OP07.

I know I'm missing something else, but I don't know what. I'll appreciate any help on the matter.

The code for the program is below. I wouldn't mind the "menu1" and "menu" subroutines, as those are for displaying text on the LCD. Like you can see, I'm not using interrupts:
Code:
#include <18F2550.h>

#fuses XT,NOWDT,NOPROTECT,NOLVP

#define adc=8

#use delay(clock=4000000)

#include "lcdpic18.c"

#byte port_a = 0xF80
#byte port_b = 0xF81
#byte port_c = 0xF82

#reserve 0x20:0x232

long sample;

/* LCD display subroutines---------------------------------*/

void menu1() {
 
   printf(lcd_putc, "Select:\n");

}


void menu(int valor){

   if (valor==1) {
   
      menu1();
      printf(lcd_putc, "1era  Mi");

   }

   if (valor==2) {
   
      menu1();
      printf(lcd_putc, "2da   Si");

   }

   if (valor==3) {
   
      menu1();
      printf(lcd_putc, "3era  Sol");

   }

   if (valor==4) {
   
      menu1();
      printf(lcd_putc, "4a    Re");
     
   }

   if (valor==5) {
   
      menu1();
      printf(lcd_putc, "5a    La");

   }

   if (valor==6) {
   
      menu1();
      printf(lcd_putc, "6ta   Mi");

   }

}

/*Period count subroutine---------------------------------------*/


void muestra() {

printf(lcd_putc, "\fN. samples\n= %ld", sample);

}


void cuenta1() {

int per, *p;
long buc;

p = 0x20;         


for(per=0;per<=10;) {
 
   if ((*(p+buc)!=0)&&(*(p+buc-1)==0)) {
      ++per;
      };
   ++buc;
   if (per>=1) {
   ++sample;
   }

   };

muestra();


}



/* ADC conversion Subroutine---------------------------------*/


void conversor() {

   int i, value, *p;
   long buc;

   p = 0x20;         

   setup_adc_ports(AN0|VSS_VDD);     
   setup_adc(ADC_CLOCK_DIV_4);     
   set_adc_channel(0);         
 
   printf(lcd_putc, "\fPlay note");

   for(i=1;i<=30;++i) {                 

      delay_ms(100);

           }

   printf(lcd_putc, "\fConverting..");
 
   for(buc=0;buc<=522;++buc) {       

      value = read_adc();     
      *(p+buc) = value;     
                         
      delay_us(209);           
   
      };

   printf(lcd_putc, "\fConversion lista");
   
   for(i=1;i<=10;++i) {     

      delay_ms(100);

           }

   cuenta1();           

}   


/* Main program--------------------------------------------------*/

void main() {

   int i, val;

   set_tris_a(00000001);           

   set_tris_b(00000000);     

   set_tris_c(10100000);     
   
   port_a = 0xFF;     
   port_c = 0XFF;           
         
     


   lcd_init();                 

   lcd_putc("\f");
   
   printf(lcd_putc, "\fBienvenido");

   for(i=1;i<=30;++i) {                 

      delay_ms(100);

           }

   lcd_putc("\f");             

   val=1;               

   menu(val);

   while(true) {

      if (!input(pin_c7)) {

         delay_ms(500);
         ++val;
         if (val==7) {
            val=1;
            }
         lcd_putc("\f");
         menu(val);
   
         }

      if (!input(pin_c6)) {
         
         lcd_putc("\f");
         
         conversor();
   
         }

      }


}         
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Oct 23, 2008 10:38 pm     Reply with quote

Quote:
#reserve 0x20:0x232

if ((*(p+buc)!=0)&&(*(p+buc-1)==0)) {
++per;
};

Why do this ? You should use an array. It's much cleaner, it's safe, etc.


Quote:

void cuenta1() {

int per, *p;
long buc;

p = 0x20;

for(per=0;per<=10;) {

if ((*(p+buc)!=0)&&(*(p+buc-1)==0)) {
++per;
};

'buc' is never initialized. It could be any value.


Quote:

long sample;

printf(lcd_putc, "\fN. samples\n= %ld", sample);

In CCS, a 'long' is an unsigned 16-bit integer. Use "%lu" to display unsigned 16-bit integers.


Quote:
for(i=1;i<=10;++i) {
delay_ms(100);
}

This loop can be replaced by one line. Example:
Code:
delay_ms(1000);

There are two other places that also need to be fixed.
Ttelmah
Guest







PostPosted: Fri Oct 24, 2008 3:08 am     Reply with quote

Start with a couple of minor 'comments'.
Why 'reserve' the RAM area for the data?. Just assign an array, so, have:
Code:

int8 sample_buff[530];

//Then in your subroutine:

p=sample_buff;

Let the compiler do it's job. It is clearer to other people, what is going on as well.

Now, looking at the code, the first big problem, is how improbable it is, that you will ever 'see' '0'. It appears that you are possibly coupling an AC signal, swinging about '0v', directly into the input pin?. If so, then there may well be problems with the drive circuit. Remember that the PIC inputs, are _not_ designed to go below 0v. There is a protection diode present, which will clamp the inputs from going more than about 0.6v below this point, and if you feed a signal from (say) an op-amp, into an input clamped like this, the result can be very nasty spikes and overshoot when the signal returns above 0v. The input signal, should have a DC bias applied equivalent to half the ADC Vref voltage (in your case half the supply voltage), so that it swings about this halfway point, _not_ 0v.

Question. What input filtering have you got?. You want to filter the signal, so that at least 90% of any signal above about 2KHz, is removed. Otherwise in the real world, you are going to suffer from aliasing as higher frequency components 'beat' with the sampling frequency, and are seen as incorrect readings.

Then, instead of looking for '0', look for the point where the signal drops below the half way voltage (128). Even with a signal, that is actually 'at' 0v, in the original, unless your system is noise free, there is a very good chance of seeing individual counts. By looking for the reversal, you avoid this. So, your 'edge' search, becomes something like:
Code:

void cuenta1() {
   int1 last_below=false;

   sample=0;
   for (per=0;per<=10;) {
      if (sample_buff[sample++]>128) {
        //Here I have a sample above the mid point
        if (last_below) {
           ++per;
           last_below=false;
        }
        else last_below=true;
      }
      if (sample>522) per=11;
   }
   muestra();
}


Best Wishes
Guest








PostPosted: Fri Oct 24, 2008 5:20 pm     Reply with quote

Thanks a lot for the answers.

PCW Programmer, thanks for pointing out my newbie mistakes. This is actually my first program in C language, so a couple of inefficiencies in programming were a given, I guess.

Ttelmah, I was aware of that property of the pins, as I noticed how some kind of internal diode cuts the signal below zero, watching with an oscilloscope the AC analog signal entering the A0 pin. I thought of just letting it be, but since it may cause me problems like the one you wrote about, I may as well do as you say and add a DC bias to the analog signal.

I modified the program considering all of that, and it runs quite well on Proteus. I'll let you know for sure when I run the new configuration on the lab.

About the filter, I'm using a Butterworth 2nd order with fc = 2 Khz, although it may be better to change that to a lower value, like 1.5 or even 1 Khz just to be sure that no signal above 2 Khz makes it.

Thank you both for pointing out the use of arrays, I actually read about them somewhere else in this forum, but never bothered to learn to use them. I applied them to the program and it certainly looks much more ordered that way.

BTW, I also read about the use of sleep mode in ADC in order to avoid PIC noise issues. How is that done?
Felipe S.



Joined: 23 Oct 2008
Posts: 10
Location: Vina del Mar, Chile

View user's profile Send private message

PostPosted: Fri Oct 24, 2008 5:21 pm     Reply with quote

^^ That is me. Forgot to log in. Razz
Felipe S.



Joined: 23 Oct 2008
Posts: 10
Location: Vina del Mar, Chile

View user's profile Send private message

PostPosted: Mon Oct 27, 2008 2:50 pm     Reply with quote

Ok, nevermind, I found this example of how to use sleep mode in a/d conversions, and modified the program accordingly.

I'm happy to say that now the display gives me much more stable readings. There's still some problems in reaching a steady and low-range interval of sample numbers. Here I feel I must explain myself.

The frequencies I'm testing with my program are as follows: 329.6, 246.9, 196, 146.8, 110, 82.4 Hz. Each of them corresponds to the first harmonic of the open note in each of the guitar's strings (we are talking about standard tuning, for those who know the instrument). The program is designed so that at the end of the whole process, the display shows me a number, which is the number of samples taken in 10 periods of the converted signal. This number must be inside a certain range, and the smaller this range, the better. For example (I'm guessing here):

329.6 Hz ----> 53-56
246.9 Hz ----> 60-62
196 Hz ----> 68-70
146. 8 Hz ----> 75-78
110 Hz ----> 84-86
82.4 Hz ----> 93-98

Now, this is the how the a/d conversion loop looks now:

Code:
   for(buc=0;buc<=522;++buc) {         

      disable_interrupts(global);
      enable_interrupts(INT_AD);
      clear_interrupt(INT_AD);
      read_adc(ADC_START_ONLY);       
      sleep();
      delay_cycles(1);
      valor = read_adc(ADC_READ_ONLY);

      disable_interrupts(INT_AD);
      enable_interrupts(GLOBAL);
      *(p+buc) = valor;         
                               
      delay_us(209);           
      };


The comments were in spanish, so I took them out. Anyways, the readings weren't too good with the 209 useg. delay (last line inside the loop), so I modified it to increase the sampling frequency and get more samples-per-period, figuring it would give me a better reading. With a 10 usegs. delay, it read as follows:

329.6 Hz ----> 53-56
246.9 Hz ----> 67-71
196 Hz ----> 59-64
146. 8 Hz ----> 66-74
110 Hz ----> 63-76
82.4 Hz ----> 62-75

This is certainly less than good, but still much better than the readings given previously! I'm guessing there must still be some noise problems. I'll try and see what to do and keep you informed about it, but any advice is welcome!

Thanks again. Smile
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

PostPosted: Mon Oct 27, 2008 3:13 pm     Reply with quote

I don't see how you are going to measure the frequency of a guitar string with sufficient accuracy to be useful. A typical guitar tuner can measure the pitch to within 0.057%. Are you measuring the pitch based only on the number of samples taken in 10 cycles of the tone? If so, then you had better be taking about 1700 samples in those 10 cycles. If the pitch is 329.6 Hz, as it is in your highest string, then those 1700 samples would have to be taken in 30 mSec, so one sample would have to be taken in 17 usec. Is that what you hope to do? Take an A/D reading every 17 uSec? Or do I totally misunderstand your method?
_________________
Robert Scott
Real-Time Specialties
Embedded Systems Consulting
Guest








PostPosted: Thu Dec 04, 2008 8:27 am     Reply with quote

Hello again. Sorry for not posting updates.

About the previously explained sampling issue, I figured it out. Turns out it wasn't a noise problem like I thought, but a mistake in the sampling counting subroutine. It runs perfectly now, with a signal generator as input. I must stop blaming noise for all the problems I run into. Razz

RLScott, despite seeing similar guitar tuning projects on the Internet using a 5000 Hz sampling frequency (or something along that value), you made a valid point there: how can I be sure that with that sampling freq. I can get an accurate result in tuning? Well, I did a test with the lab's signal generator last week. It turns out it has an error range of about 2.5 Hz on each side of the fundamental frequency of the first string (for example, if the fundamental of a perfectly tuned E first string is 329.6 Hz, my circuit also recognized as "tuned" a limit of 332.1 Hz on one side, and 327.1 Hz on the other), but that range got shorter and shorter as I went up the strings, for obvious reasons (namely, more samples per period, as the frequency in each string got lower and lower).

I found out that there isn't much of a difference in tone between, say, a 329.6 Hz tone and a 332.1 Hz tone and unless you have damn good hearing, you won't notice the difference (my ear is pretty good, so I noticed some minor difference in tone). Still, I'll consider what you said, even though I guess I need to increase the sampling freq just a bit (to, say, 6000 Hz) in order to get very accurate results.

Now, I'm doing the digital filtering part, and here's another part where I need the help of someone more experienced, as I'm a total newbie on the subject. I read that the best choice for the PIC's limited arithmetic capabilities would have to be a butterworth filter of order 1. Using MATLAB to figure out the coefficients, I came up with this formula for the first string (corner frequency = 400 Hz):

Y(n) = 8*X(n) - 6*X(n-1) - Y(n-1)

I approximated the values of the coeffs. which initially gave 7.794 and 5.795 respectively, because I want to work with ints, not with floats, although I'm not sure how much this will affect the result of the filtering (maybe a lot, that's where I need some advice). For this, I'm using pointers, so my filtering subroutine is as follows:

Code:
void filtro() {

   int8 *p1;
   int16 *p2, term;
   
   p1 = sample_buff;   //this is the RAM array for the unfiltered sampled signal
   p2 = sample_filt;   //this is the RAM array for the filtered signal
   
    /* Cálculo de Y(0), Y(1), Y(2), etc. */

   *p2 = (8l*(*p1));   // Y(0)

   for(term=1;term<=522;++term){

      *(p2+term) = (8l*(*(p1+term)))-(6l*(*(p1+(term-1))))-(*(p2+(term-1)));   // Y(1), Y(2), Y(3), etc.

      };

   return;

}               
       


The result of the filtering would be in the form of 16-bit values. So, I'll use the following subroutine for counting the filtered samples (following Ttelmah's advice):

Code:

void cuenta() {

long sample;

int per;

int1 ciclongtvo=false;

long term;

sample = 0;

term = 0;

for(per=0;per<=10;term++) {
   
   if (sample_filt[term]>=32761) {
      if (ciclongtvo==true) {
            ++per;
            ciclongtvo=false;
            };
      }
   
   else ciclongtvo=true;
 

   if (per>=1) {
   ++sample;
   };
   }   

   return;

}


BTW, the int1 'ciclongtvo' indicates when the signal passes to its negative semicycle.

Simulating in Proteus with a signal generator as input (that is, a pure senoidal signal), I figured that it should give me stable results, but it doesn't. The sample count instead give me extremely erratic values. I'm not sure what I'm doing wrong, so I'll appreciate any advice you may give me, specially concerning basics about digital filtering in CCS.

Thanks in advance.
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

PostPosted: Thu Dec 04, 2008 9:25 am     Reply with quote

Quote:
...despite seeing similar guitar tuning projects on the Internet using a 5000 Hz sampling frequency (or something along that value), you made a valid point there: how can I be sure that with that sampling freq?...

The problem is not just the sample rate. It is the extremely short sample period of 10 cycles. You need to sample for at least 100 cycles for the notes you are measuring to get sufficient accuracy.
Quote:

..I found out that there isn't much of a difference in tone between, say, a 329.6 Hz tone and a 332.1 Hz tone and unless you have damn good hearing...

You are way off on that one. Musical pitch is measured in cents on a logarithmic scale. 100 cents is the change from a 'B' to a 'C' (a single half-step). 1200 cents then is an octave. In that scale, the difference between 329.6 amd 332.1 is more than 13 cents. That is huge! Pianos are tuned to an accuracy of 0.3 cents. Commercial guitar tuners can resolve 1 cent. And you think 13 cents is nothing?

Your sampling rate of 5000 is OK. You don't need to raise it, especially if you want to leave enough time to do your Butterworth filter. You just need to average over a longer period. What's the rush? Anyone using a guitar tuner expects to see a delay of at least a half second in the indicator.
_________________
Robert Scott
Real-Time Specialties
Embedded Systems Consulting
Guest








PostPosted: Thu Dec 04, 2008 5:44 pm     Reply with quote

RLScott wrote:

The problem is not just the sample rate. It is the extremely short sample period of 10 cycles. You need to sample for at least 100 cycles for the notes you are measuring to get sufficient accuracy.


I think I see your point, however, sampling and filtering 100 cycles would need a huge amount of RAM, and I'm already using 77% of the PIC's data memory in both the sampled signal and the filtered signal arrays. I would need at least 14K of RAM, which would require 7 2K RAM chips, and I can't get any higher value than that here.

Alternately one can use the PIC's flash memory, I guess. I'll look into that.

Quote:

You are way off on that one. Musical pitch is measured in cents on a logarithmic scale. 100 cents is the change from a 'B' to a 'C' (a single half-step). 1200 cents then is an octave. In that scale, the difference between 329.6 amd 332.1 is more than 13 cents. That is huge! Pianos are tuned to an accuracy of 0.3 cents. Commercial guitar tuners can resolve 1 cent. And you think 13 cents is nothing?


Numerically, of course it is a considerable difference. I'd love to get a perfect accuracy, but due to time constrains I won't be able to.
Felipe S.



Joined: 23 Oct 2008
Posts: 10
Location: Vina del Mar, Chile

View user's profile Send private message

PostPosted: Thu Dec 04, 2008 5:46 pm     Reply with quote

^^ Me. Forgot to log in again.
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

PostPosted: Thu Dec 04, 2008 6:15 pm     Reply with quote

Quote:
..sampling and filtering 100 cycles would need a huge amount of RAM...

Not at all. You process the samples through your filter as you take them. There is no need to store a big array. Just filter as you go, detect zero-crossings as you go, count cycles as you go, count samples as you go, and when you have reached 100 cycles and you detect a zero-crossing, the number of samples processed tells you everything you need to know about the pitch. It is just as easy to process 100 cycles as 1000 cycles. It just takes longer with 1000.
_________________
Robert Scott
Real-Time Specialties
Embedded Systems Consulting
FvM



Joined: 27 Aug 2008
Posts: 2337
Location: Germany

View user's profile Send private message

PostPosted: Fri Dec 05, 2008 12:16 am     Reply with quote

I'm not aware of the technology of today's simple (1 cent accurate) tuners, but I wonder if they use digital signal processing as you intended to do. If so, they may utilize more specialized hardware.

Previous designs have been using analog filtering and e. g. multicycle period measurement. That means to have a defined measurement window, determining the exact location of the first and last zero crossing within and the number of cycles in-between. Measurement accuracy is only limited by signal jitter and clock frequency related to measurement interval.

With a sampled digital signal of rather low sampling rate (e. g. 5k), an individual sample has a huge timing uncertainty without utilizing the amplitude information at the same time. To achieve a zero crossing measurement sufficient for a cent accurate pitch determination based on sampled data, you need to interpolate the samples to a higher timing resolution. As a prerequisite, the sampling process itself must have a low jitter, which may be difficult with a pure software based sampling.

All these operations are basically simple in DSP, but may overstretch a PIC's resources. You need a clever application layout to overcome these limitations, if feasible at all.

Good luck,
Frank
RLScott



Joined: 10 Jul 2007
Posts: 465

View user's profile Send private message

PostPosted: Fri Dec 05, 2008 7:15 am     Reply with quote

FvM wrote:
...With a sampled digital signal of rather low sampling rate (e. g. 5k), an individual sample has a huge timing uncertainty without utilizing the amplitude information at the same time...

You make some very good points about averaging. However I would like to say something in defense of the 5K sample rate. It is really not bad for this application. Going to a higher sample rate may not be much better. The waveform of a guitar string has lots of harmonics, and those harmonics are not true harmonics, but are slightly inharmonic, meaning they are not phase-locked to the fundamental frequency. Therefore the waveform is not exactly periodic. If you look at it on a scope you will see the shape changing with time as the various near-harmonics slide through different phases. This makes it inherently difficult to identify a zero-crossing without jitter, regardless of the sample rate. The filtering that is done before this point, whether it be in analog or digital form, will help to reduce the effect of these near-harmonics. But it will not eliminate the effect. Overall, I would say the easiest way to increase accuracy is to increase the averaging period - until you get to the point where the user is bugged by the delay in response.

I agree with you about the DSP. It will be a real challenge to implement an effective real-time Butterworth filter in the PIC18F series.
_________________
Robert Scott
Real-Time Specialties
Embedded Systems Consulting
FvM



Joined: 27 Aug 2008
Posts: 2337
Location: Germany

View user's profile Send private message

PostPosted: Fri Dec 05, 2008 9:28 am     Reply with quote

I completely aggree. I didn't opt for a higher sampling rate, just wanted to clarify, that counting zero crossings probably wouldn't be enough, at least with a limited measuring time respectively when intending a fast response. There are different options to estimate a pitch accurately from a discrete spectrum or to determine it a from a limited length sample, I think.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Goto page 1, 2, 3  Next
Page 1 of 3

 
Jump to:  
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