Counting pulses on a tone wheel

I’ve been working on a project where I’m trying to control a diesel injection pump with an Arduino Due. This isn’t something I expect to run a vehicle with, I just want to run an engine on the stand to see if its feasible to pursue this project any further. The pump itself is relatively simple to control once you have the proper hardware interface. Currently there is a peak and hold driver and an internal tone wheel with two separate outputs.

In the attached image the yellow channel is the peak and hold driver, purple is one of the tone wheel outputs, and green is the other tone wheel output. What I’m trying to do is start counting falling edges on the purple signal once the green signal goes low, after X(5 or 6) purple pulses I’ll turn on the peak and hold driver for a set amount of time.

My current code is below and it’s not counting correctly. When channel 3 is low the counter just continues to increment can anybody see what I’m doing wrong?

I’m open to another way to do this, my software skills are rusty so this is the best I could come up with. Thanks in advance.

Tone_Wheel.ino (1.17 KB)

Hi,
Welcome to the forum.

Please read the post at the start of any forum , entitled "How to use this Forum".
OR
http://forum.arduino.cc/index.php/topic,148850.0.html.
Then look down to item #7 about how to post your code.
It will be formatted in a scrolling window that makes it easier to read.

Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?

Ops trace;

Thanks.. Tom... :slight_smile:

Hi achuree,

you wrote

hat I'm trying to do is start counting falling edges on the purple signal once the green signal goes low, after X(5 or 6) purple pulses I'll turn on the peak and hold driver for a set amount of time.

the foto shows more than 5 or 6 pink pulses until the green pulse has a state change.

My current code is below and it's not counting correctly. When channel 3 is low the counter just continues to increment can anybody see what I'm doing wrong?

The picture shows state-changes of the yellow channel. But your code has nothing inside that would do change an output.
So where does the state-change of the yellwo channel come from? Was the picture taken while the pump was controlled by another device?

I'm not sure what is channel 3 the pink channel? If this is the case how did oyu determine that the counter is counting up more?

Can you give a more detailed description with numbers that fit to the picture.

best regards Stefan

Please include short programs in your Post using the code button </> like this. That way people don’t have to download your code.

                                   // constants won't change. They're used here to set pin numbers
const int IAT120  = 8;             // the number of the 120 pulse/rev IAT pin
const int IAT6    = 5;             // the number of the 6 pulse/rev IAT pin  

                                     // variables that will change
int counter = 0;
int IAT120State;
int LastIAT120State;   
int IAT6State;                            
                                     
void setup() {

  pinMode(IAT120, INPUT);           // initialize the IAT pins as inputs
  pinMode(IAT6, INPUT);

  Serial.begin(9600);

  LastIAT120State = digitalRead(IAT120);
  
}

void loop() {

 IAT6State = digitalRead(IAT6);               // Reads the "current" state of the IAT6
 IAT120State = digitalRead(IAT120);               // Reads the "current" state of the IAT120

   if (IAT6State == LOW){
    if (IAT120State != LastIAT120State){           // If the previous and the current state of IAT120 are different, that means a pulse has occured       
       counter ++;
    } 
      else {
      counter;      
      }
  }
      
   Serial.print("Position: ");
   Serial.println(counter);

}

…R

You never update LastIAT120State

