# Digital tachometer using PIC18f code

#### Qual

Joined Aug 5, 2016
48
Hi All,

Rigging up Digital Tachometer using PIC18F45K50 and IR Module, connected IR Module output to RA4 of Pic,

Followed http://embedded-lab.com/blog/how-to-...ion-technique/

And here is the code modiified

C:
unsigned long RPM_Value;

// Define LCD module connections.
sbit LCD_RS at RB5_bit;
sbit LCD_EN at RB4_bit;
sbit LCD_D4 at RA0_bit;
sbit LCD_D5 at RA1_bit;
sbit LCD_D6 at RA2_bit;
sbit LCD_D7 at RA3_bit;
sbit LCD_RS_Direction at TRISB5_bit;
sbit LCD_EN_Direction at TRISB4_bit;
sbit LCD_D4_Direction at TRISA0_bit;
sbit LCD_D5_Direction at TRISA1_bit;
sbit LCD_D6_Direction at TRISA2_bit;
sbit LCD_D7_Direction at TRISA3_bit;
// End LCD module connection definition
sbit IR_Tx at RA4_bit;

// Define Messages
char message1[] = "Tachometer";
char *RPM = "00000 RPM";
void Display_RPM(unsigned long num){
RPM[0] = num/10000 + 48;
RPM[1] = (num/1000)%10 + 48;
RPM[2] = (num/100)%10 + 48;
RPM[3] = (num/10)%10 + 48;
RPM[4] = num%10 + 48;
Lcd_Out(2,4,RPM);
}

void main() {
CMCON = 0x07; // Disable comparators
ADCON1 = 0x0F; // Disable Analog functions
TRISC = 0x00;
TRISB = 0x00;
PORTA = 0x00;
TRISA = 0b00010000;
T0CON = 0b10011000; // TMR0 as 16-bit counter,
Lcd_Init(); // Initialize LCD
Lcd_Cmd(_LCD_CLEAR); // CLEAR display
Lcd_Cmd(_LCD_CURSOR_OFF); // Cursor off
Lcd_Out(1,4,message1); // Write message1 in 1st row
do {

T0CON.TMR0ON = 1;
TMR0L = 0;
TMR0H = 0;
IR_Tx = 1;

Delay_ms(1000); // Wait for 1 sec
IR_Tx = 0;
T0CON.TMR0ON = 0; // Stop the timer
RPM_Value = (256*TMR0H + TMR0L)*60;
Display_RPM(RPM_Value);
} while(1); // Infinite Loop
}
i get display on LCD, but default it displays 3963 rpm and shows no much changes. Not measuring increase or decrease of motor . Is there any correction needed in code, bit confused with different approaches for tachometer.

Modrators note: used code tags

Last edited by a moderator:

#### Qual

Joined Aug 5, 2016
48
Hi ,

I have changed timer0 from 16 bit to 8 bit, even then i get constant value of display.

Is there any correction required in code.

Unable to figure it out.

#### ErnieM

Joined Apr 24, 2011
8,196
Have you checked your hardware to insure you are getting pulses to the PIC input pin?

#### Qual

Joined Aug 5, 2016
48
Have you checked your hardware to insure you are getting pulses to the PIC input pin?
Yes i am getting 3.6V near PIC input pin and at output of IR Tx Rx Module.

#### joeyd999

Joined Jun 6, 2011
4,477
Line 39: T0CON =0b10011000 sets timer 0 as a timer.

Bit 5 must be 1 for counter (transition on T0CKI) operation:

T0CON =0b10111000

#### ErnieM

Joined Apr 24, 2011
8,196
Yes i am getting 3.6V near PIC input pin and at output of IR Tx Rx Module.
That means the power is on. Do you get changes when the Rx diode is exposed or blocked from the IR light?

You need to see a valid logic one and zero voltage there for this part to be working.

#### Qual

Joined Aug 5, 2016
48
Line 39: T0CON =0b10011000 sets timer 0 as a timer.

Bit 5 must be 1 for counter (transition on T0CKI) operation:

T0CON =0b10111000

Thank you, Did not study properly in datasheet...

Thanks a lot...

#### Qual

Joined Aug 5, 2016
48
That means the power is on. Do you get changes when the Rx diode is exposed or blocked from the IR light?

You need to see a valid logic one and zero voltage there for this part to be working.
Yes i do see these valid logic one and zero voltage at output.

#### JohnInTX

Joined Jun 26, 2012
4,594
In line 22:
char *RPM = "00000 RPM";
is wrong. RPM is a pointer to char but that does not allocate space for the string, only space for the pointer itself.

