Mosaic
Well-Known Member
I just wanted to share my approach to an auto tach using a 16f pic.
As it happens the method allows for 8bit integer math and display updates every .3 second.
Also it's inherently accurate to 100 rpm but this can be easily improved to 50 rpm with a bit of 2x oversampling and an integer /2 & using the carry to track the 50rpm state.
The approach directly translates the RPM into binary for a simple bin2dec display to handle.
It works as follows:
1) A 10 ms interupt timer determines the accuracy of the time segment to be sampled for RPM pulses via the T0CK input. Here is the code fragment from my 10 ms ISR routine
As it happens the method allows for 8bit integer math and display updates every .3 second.
Also it's inherently accurate to 100 rpm but this can be easily improved to 50 rpm with a bit of 2x oversampling and an integer /2 & using the carry to track the 50rpm state.
The approach directly translates the RPM into binary for a simple bin2dec display to handle.
It works as follows:
1) A 10 ms interupt timer determines the accuracy of the time segment to be sampled for RPM pulses via the T0CK input. Here is the code fragment from my 10 ms ISR routine
Code:
incf Tachperiod,f ;this routine counts pulses on ra4,t0ck, to calc rpm.
movf Tachperiod,w
; handle 6cylnders setting
btfsc RPM6cyl,0; skip if 6cylinders not selected, else
addlw .10; makes next sublw .30 seem like a sublw .20 for a net 200 msec period.
sublw .30 ; standard sample period in tens of msec => 300 msec.
skpz
goto ISR1 ; ISR1 is a routine to restore w-reg,STATUS & PCLATH before RETFIE
;this defaults to a 4CYL RPM reading, .30 sec updates.
clrf Tachperiod;
bcf TachLSB,6; default clr LSB of rpm
movf TMR0,w ; load undivided timer0
movwf Timer0 ; capture timer 0 .
bcf STATUS,C
;check for 8cyl setting (1:2)
btfsc RPM8cyl,1 ; if set then
rrf Timer0,f ; div by 2 , else
btfsc STATUS,C; if carry has a 1 then RPM lsb of 50 is active.
bsf TachLSB,6 ;set this to be handled by tachdisp, only useful with 8 cyl.
clrf TMR0 ; reset Timer0
bcf STATUS,C
;check for ecu signal (1:1) setting
btfsc RPMecu,3
rlf Timer0,f; X 2 for ECU signal
;check for Coil on plug (4:1) setting
btfsc RPMplug,4
rlf Timer0,f; x 2
bcf STATUS,C
btfsc RPMplug,4
rlf Timer0,f; x4 for RPMplug
movlw .50
btfss Featuresel,2; skip if tachometer feature bit enabled, else
movwf Timer0; force RPM to read 5000
As u see this bit of code samples the T0CK tmr0 count over a specified interval of either .30 sec or .20 sec (for 6 cylinders).
It also does some simple integer math to handle different tach inputs such as:
ECU signal, Coil on Plug signal, default 4 cylinder coil signal and 8 cylinder coil signal.
The last section forces a 5000RPM fixed reading if the RPM display is disabled.
The TachLSB bit setting allows the 8 cylinder mode to be accurate to 50RPM .
The result of this code is a value in the Timer0 user GPR variable that is directly equivalent to the RPM.
If Timer0 = 30, then the rpm = 3000.
Timer0 = 99 then RPM = 9900
Thus the display of the RPM is a simple bin2dec display for 2 significant digits and then the last 2 digits are padded with zeroes. For the 50RPM extra accuracy, you'd have to oversample by doing a .6 sec sample and then dividing by 2 to get a lsb in the carry to drive the 50RPM accuracy.
The RPMecu,RPM8cyl, RPM6cyl, RPMplug, TachLsb bits are all bits in a single GPR byte used to configure the tachometer to the type of input. You'll notice the tactic used for the 6 cylinder variant is not to engage in a somewhat time consuming divide-by-3 asm code but to reduce the sample period and thus get an exact value with no math.
The basis for this approach comes from the formula:
Direct RPM => Pulses per minute/60=> pulses per second
So 3000 RPM =>3000/60 = 50 pulses per second
Thus in .6 sec the # of pulses = 50 *3/5 = 30
Thus for an ecu direct RPM signal sampled over .3sec we multiply by 2 to get the correct result.
For a 4 cylinder coil signal which delivers 2 pulses per rpm (4 cycle engine) the .3 sec sample gives the precise result with no math.
An 8 cyl motor gives 4 pulses per rpm so we divide by 2 to get the precise result and use the carry to derive incidental 50rpm LSB accuracy.
The 6 cylinder motor has a 3/2 coil pulse relationship to the 4 cyl. So we reduce the sample period by 2/3 to have a 1:1 numerical result.
Coil on plug takes a single signal from 1 plug, which means the plug fires once every 2rpm in a 4 cycle motor regardless of cylinders. this is 1/4 the rate of the 4cyl sample => x 4 to get actual RPM
In wasted spark systems the shared coil on plug will fire once per rpm, therefore you'd use the ECu direct setting for any wasted spark shared coil on plug signal.
I found this to be just what I needed, simple and flexible. I hope it's useful to the community.
Last edited: