# Reading frequency with a PIC16F877?

#### GuitarMan216

Joined Oct 6, 2011
18
Hey guys, I've decided to start a little guitar tuner project, using assembly with a PIC16F877. I have the circuit set up, so my guitar output is essentially a square wave that I can go into the pic with. My DMM is reading the right frequencies and voltage there, so I know that's good.

My issue is, I'm really green to this stuff, so I'm not sure how to start with the programming and input. Is there a way I could increment a timer every time the input is high, and compare say 20 input pulses with the external oscillator cycles in the time frame it took? Or is there an easier way? Any help would be appreciated. Thanks!

#### ErnieM

Joined Apr 24, 2011
8,010
There are several timers in there and at least one of them has a setting to use an external pin as the clock: that means it is "increment a timer every time the input is high" (actually it is edge triggered). You can cleat the timer register, wait a specific interval, then read the timer register and there you have counts per your specific interval, or the inverse of frequency.

If you have nothing else happening just waiting a given number of statements can give you the specific interval.

#### GuitarMan216

Joined Oct 6, 2011
18
There are several timers in there and at least one of them has a setting to use an external pin as the clock: that means it is "increment a timer every time the input is high" (actually it is edge triggered). You can cleat the timer register, wait a specific interval, then read the timer register and there you have counts per your specific interval, or the inverse of frequency.

If you have nothing else happening just waiting a given number of statements can give you the specific interval.
Thanks for the quick reply! I just found this forum the other day, and it seems like a goldmine.

I'm just unsure how this code would go, would I want to create some sort of loop with some set value for the TMR?:

banksel TMR1
movlw 0xFF00
movwf TMR1
btfsc TMR1
goto whatever

Would that then allow the timer to increment about 30 times until it's cleared, and then somehow read the interval?

Sorry, I know I'm being incredibly vague.

#### Markd77

Joined Sep 7, 2009
2,806
There is a capture mode on this PIC, which will store the value of timer1, and optionally trigger an interrupt, every 16 rising edges (or 1,2,4,8) of the CCP pin.
timer1 doesen't get reset so you just subtract the previous value. Ignore the first result, it won't be accurate unless you put in extra effort.
Have a look at section 8 of the datasheet for more info.

#### GuitarMan216

Joined Oct 6, 2011
18
There is a capture mode on this PIC, which will store the value of timer1, and optionally trigger an interrupt, every 16 rising edges (or 1,2,4,8) of the CCP pin.
timer1 doesen't get reset so you just subtract the previous value. Ignore the first result, it won't be accurate unless you put in extra effort.
Have a look at section 8 of the datasheet for more info.
Thanks man. I've been reading up in section 8 for the past hour or so. This stuff is just so confusing, that I'm having a hard time getting started.

#### GuitarMan216

Joined Oct 6, 2011
18
Ok, so I've spent some time in Section 8, and am starting to get an idea. I switched over to C, so I'm a little more comfortable. I'm using Hi-Tech C.

Here's my code so far, but I have no clue if I'm anywhere close. Any insight?

Thanks!

Rich (BB code):
#include <pic16f877.h> //Microcontroller header file

#define E_S		164.81	//Low E(2nd harmonic)
#define A_S		220.00
#define D_S		293.67
#define G_S		392.00
#define B_S		493.88
#define EH_S	659.26	//High E(2nd harmonic)