Change line 22 to:
unsigned char RPM[]= "00000 RPM";
That will allocate space in RAM for the string. RPM is automatically a pointer to char but you don't care since you use indexing in Display_RPM.
It seems to work in the sim when TMR0H/L are manually loaded with a test value.
I'm surprised that the compiler doesn't issue a warning...

Also, for the 18F45K50 change
//CMCON = 0x07; // Disable comparators
to:
CM1CON0 = 0x00; // DISABLE COMPARATOR 1 and 2 (18F45K50)
CM2CON0 = 0x00;

What chip did you compile it for?

Good luck!

#### Qual

Joined Aug 5, 2016
48
In line 22:
char *RPM = "00000 RPM";
is wrong. RPM is a pointer to char but that does not allocate space for the string, only space for the pointer itself.

Change line 22 to:
unsigned char RPM[]= "00000 RPM";
That will allocate space in RAM for the string. RPM is automatically a pointer to char but you don't care since you use indexing in Display_RPM.
It seems to work in the sim when TMR0H/L are manually loaded with a test value.
I'm surprised that the compiler doesn't issue a warning...

Also, for the 18F45K50 change
//CMCON = 0x07; // Disable comparators
to:
CM1CON0 = 0x00; // DISABLE COMPARATOR 1 and 2 (18F45K50)
CM2CON0 = 0x00;

What chip did you compile it for?

Good luck!

I will do these changes....i am using 40 Pin IC PDIP.

#### JohnInTX

Joined Jun 26, 2012
4,594
The package doesn't matter but the numbers on it do

Looking further, you also have to specify your constants as long in the RPM_Value calculation:
RPM_Value = (256L*TMR0H + TMR0L)*60L;
By rights, you should manually cast the timer values to unsigned long too but the compiler doesn't seem to mind..

#### JohnInTX

Joined Jun 26, 2012
4,594
@Qual
Finally, I would recommend that you do not read TMR0H and TMR0L in the RPM_Value calculation. See 12.3 in the datasheet. It indicates that the read value for TMR0H is updated only when TMR0L is read. Since you can't predict the order that the compiler reads the two timer bytes in the calculation, I would read TMR0L and TMR0H into temporary variables, in that order. Then operate on the temp variables. That is a good practice whenever you do multiple reads on a peripheral or input port.

You didn't ask but if I were writing this, I would use a capture register and a free-running 16bit TMR1 or TMR3. Each pulse from the opto would cause the timer value to be captured instantly. On each capture, just subtract the value from the previous one to get the period (if the timer has rolled over the value will be negative, compliment it and continue). Compute RPM from period. By leaving the reading of the timer to the hardware, you don't have to mess with read latencies/byte order or any of that stuff. You have the time between tiks to compute the RPM. Average several periods for a nice smooth display.

#### Qual

Joined Aug 5, 2016
48
@Qual
Finally, I would recommend that you do not read TMR0H and TMR0L in the RPM_Value calculation. See 12.3 in the datasheet. It indicates that the read value for TMR0H is updated only when TMR0L is read. Since you can't predict the order that the compiler reads the two timer bytes in the calculation, I would read TMR0L and TMR0H into temporary variables, in that order. Then operate on the temp variables. That is a good practice whenever you do multiple reads on a peripheral or input port.

You didn't ask but if I were writing this, I would use a capture register and a free-running 16bit TMR1 or TMR3. Each pulse from the opto would cause the timer value to be captured instantly. On each capture, just subtract the value from the previous one to get the period (if the timer has rolled over the value will be negative, compliment it and continue). Compute RPM from period. By leaving the reading of the timer to the hardware, you don't have to mess with read latencies/byte order or any of that stuff. You have the time between tiks to compute the RPM. Average several periods for a nice smooth display.

Initially i started this circuit with CCP module of PIC, using RC1 or RC2 pin.
Since i am newbie in coding ,thought using timer would make it easy.

Thanks a lot....

#### JohnInTX

Joined Jun 26, 2012
4,594

Initially i started this circuit with CCP module of PIC, using RC1 or RC2 pin.
Since i am newbie in coding ,thought using timer would make it easy.

Thanks a lot....
You're welcome. FWIW, I looked at the generated code and it indeed reads the upper byte of the timer first. I guess that is consistent with left-to-right evaluation but I don't like to rely on such things.. Anyway, it looks like it needs to be changed to read L then H. I'd use temp variables.
As far as your method vs. mine, why not give yours a try? Once your math and display are sorted out, you can experiment and refine from there or leave as is if it works to your satisfaction.

