Tach RPM input from coil (Uno - Loose USB serial connection upon digital input)

Hope this is the right category, otherwise please move it.

I try to make my Arduino read combustion engine RPM by connecting it to the engines ignition coil. Coil negative is connected straight to optocoupler. Cars 12v positive is connected thru 20Kohm resistor to the other leg of the optocoupler. Arduino is powered from my laptop USB. All works fine, except: Arduino Uno looses USB serial connection to my laptop when its connected and engine running. I have to disconnect USB, close down Arduino software, change USB port and try again. Sometimes it takes up to 10 minutes to establish the connection again.

It works fine on the kitchen table giving it 20v input at about 4 hz. It keeps the USB connection fine. But when connected to the engine, data logging stops and I cant upload any new code. If I turn on some serial.print to see what is happening, it stops printing to serial as soon as I start the engine. Arduino is powered from my laptop USB. The Arduino keeps running its code, I can see that on my LED's. The red LED flashes on idle, the green one turns on over 1500 rpm.

Any input on this would be highly appreciated!

#include <Servo.h>

const int LedPinRed1 = 10; 
const int LedPinRed2 = 4; 
const int LedPinGreen1 = 9; 
const int RpmPin = 3;                  // RPM input on pin A0.

int RpmPinState = 0;
volatile int tachCount = 0;
long tachCounttime;
long tachCountrpm = 0;


void setup()  { 
 pinMode(RpmPin, INPUT);   
 digitalWrite(RpmPin, HIGH);  
 attachInterrupt(1, tachPulse, FALLING);
 tachCounttime = millis();
 pinMode(LedPinRed1, OUTPUT);
 pinMode(LedPinRed2, OUTPUT);
 pinMode(LedPinGreen1, OUTPUT);
 digitalWrite(LedPinRed1, LOW); 
 digitalWrite(LedPinRed2, LOW); 
 digitalWrite(LedPinGreen1, LOW); 
} 


void loop() { 
          if (millis() - tachCounttime >= 250)
          {
          // rpm = number of interrupts counted in 250ms
          //  *4 to make per second
          //  *60 to make per minute
          //  /2 for 2 pulses per rotation (4 cylinder)
          //  /10 for some reason??? 
              tachCountrpm = ((tachCount*4)*60)/10;         
          //Do things with the RPM value here (update display etc)...
          tachCount = 0;
        tachCounttime = millis();
        }


RpmPinState = digitalRead(RpmPin); 

// LED OUTPOUT		        // Turn on LED when state changes.
if (RpmPinState == 0) {
digitalWrite(LedPinRed1, HIGH);
}
else
{
digitalWrite(LedPinRed1, LOW);
}

if (tachCountrpm > 1500) {       // If RPM is above 1500rpm, then turn on LED.
digitalWrite(LedPinGreen1, HIGH);
}
else
{
digitalWrite(LedPinGreen1, LOW);
}  

}

void tachPulse()
{
  ++tachCount;
}

Is the laptop on battery or is it powered through the car battery?

The laptop is on battery only.

This may well not be a software problem. I think you may have a case of electronic interference from the engine and need to add some shielding for your cables and electronics.

Mark

That was my thought. I don't know how good the opto-isolator is but it's possible it's breaking down.

I found this: http://www.dgp.toronto.edu/~mccrae/projects/492project/tachsignal.html - might help.

There is some useful information on that page, especially the circuit diagram:

However, I'm not tio sure how to implement this to my circut. Should I ground the zener diode to arduino ground, for example?

Regards
Tobias

cptdondo:
That was my thought. I don't know how good the opto-isolator is but it's possible it's breaking down.

I found this: Vehicular Signal Acquisition and Processing - Tachometer Signal - might help.

Could you give me some advise on how to try this? Twisted, none twisted wires?

Regards
Tobias

holmes4:
This may well not be a software problem. I think you may have a case of electronic interference from the engine and need to add some shielding for your cables and electronics.

Mark

I have done some more troubleshooting. After searching around on the net and collecting information, I've done the following:

And it works by not destroying the Arduino USB connection. I can log RPM thru serial monitor for several minutes. But the RPM-reading is a bit strange. Jumps up and down and dont follow all the way thru the RPM-range.