void main()
{

unsigned int TIM1, TIM2, capold, capnew, diff, TMRdiff;

TRISC=0b00000000; //Setting PORTC pins to inputs
TRISB=0b11111111; //Setting PORTB pins to outputs
T1CON=0b00001001; //Initiating TMR1(Fosc/4)
CCP1CON=0b00000110; //Capture every 4th rising edge
TMR1IF=0;

float FREQ;

Main:

if(CCP2IF) //Timer1 Register Capture
{
capold=CCPR1H;
TIM1=TMR1;
Loop:

if(CCP2IF)
{
capnew=CCPR2H;
TIM2=TMR1;
goto StringDetect;
}
goto Loop;
}

goto Main;

StringDetect:

diff=capnew-capold;
TMRdiff=TIM2-TIM1;

FREQ=(diff/TMRdiff)*1000000*4; //Multiply by 4, since it's finding every 4th trigger

if(FREQ<((A_S+E_S)/2))
{
if(FREQ>70.00)
{
if(FREQ<(E_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(E_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((E_S-1.0)<FREQ<(E_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
if(FREQ<((D_S+A_S)/2))
{
if(FREQ>((A_S+E_S)/2))
{
if(FREQ<(A_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(A_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((A_S-1.0)<FREQ<(A_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
if(FREQ<((G_S+D_S)/2))
{
if(FREQ>((D_S+A_S)/2))
{
if(FREQ<(D_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(D_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((D_S-1.0)<FREQ<(D_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
if(FREQ<((B_S+G_S)/2))
{
if(FREQ>((G_S+D_S)/2))
{
if(FREQ<(G_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(G_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((G_S-1.0)<FREQ<(G_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
if(FREQ<((E_S+B_S)/2))
{
if(FREQ>((B_S+G_S)/2))
{
if(FREQ<(B_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(B_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((B_S-1.0)<FREQ<(B_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
if(FREQ>((EH_S+B_S)/2))
{
if(FREQ<1000.00)
{
if(FREQ<(EH_S-1.0)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(EH_S+1.0)){
RB2=1;
RB1=0;
RB0=0;
}
if((EH_S-1.0)<FREQ<(EH_S+1.0)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
}

Last edited by a moderator:

#### Markd77

Joined Sep 7, 2009
2,806
I don't know much about C, but the way I'd do it is:
every CCP1IF (don't confuse CCP1 and CCP2, they are seperate counters):
store CCPR1H and CCPR1L as a 16 bit C variable (capold),
subtract that from the current value of CCPR1H and CCPR1L (I think you can use CCPR1H*256+CCPR1L),
make sure that the case where TMR0 has changed from FFFF to 0000 during the period doesn't cause problems,
compare that to precalculated periods, eg for 220Hz use (1000000*4)/220 = 18182
This saves clock cycles and program memory.

#### GuitarMan216

Joined Oct 6, 2011
18
I don't know much about C, but the way I'd do it is:
every CCP1IF (don't confuse CCP1 and CCP2, they are seperate counters):
store CCPR1H and CCPR1L as a 16 bit C variable (capold),
subtract that from the current value of CCPR1H and CCPR1L (I think you can use CCPR1H*256+CCPR1L),
compare that to precalculated periods, eg for 220Hz use (1000000*4)/220 = 18182
This saves clock cycles and program memory.
Thanks again Mark! The CCPR1H*256+CCPR1L helps. I'll throw it in, do a little more research and see what pops up.

#### ErnieM

Joined Apr 24, 2011
8,010
Hey GuitarMan: first off, where is your jungle band?

I'm trying to follow your work here but I'm confused what you are doing. You said you have a nice square wave of your guitar note, what pin are you applying that to the PIC?

Why would that square wave be the 2nd harmonic as the comments in your code suggest? I would expect it to be the fundamental but I'm no player.

Also, using HiTech C they define the symbol "CCPR2" for your processor that gives you the whole 16 bit value of Timer 1 in one shot.

I'm still trying to figure out the settings for Timer1 to use T1CKI an the external clock If that is how you are doing it then I don't check your T1CON setting (but don't have a new value for you).

#### GuitarMan216

Joined Oct 6, 2011
18
Hey GuitarMan: first off, where is your jungle band?

I'm trying to follow your work here but I'm confused what you are doing. You said you have a nice square wave of your guitar note, what pin are you applying that to the PIC?

Why would that square wave be the 2nd harmonic as the comments in your code suggest? I would expect it to be the fundamental but I'm no player.

Also, using HiTech C they define the symbol "CCPR2" for your processor that gives you the whole 16 bit value of Timer 1 in one shot.

I'm still trying to figure out the settings for Timer1 to use T1CKI an the external clock If that is how you are doing it then I don't check your T1CON setting (but don't have a new value for you).
Hey Ernie, first off, thanks for the response.

Ok, I'm about as new to this as can be, so I don't even know what a jungle band is. Whoops lol.

My computer died, and I left the charger, so I don't have all the info right now, but I believe I was going into RC2.

As far as the 2nd harmonic, I tune by playing a 12 fret harmonic(One octave above), which would give that frequency, with a more pure and accurate signal. The fundamental if you play it open, is less "stable", so you have more harmonics. Not sure if I made sense there.

I don't know what you mean by: "Also, using HiTech C they define the symbol "CCPR2" for your processor that gives you the whole 16 bit value of Timer 1 in one shot."

Again, thanks so much for your help. I can picture you guys shaking your head staring at the computer monitor, but hey, I gotta start somewhere right?

#### Markd77

Joined Sep 7, 2009
2,806
Also, using HiTech C they define the symbol "CCPR2" for your processor that gives you the whole 16 bit value of Timer 1 in one shot.
This would mean you can use CCPR1 instead of CCPR1H*256+CCPR1L, good call.

I'm still trying to figure out the settings for Timer1 to use T1CKI an the external clock If that is how you are doing it then I don't check your T1CON setting (but don't have a new value for you).
This would be for the original way that GuitarMan suggested, and would be a valid method.
I think the easier method is setting Timer1 on the internal clock and using capture mode.

#### GuitarMan216

Joined Oct 6, 2011
18
This would mean you can use CCPR1 instead of CCPR1H*256+CCPR1L, good call.

This would be for the original way that GuitarMan suggested, and would be a valid method.
I think the easier method is setting Timer1 on the internal clock and using capture mode.
Ah, so CCPR1 stores the capture info? And can I just say: NewCap=CCPR1?

So you're saying I don't even need the external oscillator? I'm not sure how the internal clock works...

#### Markd77

Joined Sep 7, 2009
2,806
Sorry for confusing you, I've been less than precise with some of my descriptions.
The 16F877 needs an external oscillator, usually a crystal.
What I've been calling the internal clock is the instruction cycle clock, Tcy, which is 1/4 of the external oscillator.
The method I've been describing is to set Timer1 to the instruction cycle clock and use capture mode to make it store the Timer1 value every (n) times that a CCP pin toggles.
The other option would be to set Timer1 to counter mode (so your guitar input would become an external clock on the T1CKI pin and Timer1 would run at the frequency of the guitar) and have a timer based on the instruction cycle clock.
Both methods would work, but I think method 1 is simpler.
I hope I've cleared it up a bit.

#### GuitarMan216

Joined Oct 6, 2011
18
Ok guys, I have the code "almost" working. the middle LED (RB1), is constantly on though, and the side LED's just flicker when it's out of tune. It seems to be reading the frequency right, but the output is messed up. Any suggestions?

Rich (BB code):
#include <htc.h> //Microcontroller header file

#define E_S		164.81	//Low E(2nd harmonic)
#define A_S		220.00
#define D_S		293.67
#define G_S		392.00
#define B_S		493.88
#define EH_S	659.26	//High E(2nd harmonic)
#define OSC     4000000L

void main()
{

unsigned int capold, capnew, cap, FREQ;

INTCON = 0b11000000;
PIE1 = 0b00100000;
PIR1 = 0b00000000;
TRISC=0b00000100; //Setting RC2 to input
TRISB=0b00000000; //Setting PORTB pins to outputs
T1CON=0b00110001; //Initiating TMR1(Fosc/4)(1:8 prescaler)

TMR1L = 0b00000000;		// Set Timer1 register to zero
TMR1H = 0b00000000;
CCPR1L = 0b00000000;		// Set CCP register to 0
CCPR1H = 0b00000000;
CCP1CON=0b00000110; //Capture every 4th rising edge

TMR0IE = 0;     // Disable interrupt on TMR0 overflow
PEIE = 1;       // Enable peripheral interrupts
GIE = 1;        // Global interrupt enable
CCP1IF = 0;		//Clear interrupt flag

Main:

{
if (CCP1IF)               // A TMR1 register capture occurred
{
capold = capnew;
capnew = 256*CCPR1H + CCPR1L;

if (capnew > capold)
{
cap = capnew - capold;
FREQ=(((OSC/4)*4)/(8*(cap)));

if(FREQ<((A_S+E_S)/2))
{
if(FREQ>70.00)
{
if(FREQ<(E_S-1)){
RB0=1;
RB1=0;
RB2=0;
}
if(FREQ>(E_S+1)){
RB2=1;
RB1=0;
RB0=0;
}
if((E_S-1)<FREQ<(E_S+1)){
RB1=1;
RB2=0;
RB0=0;
}
}
}
}

goto Main;
}
}
}

#### GuitarMan216

Joined Oct 6, 2011
18
Nevermind, I got it!!! Thanks so much for the help guys, it's working great! Now I might add a servo motor for automated tuning.

#### djsfantasi

Joined Apr 11, 2010
5,839
Glad you got it, but could you share what you found for us following the thread?

Thanks

#### GuitarMan216

Joined Oct 6, 2011
18
Glad you got it, but could you share what you found for us following the thread?

Thanks
Sure, after a lot of research and reading, as well as your guys suggestions, I messed with the code a bunch and ended up with the code below. It wasn't so much one thing that I found, but a bunch. I'm trying to make it even better, but as of now, it's working pretty well.

Rich (BB code):
/*********************************************
/
/PIC based guitar tuner
/
/Ryan Monteleone
/
**********************************************/

#define PIC_CLK 4000000 //External Oscillator speed

#define NOTE	1.059463094	//The 12th root of 2(The difference between each note)

#define E_S		164.81	//Low E Standard(2nd harmonic)
#define A_S		220.00
#define D_S		293.67
#define G_S		392.00
#define B_S		493.88
#define EH_S	659.26	//High E Standard(2nd harmonic)

#define E_F		E_S/NOTE	//Low E Flat(2nd harmonic)
#define A_F		A_S/NOTE
#define D_F		D_S/NOTE
#define G_F		G_S/NOTE
#define B_F		B_S/NOTE
#define EH_F	EH_S/NOTE	//High E Flat(2nd harmonic)

#define OSC     4000000L //Defining a long variable for the clock speed

void main()
{

unsigned int capold, capnew, cap, FREQ;	//Creating unsigned variables to capture the frequency

int x=0, y=0;	//Creating delay variables

INTCON= 0b11000000;	//Enabling all masked interrupts(including peripherals)
PIE1=   0b00100000;	//Enabling the USART receive interrupt
PIR1=   0b00000000;	//
TRISC=  0b00000110; //Setting RC2 to input
TRISB=  0b00000000; //Setting PORTB pins to outputs
TRISA=  0b00000000; //Setting PORTA pins to outputs for 7-segment display
T1CON=  0b00100001; //Initiating TMR1(Fosc/4)(1:8 prescaler)

TMR1L=  0b00000000;	// Set Timer1 register to zero
TMR1H=  0b00000000; //
CCPR1L= 0b00000000;	// Set CCP register to 0
CCPR1H= 0b00000000; //
CCP1CON=0b00000110; //Capture every 4th rising edge

TMR0IE=0;   // Disable interrupt on TMR0 overflow
PEIE=1;     // Enable peripheral interrupts
GIE=1;      // Global interrupt enable
CCP1IF=0;	//Clear interrupt flag

RB0=0;	//clearing pins
RB1=0;	//
RB2=0;	//

RA0=0;	//Turning on all 7 LED's of the 7-segment display
RA1=0;	//
RA2=0;	//
RA3=0;	//
RA4=0;	//
RA5=0;	//

Main:

TRISC= 0b00000110;
{
if (CCP1IF) //TMR1 capture
{
capold=capnew;
capnew=256*CCPR1H+CCPR1L; //Storing capture value in capnew

if(capnew>capold)	//Verifying both captures are independent
{
cap=capnew-capold;
FREQ=(((OSC/4)*4)/(4*(cap)));	//Calculating the frequency based on timer, and 4 rising edges
if(FREQ<((A_S+E_S)/2))
{
if(FREQ>100.00)	//Filtering out any low frequency noise
{
if(FREQ<(E_S-3.5)){	//Setting the tolerance
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00011000;	//7-segment display

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;	//Start the capture again
}
if(FREQ>(E_S+3.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00011000;

for(x;x<9999;x++){
for(y;y<6;y++){
}
}
goto Main;
}
if((E_S-3.5)<FREQ<(E_S+3.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00011000;

for(x;x<9999;x++){
for(y;y<6;y++){
}
}
goto Main;
}
}
}
if(FREQ<((D_S+A_S)/2))
{
if(FREQ>((A_S+E_S)/2))
{
if(FREQ<(A_S-3.5)){
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00000100;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if(FREQ>(A_S+3.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00000100;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if((A_S-3.5)<FREQ<(A_S+3.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00000100;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
}
}
if(FREQ<((G_S+D_S)/2))
{
if(FREQ>((D_S+A_S)/2))
{
if(FREQ<(D_S-3.5)){
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00100010;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if(FREQ>(D_S+3.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00100010;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if((D_S-3.5)<FREQ<(D_S+3.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00100010;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
}
}
if(FREQ<((B_S+G_S)/2))
{
if(FREQ>((G_S+D_S)/2))
{
if(FREQ<(G_S-6.5)){
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00010001;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if(FREQ>(G_S+6.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00010001;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if((G_S-8.5)<FREQ<(G_S+6.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00010001;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
}
}
if(FREQ<((EH_S+B_S)/2))
{
if(FREQ>((B_S+G_S)/2))
{
if(FREQ<(B_S-12.5)){
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00110000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if(FREQ>(B_S+12.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00110000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if((B_S-12.5)<FREQ<(B_S+12.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00110000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
}
}
if(FREQ>((EH_S+B_S)/2))
{
if(FREQ<1000.00)
{
if(FREQ<(EH_S-14.5)){
RB0=0;
RB1=0;
RB2=1;

TRISA=0b00011000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if(FREQ>(EH_S+14.5)){
RB2=0;
RB1=0;
RB0=1;

TRISA=0b00011000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
if((EH_S-14.5)<FREQ<(EH_S+14.5)){
RB1=1;
RB2=0;
RB0=0;

TRISA=0b00011000;

for(x;x<9999;x++){ //creating a delay
for(y;y<6;y++){
}
}
goto Main;
}
}
}
}

goto Main;
}
}
}