if (IAT120State != LastIAT120State

...R

Sorry about attaching the code instead of inserting it, it was late when the post went up.

I don’t have a schematic drawn up currently but I can work on it.

The state change of the yellow output was not controlled by the attached code, it was just attached for reference. Effectively what I need to do is move that yellow pulse to the right by 5 or 6 purple pulses so the injection event starts later.

Robin2:
You never update LastIAT120State

if (IAT120State != LastIAT120State

…R

Good catch, that could very well be the issue. I’ll make that change and try it in a minute.

                                          // constants won't change. They're used here to set pin numbers
const int IAT120  = 8;                    // the number of the 120 pulse/rev IAT pin
const int IAT6    = 5;                    // the number of the 6 pulse/rev IAT pin
const int FuelOut = 2;                    // the number of the fuel pin  

                                          // variables that will change
int counter = 0;                          // counter for IAT120 pulses
int IAT120State;                          // state of the IAT120 variable
int LastIAT120State;                      // last state of IAT120
int IAT6State;                            // state of the IAT6 variable       
                                     
void setup() {

  pinMode(FuelOut, OUTPUT);               // initialize the FuelOut pin as an output
  pinMode(IAT120, INPUT);                 // initialize the IAT pins as inputs
  pinMode(IAT6, INPUT);
  
}

void loop() {

  IAT6State = digitalRead(IAT6);          // Reads the "current" state of the IAT6
  IAT120State = digitalRead(IAT120);      // Reads the "current" state of the IAT120

  if (IAT6State == LOW){                  // if IAT6 is low then start the counting loop
    if (IAT120State != LastIAT120State){  // If the states of IAT120 are different a pulse has occured        
      counter ++;                         // increment counter
    }    
  }
  LastIAT120State = IAT120State;          // set states equal so counting can continue

  if (counter == 15){                     // if counter reads 15 then run fuel loop
    digitalWrite(FuelOut, HIGH);          // set fuel output to high
    delayMicroseconds(3000);              // keep on for 3000 microseconds
    digitalWrite(FuelOut, LOW);           // set fuel output to low
  } else {
    digitalWrite(FuelOut, LOW);           // otherwise fuel output should always be low
  }
  
  if (IAT6State == HIGH){                 // if IAT6 goes high then reset the counter
    counter = 0;
  }
  
}

Robins suggestion was spot on, that fixed that portion of the code. Thank you.

I added in the rest to control the fuel solenoid so you can see what I'm trying to accomplish. The code is working as posted but it doesn't seem to be very stable and it's skipping teeth at higher rpm. Any suggestions to remedy that?

In the capture below IAT120 is the yellow trace and FuelOut is the blue trace.

So what hardware are you using? Arduino Uno or something else.
What is the mximum rpm that can occur?

Do you still have the serial-output active?
at a baudrate of 9600 this is slowing down a lot.

Minimum is to change baudrate to 115200 or to deactivate it completely.
and if you really need to have serial output to shorten the number of characters to the minimum.

You have a kind of a realtime-controller which needs to be as responsive as possible.

Where did you get the names " IAT120" and " IAT6" from. it might be that these are the nmaes inside the schematic.
But wen don't ahve your schematic and these names requiere a "translation" to "fulepump" "tonewheel" every time reading them. porgrams become easier to understand if all names are selfexplaining.

best regards Stefan

zachuhree93:
[and it’s skipping teeth at higher rpm. Any suggestions to remedy that?

How many teeth-per-second are you trying to detect?

I suspect that delayMicroseconds() should go. The functions delay() and delayMicroseconds() block the Arduino until they complete. Have a look at how millis() is used to manage timing without blocking in Several Things at a Time. The same technique works with micros()

And see Using millis() for timing. A beginners guide if you need more explanation.

…R

StefanL38:
So what hardware are you using? Arduino Uno or something else.
What is the mximum rpm that can occur?

Do you still have the serial-output active?
at a baudrate of 9600 this is slowing down a lot.

Minimum is to change baudrate to 115200 or to deactivate it completely.
and if you really need to have serial output to shorten the number of characters to the minimum.

You have a kind of a realtime-controller which needs to be as responsive as possible.

Where did you get the names "IAT120" and "IAT6" from. it might be that these are the nmaes inside the schematic.
But wen don't ahve your schematic and these names requiere a "translation" to "fulepump" "tonewheel" every time reading them. porgrams become easier to understand if all names are selfexplaining.

best regards Stefan

I'm using an Arduino Due. Maximum rpm is 2000, one sensor input has 6 pulses per revolution, other has 120 pulses per revolution.

Serial output is gone from the code now, it was used as a reference at first but I can see with the oscilloscope now what's happening.

I see your point about the names, if you read the comments in my code they're pretty well explained.

Robin2:
How many teeth-per-second are you trying to detect?

I suspect that delayMicroseconds() should go. The functions delay() and delayMicroseconds() block the Arduino until they complete. Have a look at how millis() is used to manage timing without blocking in Several Things at a Time. The same technique works with micros()

And see Using millis() for timing. A beginners guide if you need more explanation.

...R

If my math is correct I'll need to read 4000 teeth per second when the pump is at its maximum of 2000 rpm with the 120 pulse/revolution tone wheel. For starters just to make this pump run on the stand 400 rpm would be sufficient which translates into 800 teeth per second.

I'll look into the millis() and micros() functions, I didn't realize the limitation of the delay() function until now. Thanks for the insight.

4000 pulses per second is one every 250 microsecs. That should be no problem for a DUE but it would probably be better to use an interrupt to detect the pulse rather than polling.

I have no experience with the DUE but the code in this link is derived from a program I used to detect and control the speed of a small DC motor with an Uno or Attiny. It should illustrate the general idea of using an interrupt.

...R

4000 teeth per second means

1/4000 = 0,25 milliseconds per tooth.
delaying for 3000 microseconds means delaying for 3 milliseconds.
In the meantime 3 milliseconds / 0,25 milliseconds = 12 teeth were not counted.

I estimate a loop that tries to keep track of every pulse by polling the pulses is not fast enough.
I'm not familiar with all the counter-modes that an Atmega2560 has. In earlier times I read about counters of the propeller-chip (a very different and unique microcontroller)

But as any microcontroller has counter-module with a lot of different modes something like the follwoing might be possible.

To get the reaction as fast and as precise as possible a hardware-counter could be setup to count a certain amount of pulses. The counting is enabled/disabled by the state of a certain IO-PIN. If number of pulses has reached this amount an interrupt occurs and the interrupt changes the state of another IO-pin and a third timer starts counting down. If countdown has reached zero change back the IO-Pin-state.

The above is just a functional description without knowing if an ATmega2560 even has three counters that could be setup this way.

Pure software-solution could be used too. But then there have to be done some calculations about how fast the loop can run, The basic principle then would be create a loop that runs as fast as possible. One loop is the smallest time-slice that is available. All other timing-actions must be a mutliple of that timeslice to achive deterministic behavior of the loop.

I'm pretty sure with assembler-code written for the propeller-chip this would be possible. Propeller-assembler is 32bit and every command can be executed conditional on two flags. You read right every assembler-command can be conditional. And you can write on runtime self-modifying code because all assembler-code is hold in RAM. RAM doesn't wear out like flash. (this are two things that make the propeller-chip so unique) It would mean to have the aditional effort of learning propeller-chip-assembler.

So first thing is to analyse can the ATmega2560 be fast enough to do it.
If not can anybody suggest a faster but still Arduino-IDE compatible microcontroller?

best regards Stefan

StefanL38:
I estimate a loop that tries to keep track of every pulse by polling the pulses is not fast enough.
I'm not familiar with all the counter-modes that an Atmega2560 has. In earlier times I read about counters of the propeller-chip (a very different and unique microcontroller)

I don't think there is any need to use a hardware counter.

Using an interrupt (as suggested in Reply #10) a 16MHz Arduino would probably be fast enough and a DUE certainly would be.

...R

Robin you are right,

So I coded a demo that shows how to poll for the pulses and the use of boolean variables to detect rising and falling edges of pulses. And most important how to create a 3000 microsecond pulse without blocking the rest of the code.
Additional the democode measures the time how long it takes do loop 1.000.000 times.

Your maximum speed is 2000 rpm * 120 pulses/rmp / 60 rpm/rpsecond = 4000 pulses/second

So minimum puslse-length is ( 1 / 4000 * 1.000.000 msecs/sec ) / 2 = 125 microseconds high 125 microseconds low
This democode who just uses polling without interrupts needs around 19 microseconds to run down one loop
So seems to be fast enough.

The demo-code has some comments trying to explain the functionality
read the comments and post questions if there are still questions

                                         // constants won't change. They're used here to set pin numbers
const int IAT120  = 8;                    // the number of the 120 pulse/rev IAT pin
const int IAT6    = 5;                    // the number of the 6 pulse/rev IAT pin
const int FuelOut = 2;                    // the number of the fuel pin  

int FuelPulseLength = 3000;
                                          // variables that will change
unsigned long counter     = 0;            // counter for IAT120 pulses

unsigned long LoopCounter = 0;            
const unsigned long LoopDivider = 1000000;

int     IAT120State;                      // state of the IAT120 variable
boolean IAT120Rising = false;
int LastIAT120State;                      // last state of IAT120

int     IAT6State;                        // state of the IAT6 variable      
boolean IAT6Rising = false;

unsigned long Start;

unsigned long FuelOutStart = 0;
boolean       FuelSetHigh = false;                                    

void setup() {
  
  pinMode(FuelOut, OUTPUT);               // initialize the FuelOut pin as an output
  pinMode(IAT120, INPUT);                 // initialize the IAT pins as inputs
  pinMode(IAT6, INPUT);
  Serial.begin(115200);
  Serial.println("start 1000000 loops take 20 to 40 seconds wait...");
  Start = micros();  
}

void loop() {

  IAT6State = digitalRead(IAT6);            // Reads the "current" state of the IAT6
  if (!IAT6Rising && IAT6State == HIGH) {   // rising edge detected
    IAT6Rising = true; 
    counter = 0; 
  }

  if (IAT6Rising && (IAT6State == LOW) ) {   // high level has finished = falling edge
    IAT6Rising = false;
  }

  IAT120State = digitalRead(IAT120);          // Reads the "current" state of the IAT120
  if (!IAT120Rising && IAT120State == HIGH)   // rising edge detected  // ! is the logical NOT-operator 
    { IAT120Rising = true; }

  if (IAT120Rising && IAT120State == LOW) {   // high level has finished = falling edge
    IAT120Rising = false;
    counter ++;
  }
  
  if (!FuelSetHigh && (counter == 5) ) {      // ! is the logical NOT-operator 
    digitalWrite(FuelOut, HIGH);              // Open FuelValve
    FuelOutStart = micros();                  // store SnapShot if microseconds
    FuelSetHigh = true;                       // boolean variabe to lock against additional SnapShots
  }

  // Check if FuelValve PulseLength has passed by
  // this check is done on every run of the loop
  // but only if the time specified in variable FuelPulseLength is over the condition becomes true
  if ( FuelSetHigh && ( micros() - FuelOutStart >= FuelPulseLength) ) {
    digitalWrite(FuelOut, LOW);               // close FuelValve
    FuelSetHigh = false;      
  }

  LoopCounter ++;

  // count up a lot of loops and then print time
  if (LoopCounter == LoopDivider) {
    Serial.print("one loop needs ");
    Serial.print( ( micros() - Start ) / LoopDivider );
    Serial.println(" microseconds");
    LoopCounter = 0;
    Start = micros();
  }
}

best regards Stefan

Thanks Stefan.

I skimmed through the code before uploading it and it seemed well enough so I tried it out, works perfect, even at 2000 rpm. Now I need to spend some time and understand how it really works. There is some odd variation in the pulse at low rpms where the duration doubles but there is an 8 microsecond dip between the pulses. I don't think it will be an issue but I need to understand it regardless.

Next step for me is to let the duration of the pulse vary according to an analog input to control fuel flow on the stand, this doesn't seem too difficult in my head. I'll attempt something tomorrow.

I appreciate the help, didn't expect to get this close this quick. Still a lot to learn.

I’ve been making adjustments over the past week and finally got to a point that I felt comfortable to try this on the engine. I got the engine to start up and it almost immediately ran away. After some head scratching I was able to understand why, I need to be controlling the injection duration based on degrees of crank angle instead of duration.

My question is how should I go about creating variables for RPM and degrees? I was thinking of using an interrupt to catch the change in pulses but do I run the calculation for RPM and degrees in the interrupt loop or in the main loop? I have some ideas how to go about the calculations but want to make sure I’m running them correctly and as quickly as possible.

The relationship between RPM and degrees is time. If you know the RPM (RPS is probably better) you know how long it takes for 360° so you can calculate the time for any other angle.

For example at 30 RPS a revolution takes 33,333 micro secs and 10° would take 926 µsecs (if my maths is correct)

...R

Should I perform that calculation inside the interrupt or pass values on to a variable and run the calculation in the main loop?

zachuhree93:
Should I perform that calculation inside the interrupt or pass values on to a variable and run the calculation in the main loop?

It would be much better to do the calculations outside the ISR if at all possible.

...R