Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[SOLVED] Digital Tachometer issues

Status
Not open for further replies.
The display routine seems to be working fine.
I'm tied up with another job right now and there is no chance I can help before your deadline. The bug is in the calculateRPM() routine, the formula for working out the speed is not working properly. I'll have to let you work that one out for yourself but I think you can see how it processes the result, the thousands digit goes in Digit[0], hundreds in Digit[1], tens in Digit[2] and units in Digit[0]. Let me know how you get on.

Please remember it shows the pulses per 0.1 seconds interval at the moment, you can adjust the timing interval with the value loaded into TMR1 but if you need longer intervals, use a software variable to count several shorter intervals, do not use any delay routines as these will disrupt the display multiplexing.

Brian.

Well I found a different way to calculate angular velocities for my project therefore didnt use the tach...... but now i have quite a lot of time to correct the code! So plzzzz feel free to help me in ur own time! :)
 

Try:
Code:
void CalculateRPM()
{
  Digit[3] = (short)(PulseCount % 10);
  PulseCount /= 10;
  Digit[2] = (short)(PulseCount % 10);
  PulseCount /= 10;
  Digit[1] = (short)(PulseCount %10);
  PulseCount /= 10;
  Digit[0] = (short)PulseCount % 10;
}

Brian.
 

I amended the code as u told and when i placed it in front of cpu fan it provides various readings like 8, 11, 10 and 13 ............. I doubt cpu fan runs at such rpms! I think it should have been somewhere around 6000 rpms! What do u think???
 

Those are probably correct readings. The measurement window (time it counts pulses for) is 0.1 seconds as set by TMR1. So to convert to true RPM you would multiply by (seconds in one minute/0.1) = 600.

The software seems to be working though and hopefully, the LEDs are not flashing on and off like your old code did and the readings should be more stable.

To scale it to real RPM you have two options, multiply 'Pulsecount' by 600 or make the time window longer, each has it's good and bad points:

1. If you multiply by 600 the reading will be in RPM and the speed it updates will still be 0.1 seconds but the smallest unit of speed will be 600 so you lose accuracy.
2. You make the measurement time longer so there is more time for the pulses to accumulate. This makes it less responsive to change but more accurate in calculation.

It's up to you to decide whether speed or accuracy is more important. For example, if you monitor the pulses for a whole minute, the result will be exact but obviously, you have to wait a whole minute before the count is completed. If you measure for a shorter time and multiply the result, you get the same answer with a shorter update time but lose some accuracy. You probably want a compromise between the two so lets see if the code can be modified:

Code:
#define MEASURETIME 10  // measurement time in 0.1S units  (1 - 600)

void NextLED();
void CalculateRPM();

//-------------- Array of segment values for common cathode 7-seg. display
const unsigned short segments[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};

sbit IR_Tx at RB7_bit;
sbit IR_Rx at RA4_bit;
unsigned short Digit[4], ActiveDigit = 0, LastRxState = 0;
unsigned int PulseCount = 5678;
unsigned int MeasurementPrescale = MEASURETIME;

void main()
{
 TRISB = 0b00000000; // Set PORTB direction to be output
 TRISA = 0b00110000; // Set PORTA direction to be output
 PORTB = 0x00; // Turn OFF LEDs on PORTB
 CMCON = 7; // Disable comparators
 OPTION_REG = 0b10000110; // TOCS=0 for internal clock Counter mode, PSA=0 for TMR0 prescaled 1:128
 T1CON = 0b00110001;  // Timer 1 counts internal clocks prescaled 1:8
 INTCON = 0b01100000; // Enable TMR0 and PIE interrupts
 PIE1 = 0b00000001; // Enable TMR1 interrupts
 INTCON |= (1<<GIE); // Enable global interrupts
 IR_Tx = 1; // Turn ON IR emitter

 CalculateRPM();
 // count rising edge transitions
 while(1)
 {
  if((IR_Rx == 1) && (LastRxState == 0)) PulseCount++;
  LastRxState = IR_Rx;
 }
}

// TMR0 times how long each Digit is displayed, TMR1 times how long pulses are counted for
 void interrupt()
{
  if(INTCON.TMR0IF)
  {
   TMR0 = 0x53; // should be ~5mS at 4.433MHz clock
   NextLED();
   INTCON.TMR0IF = 0;
  }
  else if (PIR1.TMR1IF)
  {
   TMR1H = 0x27; TMR1L = 0x8C;  // should be ~100mS at 4.433MHz clock
   MeasurementPrescale--;
   if(MeasurementPrescale == 0)
   {
    MeasurementPrescale = MEASURETIME;
    CalculateRPM();
   }
   PulseCount = 0;
   PIR1.TMR1IF = 0;
  }
}