Should I go FALLING, CHANGE or something else on the interrupt?

OK, this is the latest attempt. Lowering the resistance to 20K Ohm and tweaking the code a bit. I get readings from 0-3000 rpm on a 1967 MGB. On higher revs it jumps around pretty much. The thing is that this project is going on a motorbike that I don't have access to yet. The bike easily revs to 15000 rpm.

#include <Servo.h>

const int LedPinRed1 = 10;
const int LedPinRed2 = 4;
const int LedPinGreen1 = 9;
const int RpmPin = 3;                  // RPM input on pin A0.

int RpmPinState = 0;
volatile int tachCount = 0;
long tachCounttime;
long tachCountrpm = 0;


void setup()  {
 
Serial.begin(9600);                      // Write debug to serial terminal at 9600 baud
 
pinMode(RpmPin, INPUT);  
digitalWrite(RpmPin, HIGH);  

attachInterrupt(1, tachPulse, FALLING); // Works on connect
// attachInterrupt(1, tachPulse, RISING);  // Works on disconnect
// attachInterrupt(1, tachPulse, CHANGE); // Works x2
// attachInterrupt(1, tachPulse, LOW); // NO!!

 tachCounttime = millis();
 
 pinMode(LedPinRed1, OUTPUT);
 pinMode(LedPinRed2, OUTPUT);
 pinMode(LedPinGreen1, OUTPUT);
 digitalWrite(LedPinRed1, LOW);
 digitalWrite(LedPinRed2, HIGH);
 digitalWrite(LedPinGreen1, LOW);
}


void loop() {
          if (millis() - tachCounttime >= 500)
          {
         tachCountrpm = (((tachCount*2)*60)*2)/10;        
         Serial.print("TachCount:");
         Serial.println(tachCountrpm);
         tachCount = 0;
        tachCounttime = millis();
        }


RpmPinState = digitalRead(RpmPin);


// LED OUTPOUT              // Turn on LED when state changes.
if (RpmPinState == 0) {
digitalWrite(LedPinRed1, HIGH);
}
else
{
digitalWrite(LedPinRed1, LOW);
}



if (tachCountrpm > 50) {       // If RPM is above 1500rpm, then turn on LED.
digitalWrite(LedPinGreen1, HIGH);
}
else
{
digitalWrite(LedPinGreen1, LOW);
}  

}

void tachPulse()
{
  ++tachCount;
}

Tobbera:
There is some useful information on that page, especially the circuit diagram:

That circuit diagram doesn't look right to me. Perhaps it is designed for a positive earth vehicle.

Alright! Have made om progress. I found the bellow circuit here (http://www.microchip.com/forums/fb.ashx?m=531373), and it seems to work pretty well on a single cylinder motorbike that I have tried it on. Opto pin 5 goes to arduino digital pin 3. Opto pin 6 goes to ground. Pullup enabled on arduino digitag pin 3.

Now I have some considerations regarding the code. I have tried booth calculations based on how many interrupts in a specific time, and how long time for a specific amount of interrupts as seen bellow. However, compared to the tachometer on the bike., my Arduino seems to show at least 1000 rpm less. As seen bellow, I tried micros instead of millis to gain accuracy. But cant notice any difference. Could the serial print interrupts be the problem? I would also appreciate suggestions on how to smooth this rpm reading, because it jumps around alot.

const int RpmPin = 3;
int RpmPinState = 0;
volatile int tachCount = 0;
long tachCounttime;
long tachCountrpm = 0;
const int LedPinRed1 = 10; 
const int LedPinRed2 = 4; 
const int LedPinGreen1 = 9; 



void setup()  { 
pinMode(RpmPin, INPUT);   
digitalWrite(RpmPin, HIGH);  
tachCounttime = micros();
attachInterrupt(1, tachPulse, FALLING);
Serial.begin(9600); 
pinMode(LedPinRed1, OUTPUT);
pinMode(LedPinRed2, OUTPUT);
pinMode(LedPinGreen1, OUTPUT);
}

void loop() { 
  if (tachCount >= 5) {
     tachCountrpm = 60000000/(micros() - tachCounttime)*tachCount;
     tachCounttime = micros();
     tachCount = 0;
     Serial.println(tachCountrpm);
  }    

// LED OUTPOUT
if (RpmPinState == 0) {
digitalWrite(LedPinRed1, HIGH);
}
else
{
digitalWrite(LedPinRed1, LOW);
}

if (tachCountrpm > 1500) {
digitalWrite(LedPinGreen1, HIGH);
}
else
{
digitalWrite(LedPinGreen1, LOW);
}  

}


void tachPulse()
{
  ++tachCount;
}

Three comments.

1} Try a capacitor across the Zener. Perhaps 10 nF to start, but really sheer guesswork on my part.

2} Don't use interrupts in your coding.

3} Make sure the wires are as short as possible. It's tricky to suggest exactly how to do this, but I would suggest having the optocoupler in the engine compartment, and grounding the output of the optocoupler to the car chassis. Use shielded cable from the optocoupler output to the Arduino, ensure that the shield is securely connected to all grounds in the Arduino circuit.

And: The fellow who made the comment about a "positive ground" car has actually been confused by the placing of the resistors. You should not have a resistor between the positive battery terminal and the optocoupler (in fact this was probably the reason for the USB problem), all the resistors, or all the resistance should be in the lead to the ignition coil.

You definitely should not be trying anything like this with a CDI!

Thanks Paul!

  1. Let me give this a try.

  2. How am I supopoed to count igntion events then?

  3. Yes, I have had the idea of moving the optocoupler out from the PCM and closer to the igntion coil. Reson havent is becouse I'm afraid that the arduino side of the optocoupler will pick up noise.

Why are you saying this about CDI? Do you think it can hurt the igntion system?

Tobbera:
2. How am I supposed to count ignition events then?

Thought you might ask. :smiley:

You just - count them! 12,000 rpm (of a single cylinder/ two stroke, 6,000 rpm for four cylinder four stroke) would correspond to 200 events per second or one every five milliseconds. Part of your task is to reject inappropriately fast events - thus my suggestion of a capacitor. As long as your main loop cycles at least each millisecond - as it certainly should whilst counting - it can simply poll the state of the input and only accept a change in state if it remains consistent for say, two milliseconds.

Tobbera:
3. Yes, I have had the idea of moving the optocoupler out from the PCM and closer to the igntion coil. Reason haven't is because I'm afraid that the Arduino side of the optocoupler will pick up noise.

Which is why I make the points about grounding and not putting resistors in the wire to the side of the coil that is relatively "quiet". Did I forget to mention having a pull-up to 5V on the optocoupler collector, and then a further isolation resistor (22k?) between that and the Arduino input itself instead of using the internal pull-up?

Tobbera:
Why are you saying this about CDI? Do you think it can hurt the ignition system?

Because CDI uses even higher and sharper impulse voltages on the ignition primary, and whatever problems you have had so far, are likely to be far worse. In fact, with CDI you should be deriving timing pulses from a suitable point in the CDI unit - from the sensor rather than the coil end.

Thanks again Paul!

  1. That was a new approach that I havent thought about. I dont now how to implement that as code. Do you have any example?

  2. Going to try this if I cant get it to work with the opto on board.

The reason I try to tap into ignition primary rather than ECU or ignition module is that I want to make this device as generic as possible. All engine has a ignition primary coil, but ECU and such varies. With ignition primary setup I can move it, or reproduce it to another engine without having to research and develop a new RPM input for that specific engine.

/Tobias

Hi, I've tried to read through all the posts, is the ignition system CDI, old style points or electronic?
If electronic then you may never see the equivalent of points closed, that is neg or coil to ground. This is because to store the maximum amount of energy in the coil at all speeds the MCU/ECU goes into current limit mode during the dwell period.
Also the voltage on the neg coil terminal is not just DC switched, it is high frequency HIGH voltage ringing.
This because the neg side of the coil is the terminal that is switched by the points/ECU/MCU to ground.
Have you tried sensing from neg coil to GND, anode side of opto to neg terminal and cathode side to GND with current limit and zener still in circuit as in the Microchip circuit.

Tom... :slight_smile:

TomGeorge, this is a modern CDI.

sensing from neg coil to GND, anode side of opto to neg terminal and cathode side to GND

Very interesting. I might give this I try. Thanks!

Alright, some progress..

I have fiddled around with the code, added cap's to filter input etc etc, but the rpm value was still bouncing around way too much. It could basically go from 2500 rpm to 25000 rpm between two readings. I figured it must be picking up signals that's not proper ignition pulses. My last try was to add another resistor in series to lower the signal from the coil even more, and thus only lightning up the optocoupler on "proper" ignition events. And that seemed to do the trick! An 1K ohm resistor just before the optocoupler all of a sudden calmed down the signal to an impressive level. And the readings followed up to about 4000 rpm, did not try it further. It seemed to show a little bit too low RPM readings tho. And I just figured out that it is probably showing exactly half the RPM since its a single cylinder, four stroke engine. That means that with the code bellow only senses the ignition events, which happens every odd revolution. Going to test *2 next week.

I also added a stronger pull up to the digital pin, to make the pin rise faster and be ready for the next pull down from the optocoupler. I used a 4K7 resistor from the digital pin to +5v.

Any input on my attempts are appreciated.

const int RpmPin = 2;                 // RPM input 
long tachCounttime;
long tachCountrpm = 0;

void setup()  { 
 Serial.begin(9600);                      // Write debug to serial terminal at 9600 baud
  digitalWrite(RpmPin, HIGH);              // Enable biult in pull-up  
  attachInterrupt(0, tachPulse, FALLING);
  tachCounttime = millis();
}

void loop() { 
 if (tachCount >= 5) {
     detachInterrupt(0);
     tachCountrpm = 60000/(millis() - tachCounttime)*tachCount;  // SHOULD ADD  "(...)*2" !!
     tachCounttime = millis();
     tachCount = 0;
     Serial.println(tachCountrpm);         
     attachInterrupt(0, tachPulse, FALLING);     
  }      
}

void tachPulse()
{
  ++tachCount;
}

Tobbera:
OK, this is the latest attempt. Lowering the resistance to 20K Ohm and tweaking the code a bit. I get readings from 0-3000 rpm on a 1967 MGB. On higher revs it jumps around pretty much. The thing is that this project is going on a motorbike that I don't have access to yet. The bike easily revs to 15000 rpm.

#include <Servo.h>

const int LedPinRed1 = 10;
const int LedPinRed2 = 4;
const int LedPinGreen1 = 9;
const int RpmPin = 3;                  // RPM input on pin A0.

int RpmPinState = 0;
volatile int tachCount = 0;
long tachCounttime;
long tachCountrpm = 0;

void setup()  {

Serial.begin(9600);                      // Write debug to serial terminal at 9600 baud

pinMode(RpmPin, INPUT); 
digitalWrite(RpmPin, HIGH);

attachInterrupt(1, tachPulse, FALLING); // Works on connect
// attachInterrupt(1, tachPulse, RISING);  // Works on disconnect
// attachInterrupt(1, tachPulse, CHANGE); // Works x2
// attachInterrupt(1, tachPulse, LOW); // NO!!

tachCounttime = millis();

pinMode(LedPinRed1, OUTPUT);
pinMode(LedPinRed2, OUTPUT);
pinMode(LedPinGreen1, OUTPUT);
digitalWrite(LedPinRed1, LOW);
digitalWrite(LedPinRed2, HIGH);
digitalWrite(LedPinGreen1, LOW);
}

void loop() {
          if (millis() - tachCounttime >= 500)
          {
        tachCountrpm = (((tachCount*2)*60)*2)/10;       
        Serial.print("TachCount:");
        Serial.println(tachCountrpm);
        tachCount = 0;
        tachCounttime = millis();
        }

RpmPinState = digitalRead(RpmPin);

// LED OUTPOUT              // Turn on LED when state changes.
if (RpmPinState == 0) {
digitalWrite(LedPinRed1, HIGH);
}
else
{
digitalWrite(LedPinRed1, LOW);
}

if (tachCountrpm > 50) {      // If RPM is above 1500rpm, then turn on LED.
digitalWrite(LedPinGreen1, HIGH);
}
else
{
digitalWrite(LedPinGreen1, LOW);
}

}

void tachPulse()
{
  ++tachCount;
}

This wiring can read form motorcycle pulser coil ?

Hello

Any update?
I must build Tach RPM like that, for my 2t Motorbike.