Hello,
as part of my CarDuino project, I would like to monitor my car’s crankshaft sensor and then calculate the engine’s current revs.
Here’s how the crankshaft sensor signal looks in XOscillo at 3000 rpm (click on image for larger view):
I was able to work out that at the car’s maximum engine speed of 7000 rpm, the signal should have a frequency of some 5.3 kHz.
I am not sure why the XOscillo, running on an Arduino Uno board, missed a few pulses in the graph, but I guess it’s because my old laptop is painfully slow.
At the moment, my rpm monitoring sketch, which is executed by an Attiny45 that runs separately from the main microcontroller and which does nothing but monitor the crankshaft, clocks the signal using pin change interrupts:
/*--------------------------------------------------------------------------------------------------------------------
Crankshaft Signal Measurement Sketch
This sketch clocks the crankshaft induction signal that is present at pins 31 and 32
of the engine control unit and calculates the engine speed in rounds per minute (rpm).
It then sends the actual momentary rpm value over to the head unit via I2C.
*/
#include <TinyWireS.h>
#include <avr/interrupt.h>
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- Pin Definitions
#define crankshaft PB1
#define SCL PB2
#define SDA PB0
#define ledRequestEvent PB3
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- Crankshaft Signal Calculation Variables
volatile unsigned long crankRisingTimestamp;
unsigned long crankTimestampCurrent;
int crankTimestampCounter;
unsigned long crank_cumul;
unsigned long crankAverage;
unsigned long microsCrank;
unsigned long microsDifference;
unsigned long microsNow;
unsigned long microsStartCount;
boolean firstByte = true;
byte low_Byte;
byte high_Byte;
volatile uint16_t rpm;
void setup() {
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- TinyWireS Initialization, Pin Modes, States and Interrupts
// TinyWireS Initialization
TinyWireS.begin(3); // Joining I2C Network As Slave # 3
TinyWireS.onRequest(requestEvent);
pinMode(crankshaft, INPUT);
pinMode(ledRequestEvent, OUTPUT);
// Setting Interrupt Registers
GIMSK = 0b00100000; // turns on pin change interrupts
PCMSK = 0b00000010; // turn on interrupt on pins PB1
sei(); // enables interrupts
}
void loop() {
microsDifference = 0;
crankTimestampCounter = 0;
crank_cumul = 0;
crankAverage = 0;
microsStartCount = micros();
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- Counting Crankshaft Signal Intervals
while (microsDifference <= 1000000) {
microsNow = micros();
microsDifference = microsNow - microsStartCount;
if (crankRisingTimestamp != crankTimestampCurrent) {
microsCrank = crankRisingTimestamp - crankTimestampCurrent;
crank_cumul += microsCrank;
crankTimestampCounter += 1;
crankTimestampCurrent = crankRisingTimestamp;
}
}
crankAverage = crank_cumul / crankTimestampCounter;
// Test Rig rpm Equation... to be replaced by an actual accurate equation:
rpm = (int) ( 10750 - (0.5 * crankAverage));
}
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- Crankshaft ISR
ISR(PCINT0_vect) {
if ((PINB & 1 << crankshaft)) crankRisingTimestamp = micros();
}
//--------------------------------------------------------------------------------------------------------------------
// -------------------------------- I2C Request Event Definition
// -------------------------------- Data is requested from master every 1000 ms.
void requestEvent() {
bitWrite(PORTB, ledRequestEvent, HIGH);
if (firstByte) {
low_Byte = (byte) (rpm & 0xff);
high_Byte = (byte) ((rpm >> 8) & 0xff);
TinyWireS.send(low_Byte);
firstByte = false;
}
else {
TinyWireS.send(high_Byte);
firstByte = true;
}
delay(50);
bitWrite(PORTB, ledRequestEvent, LOW);
TinyWireS_stop_check();
}
I’ve chosen to do the crankshaft monitoring on a separate chip because the Atmega328P-PU which gathers the rest of the engine data, including fuel injectors, vehicle speed and a few analog readings, is really already quite busy with that and probably wouldn’t be able to correctly monitor such a fast signal on top of everything.
So anyway, on the rising edge, I take the time, and then calculate the time that passes between two rising edges and work that out to the actual engine revs. I then average all the readings from one second and pass that value on to the I2C master, which also requests data every 1000 ms.
I am not sure if this method is quick enough and what the latencies are. Would it be faster if I do the crankshaft monitoring by means of the Attiny’s timer and combine it with input capturing?