void CalculateRPM()
{ 
  PulseCount *= (unsigned int)(600 / MEASURETIME);
  Digit[3] = (short)(PulseCount % 10);
  PulseCount /= 10;
  Digit[2] = (short)(PulseCount % 10);
  PulseCount /= 10;
  Digit[1] = (short)(PulseCount %10);
  PulseCount /= 10;
  Digit[0] = (short)PulseCount % 10;
 }

void NextLED()
{
 PORTA &= 0xF0; // turn all LED drives off
 if(ActiveDigit > 3) ActiveDigit = 0;
 PORTB = segments[Digit[ActiveDigit]];
 PORTA |= (1 << ActiveDigit);
 ActiveDigit++;
}

What I've done is added a new variable and a new definition. The variable 'MeasurementPrescale' is used to count the 0.1 seconds interrupts from TMR1 before the pulse counting is terminated. It is initially loaded and gets reloaded with the value in MEASURETIME which you can change in the first line. The resulting count of pulses is multiplied by 600/MEASURETIME so as you adjust the measurement time it should automatically adjust the result to reflect it. It is currently set to 10 so the measurement window is (10 x 0.1S) = 1 second and the result is multiplied by (600/10) = 60 so it gives the result in RPM. If you change the definition to 600 it will measure a full minute and not scale the result, again giving RPM. Any value between 1 and 600 should work.

Try it out and play with the first line to get a good balance between speed and accuracy. Again, I have not tried it and I can't get the timers to simulate in MikroC so you are on your own!

Brian.
 
  • Like
Reactions: RMMK

    RMMK

    Points: 2
    Helpful Answer Positive Rating
Well I dont know whats wrong but whenever I try to measure cpu fan rpm...... it always shows readings b/w 250 to 740!!
 

Is that using the new code I posted? If it is, what value did you use for 'MEASURETIME'?

There may be another issue here, how exactly are you measuring the fan RPM? If you are looking at interruptions of a light beam through the fan blades you have to divide the result by the number of blades present.

I didn't suggest it because it would make the hardware incompatible but the most responsive method of detecting input pulses is to use the RB0 pin as the input and use it to cause an interrupt.

Brian.
 

Sir yup i have used the new code u uploaded and the measuretime value is the same that is 10.

I have pasted a white page on the cpu fan blades and have placed this circuit in front of it...... so close that it touches the fan blades! :p

Also it seems like as if the IR led never glows without being sourced with a separate supply in addition to one attached to the uController...... led never glows by using the current from the circuit!
 

I can't understand why the IR LED doesn't work but it should be on all the time anyway so there is no point in driving it from the micro.

I have almost finshed the project I've been working on for the past few days so I'll try building it during the weekend and see what is wrong.

Brian.
 

I can't understand why the IR LED doesn't work but it should be on all the time anyway so there is no point in driving it from the micro.

I have almost finshed the project I've been working on for the past few days so I'll try building it during the weekend and see what is wrong.

Brian.

So did u check out what was wrong??? :/
 

Sorry, I have not had time. We have had severe storm damage over the past few days and I've been busy carrying out repairs. I have found all the parts it needs and I will build it as soon as I can.

Brian.
 

Sorry, I have not had time. We have had severe storm damage over the past few days and I've been busy carrying out repairs. I have found all the parts it needs and I will build it as soon as I can.

Brian.

Oh my bad....... i didnt know it! u can take ur time since in the next coming week i will be indulged in the final semester exams!
 

Still very short of time and more storms are expected tomorrow so I may be without electricity and internet again for a while.

I built the circuit and spent ages trying to get it working with MikroC but eventually gave up. MikroC's IDE looks very neat but underneath the compiler is very poor and it's debugging facilities are almost useless. I copied the code to Microchip's XC8 compiler, tweaked a few lines of code and it immediately simulated properly. I've copied the XC8 code below. It works, but the pulse counting in software causes aliasing problems with the display multiplexing. The only fix is to use a faster clock or better still, use the PICs hardware counting facilities but that can't be done with the existing wiring, it would require some pins to be changed around. Power/storm permitting, I will be working on a different project starting tomorrow morning so I'll have to abandon the techometer for now. If you want to follow it through, this is what I would do:
1. rewire the board so T1CKI (pin 12) is the pulse input.
2. remove the timer 1 code from the interrupt routine, timer 1 will become the new pulse counter instead.
3. count the multiplexing interrupts from TMR0IF to decide the gate time.

The rest of the code can stay the same, it isn't a big software change. The new method will use TMR1 to count the pulses independently of the multiplexing so the display will be on all the time. At the end of the gate period (when enough TMR0 interrupts have occurred) the value to pass to the 'CalculateRPM()' will be in TMR1H and TMR1L instead of in the 'PulseCount' variable.
Code:
#include <xc.h>

#pragma config FOSC=XT WDTE=OFF, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=OFF, LVP=OFF

#define MEASURETIME 10  // measurement time in 0.1S units  (1 - 600)
#define IR_Rx PORTAbits,RA4
#define IR_Tx PORTBbits,RB7

void NextLED();
void CalculateRPM();

//-------------- Array of segment values for common cathode 7-seg. display
const unsigned short segments[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
unsigned char Digit[4], ActiveDigit = 0, LastRxState = 0;
volatile unsigned int PulseCount = 0;
unsigned int MeasurementPrescale = MEASURETIME;

void main(void)
{
 TRISB = 0b00000000; // Set PORTB direction to be output
 TRISA = 0b00110000; // Set PORTA direction to be output
 PORTB = 0x00; // Turn OFF LEDs on PORTB
 CMCON = 7; // Disable comparators
 OPTION_REG = 0b10000011; // TOCS=0 for internal clock Counter mode, PSA=0 for TMR0 prescaled 1:16
 T1CON = 0b00010001;  // Timer 1 counts internal clocks prescaled 1:8
 INTCON = 0b01100000; // Enable TMR0 and PIE interrupts
 PIE1 = 0b00000001; // Enable TMR1 interrupts
 INTCONbits.GIE = 1; // Enable global interrupts
 IR_Tx = 1; // Turn ON IR emitter

 CalculateRPM();
 // count rising edge transitions
 while(1)
 {
  if(IR_Rx == 1)PulseCount++;
  while(IR_Rx == 1){};
 }
}

// TMR0 times how long each Digit is displayed, TMR1 times how long pulses are counted for
void __interrupt isr(void)
{
  if(INTCONbits.TMR0IF)
  {
   TMR0 = 0x30; // should be ~2mS at 4.433MHz clock
   NextLED();
   INTCONbits.TMR0IF = 0;
  }

  if(PIR1bits.TMR1IF)
  {
   TMR1H = 0x27; TMR1L = 0x8C;  // should be ~100mS at 4.433MHz clock
   MeasurementPrescale--;
   if(MeasurementPrescale == 0)
   {
    MeasurementPrescale = MEASURETIME; // re-load for next measurement
    CalculateRPM();
	PulseCount = 0;  // start next measurement from zero
   }
   PIR1bits.TMR1IF = 0;
  }
}

void CalculateRPM()
{ 
  PulseCount *= (unsigned int)(600 / MEASURETIME);
  Digit[3] = (unsigned char)(PulseCount % 10);
  PulseCount /= 10;
  Digit[2] = (unsigned char)(PulseCount % 10);
  PulseCount /= 10;
  Digit[1] = (unsigned char)(PulseCount %10);
  PulseCount /= 10;
  Digit[0] = (unsigned char)PulseCount % 10;
}

void NextLED()
{
 PORTA &= 0xF0; // turn all LED drives off
 PORTB = segments[Digit[ActiveDigit]];
 PORTA |= (1 << ActiveDigit);
 if(ActiveDigit++ == 4) ActiveDigit = 0;
}

Brian.
 

Phew - survived 130Km/h winds, 40mm of rain and only 4C temperatures. Not nice!

I've modified the code as per my previous message and it works within about 1% accuracy. I fed it with a signal generator to simulate pulses from the IR sensor and it works up to 166Hz (9999 RPM) before overflowing the available digits which is correct. Change segment 'g' to RB7 and use RB6 as the input. You can change the number of samples per minute by changing the MEAS_PER_MIN value. It will flash a LED on RB4 to announce each gate period, note it's an open drain output so connect a LED from VDD to it rather than VSS.

Code:
#include <xc.h>

#pragma config FOSC=XT WDTE=OFF, PWRTE=OFF, MCLRE=OFF, CP=OFF, CPD=OFF, BOREN=OFF, LVP=OFF

#define INTS_PER_SEC 14940  // times TMR0 overflows per minute second @ 4.433618MHz clock 
#define MEAS_PER_MIN 30  // sets how many samples per minute are taken (range 1 - 60)
#define GATE_LENGTH INTS_PER_SEC / MEAS_PER_MIN
#define IR_Rx PORTBbits,RB6
#define Gate PORTAbits,RA4

void NextLED();
void CalculateRPM();

//-------------- Array of segment values for common cathode 7-seg. display
const unsigned short segments[10] = {0x3F, 0x06, 0x9B, 0x8F, 0xA6, 0xAD, 0xBD, 0x07, 0xBF, 0xAF};
unsigned char Digit[4], ActiveDigit = 0;
volatile unsigned int PulseCount = 0;
unsigned int MeasurementPrescale = GATE_LENGTH;

void main(void)
{
 TRISB = 0b01000000; // Set PORTB direction to be output
 TRISA = 0b00100000; // Set PORTA direction to be output
 PORTB = 0x00; // Turn OFF LEDs on PORTB
 CMCON = 7; // Disable comparators
 OPTION_REG = 0b10000100; // TOCS=0 for internal clock Counter mode, PSA=0 for TMR0 prescaled 1:32
 T1CON = 0b00000111;  // Timer 1 counts RB6 clocks prescaled 1:1
 INTCON = 0b00100000; // Enable TMR0 interrupts
 INTCONbits.GIE = 1; // Enable global interrupts

 TMR1H = 0; TMR1L = 0;

 CalculateRPM();
 // count rising edge transitions
 while(1);
}
 
// TMR0 times how long each Digit is displayed
void __interrupt isr(void)
{
  if(INTCONbits.TMR0IF)
  {
   TMR0 = 0x76; // should be ~4mS at 4.433MHz clock
   NextLED();
   INTCONbits.TMR0IF = 0;
	 if(MeasurementPrescale-- == 0)
	 {
			Gate = 1;
			MeasurementPrescale = GATE_LENGTH;
			T1CONbits.TMR1ON = 0;
   		PulseCount = (TMR1H << 8) + TMR1L;
			CalculateRPM();
			TMR1H = 0; TMR1L = 0; // reset for next count
			T1CONbits.TMR1ON = 1;
			Gate = 0;
		}
  }
}

void CalculateRPM()
{ 
	PulseCount *= MEAS_PER_MIN;  // convert count to RPM
  Digit[3] = (unsigned char)(PulseCount % 10);
  PulseCount /= 10;
  Digit[2] = (unsigned char)(PulseCount % 10);
  PulseCount /= 10;
  Digit[1] = (unsigned char)(PulseCount %10);
  PulseCount /= 10;
  Digit[0] = (unsigned char)PulseCount % 10;
}

void NextLED()
{
 PORTA &= 0xF0; // turn all LED drives off
 PORTB = segments[Digit[ActiveDigit]];
 PORTA |= (1 << ActiveDigit);
 if(ActiveDigit++ == 4) ActiveDigit = 0;
}

Brian.
 
  • Like
Reactions: RMMK

    RMMK

    Points: 2
    Helpful Answer Positive Rating
Thanks a lot..... i will try this after my exams are over that is within two weeks! After that i might be able to post the results!
 

I followed ur above stated procedure....... I am observing some readings but the 7-segment now does not clearly let me see it through! There is some sort of a problem! At the start it only shows correctly 0000 but after placement for readings the segments get dim and dark with various readings spanning b/w 500 to 1439..... I cant see clearly the readings since the numbers are not getting clearly visible due to the led's getting dim and sharp in the 7 segment!
 

That's strange, the LED brightness is only determined by the current through the segments and the time they are turned on for. The current is set by the series resistors and the time is set by the TMR0 interrupt rate. It worked here but I had to dismantle it to make space for another job. Do you have access to an oscilloscope to check the timing of the digit drive signals? They should be high for 4mS each with just a few nS between one digit and the next.

Brian.
 

Well I think that the timers are working more or less fine...... but the issue is why are the 7-segment is not forming right digits??? It seems like as if there is some sort of display but the formation doesnt seems to be right!

- - - Updated - - -

Ah...... solved the problem atlast! :p But I cant get a single reading........it seems to vary a lot! from 640 to 3360! how should I correct it??? And THANKS A LOT FOR THE HELP! SIR U REALLY HELPED ME!!!!!
 

You didn't say what the problem was that you solved but I'm guessing you didn't rewire the 'g' segment as I mentioned in post #33. You may note that I changed all the segment values so that bits 6 and 7 were swapped to make RB6 available as the pulse input.

It worked OK here, I hooked the input up to a square wave generator and it displayed 60 times the frequency I fed into it. I changed the MEAS_PER_MIN from 1 to 60 and the reading stayed the same which is correct although if you set it to 1 it takes a full minute to update the readings of course. Don't forget that if you set a long measurement period and remove the pulses before the counting has finished, the number will be lower than expected.

Brian.
 

You didn't say what the problem was that you solved but I'm guessing you didn't rewire the 'g' segment as I mentioned in post #33. You may note that I changed all the segment values so that bits 6 and 7 were swapped to make RB6 available as the pulse input.

It worked OK here, I hooked the input up to a square wave generator and it displayed 60 times the frequency I fed into it. I changed the MEAS_PER_MIN from 1 to 60 and the reading stayed the same which is correct although if you set it to 1 it takes a full minute to update the readings of course. Don't forget that if you set a long measurement period and remove the pulses before the counting has finished, the number will be lower than expected.

Brian.

Yeah u are right........ its working correctly when fed with a function generator and interestingly it measured correctly the rpm of a disk I hooked in front of it but it doesnt correctly measure the fan speed......................... I think its working fine!!! Thanks a lot!
 

Status
Not open for further replies.

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top