LS1TECH - Camaro and Firebird Forum Discussion

LS1TECH - Camaro and Firebird Forum Discussion (https://ls1tech.com/forums/)
-   PCM Diagnostics & Tuning (https://ls1tech.com/forums/pcm-diagnostics-tuning-7/)
-   -   Adruino RPM (https://ls1tech.com/forums/pcm-diagnostics-tuning/1912579-adruino-rpm.html)

MaroonMonsterLS1 12-31-2018 10:40 AM

Adruino RPM
 
Hi Everybody,

I've been following along with this thread https://ls1tech.com/forums/automatic...ontroller.html
and It's pretty cool and figured it might get some attention here. it's not my thread, i just really like the project. Seeing that project do SO much...I'm hoping I can handle something a little more simple.

I'm looking for some help with getting a steady RPM input from the LS (411 pcm if it makes a difference) to the arduino. I've considered using the coil 1 trigger wire with a "PulseIn()" function, and some smoothing, but I was hoping someone here has already done a project like this and they have a working schematic and code that they would be willing to share!

This is my first big venture with arduino so I'll take all the help and advice I can get!!

Thanks

JoeNova 12-31-2018 12:47 PM

You can use the ground from the coil to the PCM, it works OK.
Can also use the wire from the PCM to the tach.

I don't use pulseIn to often. Too many weird issues. I typically use an interrupt that's triggered by a ground (like the coil wire).
This is a rough code I just borrowed from the arduino website and modified it to work in your case, but it won't be exact since I'm at work (Do the CODE tags work here)?
I use a VERY similar code to measure RPM on my small go-kart engine dyno.

Code:

unsigned long timeold;

 void setup()
 {
  Serial.begin(115200);
  pinMode(2,INPUT_PULLUP);
  attachInterrupt(0, rpm_fun, FALLING);
  revolutions = 0;
  rpm = 0;
  timeold = 0;
 }

 void loop()
 {
  if (revolutions >= 5) {
    rpm = 60*1000000/((micros() - timeold)*revolutions);
    timeold = micros();
    revolutions = 0;
    Serial.println(rpm,DEC);
  }
 }

 void rpm_fun()
 {
  revolutions++;
 }

In a nutshell:

Code is setup for an arduino nano. Interrupt 0 is pin 2 on the Nano.
Basically, the pullup on pin 2 sets it to 5v, so its always high.
You attach an interrupt to that pin that triggers when its FALLING (when it gets grounded).
The interrupt add 1 to the revolutions variable. (The point of the interrupt is that it will function outside main loop, completely independent).
When the revolutions variable gets up to 5 revolutions, it tallies up the total amount of microseconds it took for those 5 revolutions, and divides 60,000,000 microseconds (1 minute) by that number to get RPM.
Then it resets the micro counter and the revolutions variable and waits for the next 5 counts.

I use CHEAP Nanos. I get the cheapest 10-pack I can from ebay or amazon probably once a month.

JoeNova 12-31-2018 12:59 PM

Sorry, I screwed up the rpm code there with the parenthesis.
Try this instead:
Code:

rpm = revolutions * 60000000/(micros() - timeold);
You can increase or decrease the number in the "if (revolutions >= 5)" to fine tune it.
A higher number will give a better RPM resolution, a lower number will output the RPM variable more often.

JoeNova 12-31-2018 01:12 PM

DAMNIT I WISH I COULD EDIT POSTS. ITS BEEN OVER 2 YEARS LS1TECH.

That little tidbit there about increasing RPM resolution doesn't really apply to this code. Using micros() instead of the typical millis() allows you to get single RPM resolution, which wouldn't happen with millis().
Here, you will want to use the lowest revolution count that will still allow your code to function properly.
Once you add a ton of stuff inside of the loop(), the amount of time it takes for the loop to run might scew your RPM readings, so you'll need to raise the number of revolutions before RPM is calculated.
If I use this exact code on my small engine dyno with the FULL coding that calculated SAE correction, load cell readings, wideband readings, and outputs a graph through serial data, "if (revolutions >= 2)" only allows me to read RPM up to 1800 before it begins to throw off the readings. "if (revolutions >= 5)" lets me have 4500 or so RPM.

But with this code alone, you can change it to "if (revolutions >= 1)", forcing an output every revolution and it will be fine.

MaroonMonsterLS1 12-31-2018 02:19 PM

Thanks very much Joe!!
I had actually found some old posts of yours and tried to send you a PM but couldn't. I'm glad you commented!

Will this interrupt method be able to constantly run outside of the main loop? So I can always know what's going on with RPM regardless of where the loop is?
I'm trying to do some very basic nitrous control and I have most of my loop setup to use the RPM/TPS etc as allowable windows...but I would like the rpm to be able to be constantly calculated and used in the main loop. So that at any time if the RPM goes outside of the pre-set window, it can interject and kill the output.

JoeNova 12-31-2018 02:48 PM

Yes, it'll always run in the background. Its called an interrupt because it will still trigger right in the middle of a loop, so do your RPM calculations FIRST in the loop before anything else that way they'll always be calculated without error.

NSFW 01-01-2019 12:55 PM

I used a little bit different approach in a VVT controller for my Subaru, where code in the interrupt handler calculates the elapsed time between interrupts, and calculates RPM from that:

newRpmValue = TicksPerMinute / elapsedTimeInTicks;

I'm not currently using any smoothing, but I was going to use this approach:

UpdateRollingAverage(&Rpm, newRpmValue, 0.5f);

https://github.com/LegacyNsfw/AvcsCo...llingAverage.h

The basic idea is to use a global variable to hold the smoothed RPM value, and call UpdateRollingAverage with the new raw (unsmoothed) value, and UpdateRollingAverage basically moves the smoothed value closer to the raw value. The "weight" value determines how much weight is given to the new value. With a weight of 1.0, there is no smoothing, with a weight of 0.1, the smoothed value moves 10% of the way toward the new value. For now it's actually working just fine without any smoothing (weights are all 1.0) but I that might only work because VVT is disabled at idle (which is where RPM is the most erratic). I'm going to try to keep it active at idle some day, but I haven't actually tested that yet.

Some of my interrupts were being triggered by noise (which I assume came from the actual sparks), so I ended up using short loops in the interrupt handlers to make sure the pin state change interrupt was caused by an actual sensor signal rather than just spark noise. That's in the getPinState function here:

https://github.com/LegacyNsfw/AvcsCo...ptHandlers.cpp

In the beginning I tried the keep the code in the interrupt handlers as trivial as possible, but then someone reminded me that interrupt time isn't nearly as precious on an Arduino as it is in, say, Windows. The CPU in my project spends the vast majority of its time spinning in the main loop evaluating variables that haven't even changed since the last iteration, so there's really no harm done by using interrupt time to do useful work. The only important thing is that the interrupt handlers don't run long enough to compete with each other.

JoeNova 01-01-2019 07:27 PM

I had noise from the magneto ignition on the dyno triggering the interrupts, which is why I used the pullup resistor to keep the pin high and triggering the interrupt when grounding.

I then put a double check in the interrupt loop to make sure that the pin was still low before increasing the counter.

Zero interference now.


All times are GMT -5. The time now is 05:47 PM.


© 2024 MH Sub I, LLC dba Internet Brands