#### Qual

Joined Aug 5, 2016
48
You're welcome. FWIW, I looked at the generated code and it indeed reads the upper byte of the timer first. I guess that is consistent with left-to-right evaluation but I don't like to rely on such things.. Anyway, it looks like it needs to be changed to read L then H. I'd use temp variables.
As far as your method vs. mine, why not give yours a try? Once your math and display are sorted out, you can experiment and refine from there or leave as is if it works to your satisfaction.

CCP based tachometer.
C:
unsigned long RPM_Value;

// Define LCD module connections.
sbit LCD_RS at RB5_bit;
sbit LCD_EN at RB4_bit;
sbit LCD_D4 at RA0_bit;
sbit LCD_D5 at RA1_bit;
sbit LCD_D6 at RA2_bit;
sbit LCD_D7 at RA3_bit;
sbit LCD_RS_Direction at TRISB5_bit;
sbit LCD_EN_Direction at TRISB4_bit;
sbit LCD_D4_Direction at TRISA0_bit;
sbit LCD_D5_Direction at TRISA1_bit;
sbit LCD_D6_Direction at TRISA2_bit;
sbit LCD_D7_Direction at TRISA3_bit;
// End LCD module connection definition
sbit IR_Tx at RA4_bit;

// Define Messages
char message1[] = "Tachometer";
char *RPM = "00000 RPM";
unsigned char counter=0;//Overflow counter
void Display_RPM(unsigned long num)
{
RPM[0] = num/10000 + 48;
RPM[1] = (num/1000)%10 + 48;
RPM[2] = (num/100)%10 + 48;
RPM[3] = (num/10)%10 + 48;
RPM[4] = num%10 + 48;
Lcd_Out(2,4,RPM);
}

unsigned int ov_cnt, temp;
unsigned long period;
void interrupt()
{
if(PIR1.TMR1IF)
{
PIR1.TMR1IF = 0;
ov_cnt ++;
}
}

void main()
{
unsigned int temp1;
ov_cnt = 0;
ANSELA = 0x00;
ANSELB = 0x00;

ADCON1 = 0x0F;  // Disable Analog functions
PORTA = 0x00;
PORTB = 0x00;
TRISA = 0b00010000;
TRISC = 0xff;
TRISB = 0x00;

INTCON.GIE = 0;         // Global interrupt disabled
RCON.IPEN = 1;          // Interrupt pin enabled with priority
PIR1.TMR1IF = 0;       // initially no overflow in TMR1 register
IPR1.TMR1IP = 1;       // High priority for TMR1 Overflow
T1CON = 0xC1;          // 1:1 prescale and 8 bit mode with internal clock.
CCP1CON = 0x05;        //  capture mode on every rising edge
PIE1.CCP1IE = 0;      // initially CCP1 Interrupt is disabled.
PIR1.CCP1IF = 0;      // CCP1 Interrupt Flag is cleared.

T0PS2_bit = 1;     // 256 prescalar
T0PS1_bit = 1;
T0PS0_bit = 1;

PSA_bit    = 0;       // source from FCPU 5MHz
T0CS_bit   = 1;       // Counter mode and

T08BIT_bit = 1;       // 8 bit mode

TMR0IE_bit=1;   //Enable TIMER0 Interrupt
PEIE_bit=1;     //Enable Peripheral Interrupt

//GIE_bit=1;      //Enable INTs globally
TMR0ON_bit=1;      //Now start the timer!

Lcd_Init();                      // Initialize LCD
Lcd_Cmd(_LCD_CLEAR);             // CLEAR display
Lcd_Cmd(_LCD_CURSOR_OFF);        // Cursor off
Lcd_Out(1,4,message1);           // Write message1 in 1st row

while(1)
{
while (!(PIR1.CCP1IF));
temp = CCPR1;
PIR1.CCP1IF = 0;
PIR1.TMR1IF = 0;
INTCON |= 0xC0;
PIE1.TMR1IE = 1;
while (!(PIR1.CCP1IF));
CCP1CON = 0x00;
temp1 = CCPR1;
if (temp1 < temp)
{
ov_cnt--;
}
period = ov_cnt * 65536 + temp1 - temp;
RPM_Value =  period ;     // this is micro second value
RPM_Value = 60000000 / RPM_Value;      // this value multiplied by 60
RPM_Value = RPM_Value * 2;             // and whole period * 2
Display_RPM(RPM_Value);
ov_cnt = 0;
temp1 = 0 ;
temp = 0;
};             // Infinite Loop
}
no display.

Last edited by a moderator: