Engine RPM (Part 2)

Part 1, 2, 3, 4, 5

The previous post covered the hardware part of the engine RPM sensor, it is time to care about the software component.

Nothing complex ahead, and we have a variety of options for taking care about pulse counting. Globally, all we need is a pin which will read the state of the signal from the adapter circuit. Two options are available: The pin repeatedly reads the signal level or an interruption is generated when a signal level is sensed on the pin. Considering the maximum signal frequency (say 58 pulses per round time 6000 rpm / 60 = approx 6kHz), the first option can be retained and it is a very to program. However, in accordance to the principle “Why should we simple things while it is possible to make complex things”, I decided to use the second option. In fact , it is no big deal, as we can use the pins to which an interruption can be attached, using the standard attachInterrupt() function. It is very convenient as this function can trigger an event which is based on four different situations:

  • LOW to trigger the interrupt whenever the pin is low,
  • CHANGE to trigger the interrupt whenever the pin changes value
  • RISING to trigger when the pin goes from low to high,
  • FALLING for when the pin goes from high to low

Just fine. However, this is not enough for us. Say that my application already uses the default pins on which it is possible to attach an interrupt… Well, the micro controller offers a fancy option which allows the programmer to attach an interrupt to any pin fro any port!

Setting the parameters is pretty easy though. Setting PINB0 as an interruptible pin requires the following two lines of code

PCICR |= (1 << PCIE0); 
PCMSK0 = (1 << PINB0);

Next is the programming of the service executed when any type of change is sensed by PINB0

ISR(PCINT0_vect)
{
	static uint8_t previousState = 0;
	uint8_t state = (PINB >> PINB0) & 0x01;
	if (state != previousState) {
		counts += 1;
		previousState = state;
	}
}

In this case a counter is incremented on any change on PINB0. Remember that the counts variable must be declared as a global volatile variable.

So far so good. Except that the code is not very versatile, as I would like to be able to set any pin from any port. Well, this is still no big deal.

setting the pins looks like

		if (_inputPort == &PORTB) {
			PCICR |= (1 << PCIE0); 
			PCMSK0 = _inputPinMask; 
		} else if (_inputPort == &PORTC) {
			PCICR |= (1 << PCIE1); 
			PCMSK1 = _inputPinMask;
		} else if (_inputPort == &PORTD) {
			PCICR |= (1 << PCIE2); 
			PCMSK2 = _inputPinMask;
		}

And setting the appropriate service must must be programmed as

ISR(PCINT0_vect)
{
	static uint8_t previousState = 0;
	uint8_t state = (PINB >> PINB0) & 0x01;
	if (state != previousState) {
		counts += 1;
		previousState = state;
	}
}

ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect));

This all about the minimal requirements for counting pulses sensed on any pin of the Arduino board. These lines may be encapsulated in a refined library which will offer a great flexibility without the least programming complexity. This is the the aim of PlainCNTR library that I will be happly share with those we may need it. Next is a print of the usermanual which comes with the code and which give a good idea about the features of the library

Copyright (C) 2012-2013 Didier Longueville

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

PlainCNTR library contains a set of functions which are dedicated to counting pulses
There are two ways of using the counting functions: interrupted mode or non interrupted mode
In the non interrupted mode, the pulse counts or frequency measurement is made during a prefixed period
of time (dwell time) during which nothing else is executed.
In the interrupted mode, the pulse counts or frequency measurement is performed after starting the
couting mode using the StartCounter() function, until the counter is stopped using the StopCounter() function.
The pulse counts can be read at any time.
Any pin form any port can be used for sensing pulses.

Functions and commands

---------------------------------------------------------------------------------------------------
InitializeCounter(inputPin, port, biasPin, [interrupted])
---------------------------------------------------------------------------------------------------
arguments:
- inputPin: sensing pin 
- port: port to which the sensing pin is attached
- biasPin: bias sensing pin or not
- interrupted: set the interrupt mode or not

returns:
- None

---------------------------------------------------------------------------------------------------
ReleaseCounter()
---------------------------------------------------------------------------------------------------
arguments:
- None

returns:
- None

---------------------------------------------------------------------------------------------------
StartCounter([initPulses])
---------------------------------------------------------------------------------------------------
arguments:
- initPulses: initial pulse counts

returns:
- None

---------------------------------------------------------------------------------------------------
StopCounter()
---------------------------------------------------------------------------------------------------
arguments:
- None

returns:
- None

---------------------------------------------------------------------------------------------------
GetPulses([dwellTime])
---------------------------------------------------------------------------------------------------
arguments:
- dwellTime: time during which the pulses are sampled, required in the non interrupted mode only

returns:
- Number of pulses

comments:
- In non interrupted mode, dwellTime is not to be set; pulses can be read while counter is still on, 
  or on completion of counting (after execution of StopCounter)

---------------------------------------------------------------------------------------------------
GetFrequency(dwellTime)
---------------------------------------------------------------------------------------------------
arguments:
- dwellTime: time during which the pulses are sampled, non interrupted mode only

returns:
- frequency in Hertz

comments:
- None

---------------------------------------------------------------------------------------------------
Constants
---------------------------------------------------------------------------------------------------

Global public constants:
- CNTR_BIA_DISABLED 
- CNTR_BIA_ENABLED

- CNTR_NON_INTERRUPTED
- CNTR_INTERRUPTED

 

The rest is just a matter of imagination and programming skills. Here is an example of use of the engine rpm counting functions …

 

Next post on same subject

2 Comments

  1. KoliJoci says:

    Hi,

    Could you send me the PlainCNTR library?

    Thanks

Leave a Reply

You must be logged in to post a comment.