PDA

View Full Version : 16F877@4Mhz calculate Pulsin to RPM



lwindridge
- 18th April 2004, 17:09
OK, first things first I'm new to programming PIC's (although have been prgramming PC's etc for over 10 years now) and this is my first time out with PBP.
For reference I am using a 16F877 MCU running with a 4Mhz crystal and porting my information out via serial as I don't have an LCD or similar at present.

I've been trying for the past day or so to make a basic readout of
RPM based on an incoming square wave to pin B0.
I've got a 4500rpm signal coming in based on my cars crank angle sensor readout (4 pulses per rotation) and using the count function I can relate that back no problem (300 rps/4 * 60=4500rpm).
What I need to do though is count the pulses WITHOUT holding the MCU in a state of flux while the reading is being taken.
I've tried doing this with the pulsin command but I'm not sure I'm getting the right things back from it.
The pulse (in theory) should run high for 1/8 of a revolution and low for 1/8 of a revolution which signifies a quarter turn on the crank.
Currently if I count the pulse width using the pulsin command I get a result of 165 high and 168 low (using two pulsin commands, one for high one for low).

The area where I am getting stuck is how to convert this into a useable RPS value and therefore convert to RPM.

This has to be done fairly rapidly as the circuit I am designing will use this value to retard the timing on my car based on RPM and Airflow (Airflow is an easy 0-5v reference and can be read via an A/D input so no problems there). Obviously there will only be around 12-14 revs per second at idle raising to 125 at the engines redline of 7500RPM so it has to fit with a sample time of around 250ms I think (correct me if I'm wrong though!).

Can anyone offer any suggestions or code snippets for how I should go about the calculation - or even if there is a much simpler/more efficient wat of doing it.

One possibility I did think of is measuring the time between the pulses leading edges so I could get a time between the first pulse and the next and base the rps on that after multiplying by 4.

Thanks in advance.

Leigh Windridge

Darrel Taylor
- 18th April 2004, 21:15
Hi Leigh,

From your two values of 165 and 168, I calculate that the engine was running at 4504 RPM. So, the following formula may be what your looking for.
Pulse_High Var word
Pulse_Low Var word
Pulse_Total Var Word
RPM Var Word

Pulse_High = 165
Pulse_Low = 168

Pulse_Total = Pulse_High + Pulse_Low
RPM = 1000
RPM = RPM * RPM ' Preload internal registers with 1,000,000
RPM = DIV32 Pulse_Total ' 1,000,000 / Pulse_Total
RPM = RPM * 60 ' Per minute
RPM = Div32 40 ' 4 pulses per rev

lcdout $FE,1,dec RPM
In normal math it might look like this:
(1,000,000/(Pulse_High+Pulse_Low))*60/40

A better way to take the measurements might be to use the Capture mode of the CCP module. Set the prescaler to 2:1 and have it count 4 rising edges of the input and then generate an interrupt. The Timer value would contain 1/2 the number of uS it takes for 1 revolution, which should be fairly easy to convert to RPM. This way, the processor can continue doing other things while it's taking the measurement. This should work from 500-10,000 RPM

That's a bit more complicated, but may be worth looking into.

HTH,
   Darrel

lwindridge
- 18th April 2004, 22:05
Well the code you posted certainly confirms my math and exactly how I didn't know the code :)

Do you have any demo code on how I could do the CCP method as it would be much more reliable I feel.

Thanks again.

Leigh

Darrel Taylor
- 18th April 2004, 23:18
Nope, sorry, don't have any good code to show for the Capture mode.

The RPM question comes up fairly often, and I've wanted to write a good routine for it, but so far haven't got around to it. Maybe it's about time I did.

I'll play around with it and see what I can come up with, but No guarantees. In the mean time, I hope the above formula, helps get you back on track.

L8R,
   Darrel

Melanie
- 19th April 2004, 07:44
For Capture Compare usage, do a search at the MeLabs archives and look for "PICBASIC-L How to use ccpx2.bas example" (circa April 2003). The original datafile is posted on the MeLabs site also. Within that thread there are other references to frequency measurement including the thread "PICBASIC-L Frequency Measurement" also from April/May last year. From there that should give you a whole heap of ideas and code...

lwindridge
- 19th April 2004, 10:31
Thanks again for all your help folks.

Sure does help a newcomer keep going with experts around :)

One very quick question though - can anyone recommend where the best place would be to get a serial LCD display from at a reasonable price in the UK? I'm probably looking for a 16x2 but any suggestions are welcome!
I tried to order from the Crownhill website last night but the ordering section appears to not like my card number or something and keeps looping back to the payment options page and blanking out the previous values???

I'm looking at a serial LCD as I want to use most of the other pins on the MCU for other functions, but as always I'm open to suggestions.

Leigh

Melanie
- 19th April 2004, 10:54
Go phone Crownhill... tell them about your ordering problems... if they don't know they've got a problem then they can't fix it. For 1-off items, Crownhill's pricing is reasonable and I always recommend them... for quantity you can always haggle...

That aside, go build your own... you'll get change out of a tenner... standard 16x2 LCD, a 16F628 (buying one will help reduce Lester's F628 mountain *smiles*) and a resonator... and if you really want to be flash, a few jumpers or switches for baud-rate and polarity settings...

lwindridge
- 21st April 2004, 10:19
OK, so I'm getting somewhere now...

I've setup the code to use the CCP1 register to scan the pulses and return the time back in uS.
What I can't seem to figure out yet is whether I've got the prescaler etc. right.

My code has the following lines....

CCP1CON = %00000110
T1CON = %00010001

Does that look about right for the settings that Darrel suggested. 2:1 prescaler and measuring 4 rising edges?

So does this look right to calculate the RPM? (Sorry for being so dumb, but this sort of math really bakes my noodle!!)

dummy = period * 2
RPM = (dummy*60)/100

The only problem I can see is that this is going over the 65535 limit. Can I use the DIV32 command to make this work properly and read the value correctly?
For example, at 6000rpm period is approx. 4989uS, which puts dummy to 9978 so rpm should work out (9978*60)/100=5986.8 very close to 6000rpm so not bad.
BUT 9978*60=598680 - just a bit over the 65535 limit :o(

Can anyone suggest a better way to to the math as this will become much worse to calculate at lower rpm due to the increased uS per RPM time.

And yes, I do feel a bit of a fool asking these questions, but it is my first time programming a PIC :o)

Cheers

Leigh

lwindridge
- 21st April 2004, 15:15
Ignore my previous request - I've figured it out ;)

Amazing how a bit of deep thought and caffeine can help!!

Leigh

Darrel Taylor
- 22nd April 2004, 05:30
Wow, if this is truly your first time programming PIC's then your doing pretty good. I'm impressed. And also bummed. Now I'll never get to write that RPM program. ;)

No worries, keep it up.

DT

lwindridge
- 22nd April 2004, 05:48
I think one of the thing that helps me is I've been programming in various languages on PC's for about 10 years now.
Also I'm using the FlashLab system from Reynolds Electronics so the development is very easy and I don't need to worry about moving chip to programmer and back to circuit each time I make a small change.
All helps anyway :)

I might end up tapping your brain again soon anyway Darrel - got some interesting maths that's hurting my brain again....

Cheers!

Leigh

charudatt
- 22nd April 2004, 21:16
So is it possible to post the final working code in this foroum for others (like me) to take benifit.

Thanks.

lwindridge
- 22nd April 2004, 21:30
The code will probably end up being in seperate sections but I'll certainly post some here for reference :)

Leigh

lwindridge
- 22nd April 2004, 23:53
Yet another question.

I've been using a variation on ccpx2.bas to find the pulse length in uS and working the math back from there.
BUT, I don't seem to get a very reliable figure from this (eg. I have a 29hz input which is roughly 870rpm but it seems to equate to something wildly out eg. 255??).

I've attached my current code and if someone can suggest a better way of taking the rpm reading, processing it, reading the 2 adc ports and telling a timer to delay the incoming signal on an output port (eg. 44us for a 2 degree delay at 7500rpm) and display some values on an LCD then I'd be more than welcome for the additional input.

Fingers crossed it's just my maths playing up!!

Leigh

Darrel Taylor
- 23rd April 2004, 05:14
Hi Leigh,

Just took a quick look at your program, and I can't answer all those questions, but did notice one thing.

The capture function of the CCP simply latches the Timer value into the CCPRH:L register when the specified event occurs. In this case thats after 4 RISING edges. To get a meaningfull result, the timer has to be started at the same time as the CCP starts counting it's 4 periods.

You can just leave the timer turned off and let it capture 0 the first time, then as soon as the CCP1IF (PIR1.2) is raised, imediately start the timer, then reset the CCP1IF. On the next capture you should have a valid result.

I don't know about all the rest, but one thing at a time usually works best.

Maybe something like this
CCP1CON = %00000110 ' Enable the CCP1 capture, every 4th rising edge
loop:
T1CON = %00010000 ' TMR1 prescale=1:2 Timer OFF
TMR1H = 0 ' Zero the Timer
TMR1L = 0
capture = 0
StartLoop:
IF (capture = 0) Then StartLoop ' Wait here for the first capture
T1CON.0 = 1 ' Start the Timer
capture = 0 ' Reset the capture flag
CaptureLoop:
IF (capture = 0) Then CaptureLoop ' Wait here until captured
period.lowbyte = CCPR1L ' Store the captured value in
period.highbyte = CCPR1H ' period variable
DT

lwindridge
- 24th April 2004, 19:21
Well I tried the suggestions but I still don't seem to be able to get a reliable value (always out by a good margin).

I'm wondering if it would be better to include an assembly interrupt and do the pulse measurement that way?

Still very new to this so any suggestions and code snippets welcome :)

Also, I may already know the answer to this, but can a PIC reliably perform a phase shift on an incoming signal and output it in realtime?
The reason for asking is say I have a 3750mS pulse (1925 high, 1825 low) coming in, I need to put a delay of 88uS on it for example and re-output it in its original form (or as close to as possible). The shortest delay I'll need to put on is about 44uS and the longest would be about 333uS.
The theory I have is that I need to have a dedicated CMOS chip fed with a signal to modulate the pulses accordingly, but I'd rather do it with a PIC if I can because not only will I need to do it once (for ignition timing) but for two injector pulses too (current ecu provides trigger and I need to send a pulse EXACTLY on time of a length in mS to each channel when needed (2 injectors per channel on my car).

Thanks again for all your help here.

Leigh

Darrel Taylor
- 24th April 2004, 21:36
Patience, patience,

While what you've described is possible, you need to get to first base before you can get a home run.

If you made the changes I suggested, then at 4500 RPM you should now be getting a constant capture result of around 6666.

Other RPM's should give the following results.
RPM - Capture
--------------------
500   - 60,000
1000 - 30,000
2000 - 15,000
3000 - 10,000
4500 - 6,666
7000 - 4,285

The formula in your program is still the same as what I gave you for the Pulsin readings. This will no longer work for the capture results and needs to be changed.

But first, can you verify that you are getting the correct capture result?

Best reagrds,
   Darrel

lwindridge
- 24th April 2004, 22:54
Right then, next update....

I've started using an external clock source for the rpm after putting a scope on the car this morning and finding out a few more things.
This has been clocked to give me 26 pulses per second (based on a reading from a 1 second count command) and seeing as I now know for sure that I get 2 pulses per crank revolution (2:1 scaling on the crankshaft to the crank angle sensor on the back of the camshaft which pulses every 90 degrees on the cam) I'm guessing that should work out to about 780rpm.
I've chosen to go lower as it makes testing on the car easier as I can check at idle.

The pulses I get from a pulsin low and high measurement are...
1934 high and 1829 low (with a slight variation occasionally to 1935 high and 1838 low).

Running up the modified code (I've attached another version of the program as I've re-structured it somewhat to make debugging easier) I get a period value of around 9975 (fluctuates a bit though between 1966 and 1982).

Hope that make sense anyway :)

Leigh

Darrel Taylor
- 25th April 2004, 01:48
Leigh,

Didn't get your latest program.

However, now that you've changed the number of pulses per rev, the prescaler will need to be changed to 4 instead of 2.

With a prescaler of 2. If the engine is running at 794 RPM the capture count would be 75,510. This is too high. The timer would wrap around after 65535 and you would be left with 9974 as the period result.

By setting the prescaler to 4, this will keep it from overflowing untill the RPM gets below 460. Of course, this now changes the calculation again.

I'm trying to come up with a formula can take all these changes into account and automatically correct for them. No doubt, you'll be changing other things later. Most likely is the OSC speed, for what you want to eventually do with the timing, and injectors, 4mhz isn't going to cut it.

Let me know what you get with the new prescaler.

Darrel

lwindridge
- 25th April 2004, 09:02
Must have clicked the wrong button for the attach but here's the code now..

Darrel Taylor
- 25th April 2004, 09:38
Thanks Leigh,

I think this should work for you, for now. Change the T1CON assignment to:

T1CON = %00100000 ' TMR1 prescale=1:4 Timer OFF

And, use this formula:

period = period / 2
RPM = 10000
RPM = RPM * RPM ' 100,000,000
RPM = DIV32 period ' 100,000,000 / RevCount
RPM = RPM * 60 ' Per minute
RPM = DIV32 400

Also, for the lookup to the nearest 100 RPM, you might consider this:

Nearest = RPM / 100
if RPM DIG 1 > 5 then Nearest = Nearest + 1
Nearest = Nearest * 100


Hope this works for you.

Darrel

lwindridge
- 25th April 2004, 09:44
Well, what can I say - it works!

I just wish I had the maths ability to work these things out by myself - but then again I do get stuck after I run out of fingers :)

The readout now shows a clocked RPM of 795 and using your little bit of maths I get a nearest rpm of 800.
Works like a charm!

Now for the fun part, putting the pulse back out on a pin in real time....

Leigh

montree
- 14th June 2013, 06:44
Hi Darrel Taylor

You said the ccp should work from 500 - 10,000 rpm. Please kindly advice how to measure rpm 0-10,000 rpm by use ccp module on [email protected] you have source code please show me. Thank you.

Darrel Taylor
- 14th June 2013, 23:29
You'll need to count the timer overflows and use them as the high word of the period after a capture.
Then change all the math, because the values will be larger than 16-bits.

Since you are using an 877A, you won't have the luxury of using LONG variables.

montree
- 15th June 2013, 16:42
Thank you for your advice but i still not clear please kindly show example about your advice above. For my program show as below
PIC16F877A
DEFINE OSC 4
Define LCD_DREG PORTD
Define LCD_DBIT 4
Define LCD_RSREG PORTE
Define LCD_RSBIT 2
Define LCD_EREG PORTD
Define LCD_EBIT 1


Low PORTE.2
PIE1.2=1

Symbol Capture = PIR1.2 ' CCP1 capture flag
T1 VAR WORD ' 1st capture value
PW VAR WORD ' 2nd capture value & ultimately final pulse width



intermediate VAR WORD
Dummyvar VAR WORD


rpm VAR WORD

TRISC.2 = 1 ' CCP1 input pin (Capture input)
INTCON = 0 ' Interrupts off

ReLoad:
CCP1CON = %00000101 ' Capture mode, 4capture on rising edge
T1CON = 0 ' TMR1 prescale=1, clock=Fosc/4, TMR1=off (200nS per count @20MHz)
TMR1H = 0 ' Clear high byte of TMR1 counter
TMR1L = 0 ' Clear low byte
T1CON.0 = 1 ' Turn TMR1 on here


Capture = 0 ' Clear capture int flag bit
While (!Capture) ' Wait here until capture on rising edge
Wend

' Rising edge detected / stuff Timer1 value in T1
T1.HighByte = CCPR1H
T1.LowByte = CCPR1L

CCP1CON = %00000100 ' Configure capture for falling edge now
Capture = 0 ' Clear capture interrupt flag bit

While !Capture ' While here until capture on falling edge
Wend

' Falling edge detected / stuff Timer1 value in PW
PW.HighByte = CCPR1H
PW.LowByte = CCPR1L

PW = PW-T1 ' High pulse width = PW-T1


intermediate = 60 * 1000 / 4 ' Compulsory !!!
' ( no "intermediate CON xxx" allowed )


' Disable interrupts ... if some used !!!


Dummyvar = 1000 * intermediate


rpm = DIV32 PW


' re-enable interrupts allowed
lcdout $fe,1,"TCH",$fe,$84,DEC RPM

GOTO ReLoad

END