Tach input on any pin

Uses Darrel Taylor's Instant Interrupts
Fast, since is in ASM
This routine uses TMR0, but will work with any other unused timer.

To use -

Set interrupt period so that there will ALWAYS be an interrupt in the shortest
"high" or "low" period you want to measure. For example, if highest tach freq
is 100Hz and is a square wave, the period will be 10msec and the "high" part
of the square wave will be 5msec and the "low" period will be 5msec. In this case
the interrupt period should be 5msec minimum - choose 4 msec or less (must be a
little less than 5msec to cover interrupt latency). If the tach frequency is higher
or the input is not a square wave, you will have to increase the interrupt rate
accordingly.

In your main PBP routine - Clear TachCounter,ShaftCounter1,ShaftCounter2,Shaftcount erX.
Then clear PeriodDone. The routine will count for 1000 interrupts. When you want to
read the tachs, check if PeriodDone bit 0 is set - if it is, then the count is finished.
Move the ShaftCounters into PBP variables, Clear the shaftcounters and clear PeriodDone.
The cycle repeats and runs totally in the background.

You will have to mutliply the ShaftCounter variables by a value determined by
the tachometer and interrupt rate to get actual RPM.

The code can probably be improved, but it works for me.

Code:
Asm 
                           
ReadTachs
        
         movlw   0xEC              ; Reload TMR0 with your own value - sets INT rate - see explanation above
         movwf   TMR0H             ; Load the high byte first
         movlw   0x7E              ; Load the low byte
         movwf   TMR0L
     
        
         btfss   PeriodDone,0      ; Check to see if counting period is over
         bra     CheckTach         
         bra     DoneForNow
        
         
         infsnz  TachCounter       ; 16 bit counter
         incf    TachCounter + 1
              
         movlw   0x03 - 1          ; 1000 = 0x3E8, but must have one less
         cpfsgt  TachCounter + 1   ; to compare with greater than (choose a different number if you like)
         bra     FanRoutine
         movlw   0xE8 - 1          ; Again, subtract one
         cpfsgt  FanCounter
         bra     MainRoutine
         bsf     PeriodDone,0      ; Set the PERIOD Done lsbit 
         clrf    TachCounter       ; Clear the counter
         clrf    TachCounter+ 1
         bra     DoneForNow
                
MainRoutine
              
         movf    PORTB,0,0         ; Load all of Port B
         movwf   Temp,0            ; Copy it into TEMP for safety
         xorwf   OldPortB,0,0      ; XOR it with previous read
         movwf   changedB,0        ; Move the XORed byte into variable
         movff   Temp,OldPortB     ; Move Temp to OldPortB

         
         movf    PORTC,0,0         ; Do the same for PORTC (or any port with a tach input)
         movwf   Temp,0
         xorwf   OldPortC,0,0
         movwf   changedC,0
         movff   Temp,OldPortC

         
         movf    PORTD,0,0
         movwf   Temp,0
         xorwf   OldPortD,0,0
         movwf   changedD,0
         movff   Temp,OldPortD

         
         
Shaft1       
         btfss   changedB,0       ; Read in the bit - if it changed from last read it will be a "1"
         bra     Shaft2           ; It hasn't changed, so go to next
         infsnz  Shaft1Counter    ; Increment the counter lowbyte
         incf    Shaft1Counter+1  ; If necessary, increment the high byte



Shaft2        
         btfss   changedC,1       ; This shaft tach is fed into PORTC.1
         bra     Shaft3
         infsnz  Shaft2Counter
         incf    Shaft2Counter+1


        ; Put more shaftcounters here if you need them.


DoneForNow
         bcf     INTCON,2           ; Clear the TIMER0 interrupt flag

       INT_RETURN

ENDASM