Hello. This is my first post on the forum. I'm working on a big school project due Thursday 4/19. Part of my project is using an Arduino Uno as a sensor to find the RPM of a 2 stroke engine. I'm doing this by simply wrapping a wire around the spark plug wire, sending this signal through a clean signal generator circuit (turns spikes into square waves), then sending the cleaned up signal to a digital input pin on the Uno. I'm using pin 2 so that it can generate an interrupt. Each time a rising edge is detected, an interrupt is triggered to increment the pulse counter. Every 100 ms, the number of pulses stored and the pulse counter resets. I'm using a small rotating array (pulseArray) to average the last 2 values measured. This frequency value is stored in a large array (ppsArray). Once a switch is pulled (turning isSystemActive to LOW), the Arduino stops calculating engine frequency and instead writes from the array to EEPROM memory. Upon resetting the Arduino and pulling another switch (turning connectedToSerial HIGH), EEPROM is read and displayed to Serial Monitor. These values can be multiplied by 60 to produce RPM.
This works splendidly when I use a function generator as a test input to the clean signal generator circuit. However, when using the spark plug signal as input, Arduino displays the engine frequency for only a few seconds, then displays 0 as the frequency for the remaining samples. I've tested the signal that comes out of the clean signal generator circuit when the spark plug signal is the input, and it is square waves whose frequency varies with engine RPM, as expected.
I don't know why this doesn't work when using the spark plug signal as input to the Uno. It's strange that it works for a few seconds then stops. Any help is much appreciated as I will struggle to figure this out on my own. Here's the relevant parts of my code:
#include <EEPROM.h>
//pin numbers
const byte inputPin = 2; //input from sensor MUST BE PIN 2 OR 3 to be able to trigger interrupt on pin change
const byte isSystemActive = 4; //input from toggle button and safety switch
const byte connectedToSerial = 9; //connect this pin to HIGH to upload EEPROM data to serial
//parameters for EEPROM data to serial
int address;
byte value; //EEPROM hold 1 byte per address: value between 0 and 255.
//parameters for sensor
volatile byte numberOfPulsesCounted = 0;
const byte numberOfSamplesToAverage = 2;
const byte arraySize = numberOfSamplesToAverage + 1; //DO NOT EDIT
volatile byte pulseArray[arraySize] = {}; //DO NOT EDIT
byte headPointer = 0; //index of newest value in array, DO NOT EDIT
byte tailPointer = 1; //index of oldest value in array, DO NOT EDIT
volatile byte sum = 0; //DO NOT EDIT
unsigned long currentTime;
byte timeJustElapsed = 1;
byte measuredPulsesPerSecond;
int ppsArrayIndex = 0;
const int ppsArrayLength = 600;
byte ppsArray[ppsArrayLength] = {}; //how many locations needed? 10 locations filled per second. 60s * 10/s = 600.
byte systemJustActivated = 1;
//functions
void findSum()
{
sum = sum + pulseArray[headPointer] - pulseArray[tailPointer];
}
void incrementpulse()
{
numberOfPulsesCounted++;
}
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(inputPin), incrementpulse, RISING); //interrupt to incrementpulse() each time a rising edge is detected on inputpin
pinMode(inputPin, INPUT);
pinMode(isSystemActive, INPUT);
pinMode(connectedToSerial,INPUT) ;
for (int i = 0; i < ppsArrayLength; i++) //initiate ppsArray to all 0s
{
ppsArray[i] = 0;
}
//EEPROM DATA TO SERIAL-------------------------------------------------------------------------------------------------
if(digitalRead(connectedToSerial) == HIGH) //read values from EEPROM to serial display
{
address = 0;
while (address < ppsArrayLength)
{
value = EEPROM.read(address); //read value from EEPROM
Serial.print(address);
Serial.print("\t");
Serial.print(value, DEC); //display value to serial
Serial.println();
address++; //increment address
}
while(true)
{}
}
}
void loop() {
// put your main code here, to run repeatedly:
//FREQUENCY SENSOR-------------------------------------------------------------------------------------------------
if (timeJustElapsed == 1)
{
currentTime = millis(); //set currentTime to millis()
numberOfPulsesCounted = 0; //reset pulse counter
timeJustElapsed = 0; //reset timeJustElapsed to 0 because new timer cycle has begun
interrupts();
}
if (millis() >= currentTime + 100) //after 100 ms elapsed
{
noInterrupts();
timeJustElapsed = 1; //signifies new timing cycle should begin
//after full timing cycle, calculate new sum PPS and update array pointers
pulseArray[headPointer] = numberOfPulsesCounted;
findSum();
//update head and tail pointers
headPointer = (headPointer + 1) % arraySize;
tailPointer = (tailPointer + 1) % arraySize;
//Calculate and display PPS
measuredPulsesPerSecond = sum * 5; // (10 / numberOfSamplesToAverage) MUST CHANGE THIS IF PARAMETERS ARE CHANGED!!!!!!!!!!
}
//ppsArray update and EEPROM write------------------------------------------------------------------------------------------------------------------------------------------------
//if the system is not activated
if (digitalRead(isSystemActive) == LOW)
{
if (systemJustActivated == 0) //if this is the first time through loop since system has been deactivated
systemJustActivated = 1; //signify that this is no longer the first time through loop since system has been deactivated.
for (int i = 0 ; i < EEPROM.length(); i++) //clear eeprom
{
EEPROM.write(i, 0);
}
for (int i = 0 ; i < ppsArrayLength; i++) //write data to eeprom
{
EEPROM.write(i, ppsArray[i]);
}
}
else //if system is active
{
if(timeJustElapsed == 1) //each time a new frequency measurement is available
{
if(ppsArrayIndex < ppsArrayLength) //if index is within size of eeprom,
{
ppsArray[ppsArrayIndex] = measuredPulsesPerSecond; //put measured speed value in pps array
ppsArrayIndex ++; //increment index
}
}
}
}
Provide a complete wiring diagram. This is probably a hardware issue and not a software issue, otherwise it wouldn't work with the signal generator too.
pylon:
Provide a complete wiring diagram. This is probably a hardware issue and not a software issue, otherwise it wouldn't work with the signal generator too.
The only difference between the two setups is connecting the wire that feeds the clean signal circuit to the signal generator instead of wrapping it around the spark plug. I've attached a drawing of the circuit.
jremington:
You will need extremely robust spike suppression circuitry to protect the Arduino input.
What do you mean by robust spike suppression, and what do you mean by protecting the input? I've attached a pic of an oscilloscope display while running the motor. The blue signal comes from the spark plug wire, the yellow signal is the output of the circuit.
jremington:
Seems like an awful lot of code to read RPM.
It could definitely be done with less code, but for the application I'm using it for, all the variables and arrays are needed. For readability, I deleted out all the other code that doesn't deal with this specific issue before posting here.
What do you mean by robust spike suppression, and what do you mean by protecting the input?
Since the spark plug signal is tens of thousands of volts, and the input of the Arduino will be damaged or destroyed if the input voltage is less than -0.5V or greater than Vcc+0.5V, you have a problem to solve.
It is extremely likely that electrical noise from the engine is getting into the Arduino circuitry somehow. The schematic diagram posted is incomplete and does not have any protection for spikes getting into the 5V power supply, or even the ground connection.
Please post the rest of the circuit diagram, showing how the Arduino is powered, connected to the input circuitry, and how it is grounded and isolated from engine electrical noise. Everything but the electrically shielded sensor wire should be in a grounded, conductive enclosure.
The posted scope image tells me nothing, but I wonder what the nasty spikes on the blue trace mean.
jremington:
Since the spark plug signal is tens of thousands of volts
I may have not been clear. I am not using the spark plug signal itself. I am wrapping a wire around the cable that goes to the spark plug, so that a pulse is induced in this wire each time the engine sparks.
jremington:
The posted scope image tells me nothing, but I wonder what the nasty spikes on the blue trace mean.
The nasty spikes is the induced pulse that occurs each time the engine sparks. I posted the scope to show the clean signal circuit does output a square wave with the same frequency as the spark plug signal.
jremington:
Everything but the electrically shielded sensor wire should be in a grounded, conductive enclosure.
All the electrical connections are on the Arduino shield which sits on top of the Uno. I didn't think noise would be an issue.
I've posted another schematic showing how the circuit is powered and connected to Arduino. If I've left anything important off please let me know what else you need. Thanks for your help!
I am wrapping a wire around the cable that goes to the spark plug, so that a pulse is induced in this wire each time the engine sparks.
That was perfectly clear. That pulse can be hundreds, to thousands of volts, positive and negative, into a high impedance load.
I didn't think noise would be an issue.
I'm quite certain that it is, having seen quite a bit of circuitry destroyed by electrical noise in automotive electrical systems. And, you are here because you have a problem!
The entire circuit needs to be in a grounded metal box, and the signal wire needs to be shielded, with the shield grounded.
thejarson9:
I may have not been clear. I am not using the spark plug signal itself. I am wrapping a wire around the cable that goes to the spark plug, so that a pulse is induced in this wire each time the engine sparks.
The interesting thing of such induction sensors is that the voltage they produce is related to the current that goes to the spark plugs, rather than the voltage that's used. Well, I should say, it is related to the change in current in the wire, and that's a very sharp change so it's inducing a very high voltage. As this is a voltage peak, it can go right through your 22pF capacitors. The peak is very short, and fair chance your scope doesn't pick it up completely, but it can be more than enough to mess up your Arduino.
Try cleaning up your signal further and insulating the Arduino better from the nasty spark plug spikes and other noise: place an optocoupler between your 555's output and the Arduino, thereby completely separating the two circuits. So NO connected grounds or anything. Then you can place Arduino and battery inside a metal, grounded case (again, no connection between the two) keeping all the noise out.
After more testing, I've found that my sensor works great as long as the RPM stays under 15000 or so. I'm going to agree with you jremington that the spikes for higher RPMs cause too much noise in the circuit/Arduino.
wvmarle:
Try cleaning up your signal further and insulating the Arduino better from the nasty spark plug spikes and other noise: place an optocoupler between your 555's output and the Arduino, thereby completely separating the two circuits. So NO connected grounds or anything.
I checked the original source of the circuit I'm using and there was a brief mention of an optocoupler, I'll check into that.
Hmm, I think the only grounding point available is the battery. The sensor will be used on a weed whacker while it's running, so I can't have wires going off somewhere to physical ground.
Since I've found that my sensor works for sub 15000 RPM, I think I'll just adjust what I'm doing to fit that constraint. If anyone has any more suggestions please reply. This is my first big electrical project and it's teaching me there's alot I don't know
thejarson9:
After more testing, I've found that my sensor works great as long as the RPM stays under 15000 or so. I'm going to agree with you jremington that the spikes for higher RPMs cause too much noise in the circuit/Arduino.
It's probably your 555 circuit that's limiting you. 100 nF and 18kΩ means a pulse length of 2 ms. I suppose you need at least that much time before you can have the next pulse (so the cap can discharge sufficiently - I don't know enough about the 555 monostable but you definitely must allow the cap to discharge enough before it can be triggered again). 2 ms pulse + 2 ms idle = 250 pulses per second or 15,000 per minute.
Try a 47 nF cap, or even a 10 nF. That 0.2 ms pulse will readily trigger your interrupt and allow for much higher speeds.
Hmm, I think the only grounding point available is the battery.
Don't mix up "ground" with "the negative pole of the battery".
Indeed the two are often used as synonyms and connected, but it's not always the same. Ground in an electronic circuit is normally "the 0V reference level" and not necessarily connected to the earth. In you case, I think there's no need to connect the battery's negative pole to the body. Actually you're likely better off leaving them unconnected.
wvmarle:
It's probably your 555 circuit that's limiting you. 100 nF and 18kΩ means a pulse length of 2 ms. I suppose you need at least that much time before you can have the next pulse (so the cap can discharge sufficiently - I don't know enough about the 555 monostable but you definitely must allow the cap to discharge enough before it can be triggered again). 2 ms pulse + 2 ms idle = 250 pulses per second or 15,000 per minute.
Actually, the 555 circuit requires virtually no idle time. As long as the input pulses are > 2 ms apart, it detects them and outputs the right thing. For less than 2 ms pulses, you get a solid DC signal since the square waves are smashed together.
I'm going to add in an optocoupler. I haven't dealt with this before, so any recommendations on a specific chip to use?
I'm guessing putting the optocoupler on the Arduino shield with the rest of my circuitry would be pointless since noise would get in, even tho the spark signal is separated from everything else by the optocoupler.
If the optocoupler is 6-12 inches away from Arduino and circuitry, do I need to worry about shielding the spark signal and grounding everything else? I don't have a grasp on how noise gets around. How far from the spark signal cable would it carry?
thejarson9:
Actually, the 555 circuit requires virtually no idle time.
I expect it'll need at least some time to discharge the cap and be ready for the next pulse.
It's a simple experiment: run your motor at maximum speed and check with your scope what comes out. That will tell you there and then what actual output signal you get. If that's still a nice pulse train, the problem is with the software.
I'm going to add in an optocoupler. I haven't dealt with this before, so any recommendations on a specific chip to use?
A regular one like the PC817 will do great. It's response time (total <10 µs) is fast enough for your 2 ms pulse.