advice needed for arduino as diesel injection brain...

Hello,

For use on our tractor puller we where looking for an uC and since there are a lot of them I looked at open-source, large user group and support so Arduino is the most obvious choice. The device have to react fast, within 20 microseconds and arduino's probably will do but I must not screwup with coding.

So the main question is; is the arduino platform capable?

Our setup isn't that complicated, since it is a tractor puller no fancy stuff is needed, it only have to trigger the injection and control injection time depending on throttle position. (potentio meter) The potentio meter also controls the pressure of the pump directly trough hardware and the timing of the injection is done by a rotating disc and infrared LED's so not much calculating things for the device. I did try to make some code using the examples and came up with the following:

/*
  Cuprum-2.0 simplified common rail injection system.
  Use interrupts for timing injections cycle, a timing disc (alternator) is mounted to camshaft.
  
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground, output will be 0-1023.
  Potentiometer used is stereo, second line will be used to control injection pump pressure.
  
  Attach timing signals: cyl1 pin 10, cyl2 pin 11, cyl3 pin 12 and cyl4 pin 13.
  
  Attach injector hardware: cyl1 pin 4, cyl2 pin 5, cyl3 pin 6 and cyl4 pin 7.
*/

// setup pin for throttle potentiometer:
int ThrottlePin = A0;
int ThrottleValue = 0;
// define throttle calibration values:
// 9000rpm = 150rps, 1000000us/150 = 6666us per rotation.
// 6666/360*30 = 555us injection time, 30 crank degrees at 9000rpm.
// 1024/555 = 1.8
int rpm = 9000.0;
int angle = 30.0;
int ThrottleCalib = 1024.0/(1000000.0/(rpm/60.0)/360.0*angle);
// setup timer pins for interrupts:
int cyl1 = 10;
int cyl2 = 11;
int cyl3 = 12;
int cyl4 = 13;
volatile int state = LOW;
// setup pins for injector drivers:
int inj1 = 4;
int inj2 = 5;
int inj3 = 6;
int inj4 = 7;

void setup()
{
  // attach action when interrupt gets actived:
  pinMode(cyl1, OUTPUT);
  attachInterrupt(0, inject1, RISING);
  pinMode(cyl2, OUTPUT);
  attachInterrupt(0, inject2, RISING);
  pinMode(cyl3, OUTPUT);
  attachInterrupt(0, inject3, RISING);
  pinMode(cyl4, OUTPUT);
  attachInterrupt(0, inject4, RISING);
  // initialize the digital injector pins as an output:
  pinMode(inj1, OUTPUT);     
  pinMode(inj2, OUTPUT);     
  pinMode(inj3, OUTPUT);     
  pinMode(inj4, OUTPUT);
}

void loop()
{
  // read the input on analog pin 0:
  ThrottleValue = analogRead(ThrottlePin);
  // recalculate throttle value to wanted microseconds:
  ThrottleValue = ThrottleValue / ThrottleCalib;
  // check the interrupts:
  digitalWrite(cyl1, state);
  digitalWrite(cyl2, state);
  digitalWrite(cyl3, state);
  digitalWrite(cyl4, state);
}

void inject1()
{
  digitalWrite(inj1, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj1, LOW);
}

void inject2()
{
  digitalWrite(inj2, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj2, LOW);
}

void inject3()
{
  digitalWrite(inj3, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj3, LOW);
}

void inject4()
{
  digitalWrite(inj4, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj4, LOW);
}

The device have to react fast, within 20 microseconds and arduino's probably will do but I must not screwup with coding.

So the main question is; is the arduino platform capable?

React to what? Being hit with a sledgehammer? It will react to that, nearly instantaneously.

int rpm = 9000.0;
int angle = 30.0;

Clearly, you need to learn the difference between ints and floats.

  attachInterrupt(0, inject1, RISING);
  pinMode(cyl2, OUTPUT);
  attachInterrupt(0, inject2, RISING);
  pinMode(cyl3, OUTPUT);
  attachInterrupt(0, inject3, RISING);
  pinMode(cyl4, OUTPUT);
  attachInterrupt(0, inject4, RISING);

After all this, interrupt 0 has inject4 assigned as the callback function. You might as well delete the others.

You can NOT call any form of delay in an interrupt service routine.

You can NOT call any form of delay in an interrupt service routine.

sp. "You should NOT call any form of delay in an interrupt service routine."

Yes, you can use Arduino to do what you want. You may have to go slightly off-piste, but I don't really see that interrupts are necessary or desirable.

Thanks for your input Paul,

int rpm = 9000.0;
int angle = 30.0;
int ThrottleCalib = 1024.0/(1000000.0/(rpm/60.0)/360.0*angle);

Reason was that I want the calculation to be accurate but the result being rounded up and have no clue how to that otherwise, will search on a float solution.

  pinMode(cyl1, OUTPUT);
  attachInterrupt(0, inject1, RISING);
  pinMode(cyl2, OUTPUT);
  attachInterrupt(1, inject2, RISING);
  pinMode(cyl3, OUTPUT);
  attachInterrupt(2, inject3, RISING);
  pinMode(cyl4, OUTPUT);
  attachInterrupt(3, inject4, RISING);

So this is what I meant? (if INPUT gets high OUTPUT is triggered for all 4 separately)

Why can't I call a delay within the interrupt routine? Is this not possible or a coding no-no? Theoretically the interrupt won't be actived before the timer ends, if that happens in practice it means I got 30K rpm and I am pretty sure my rods are a bigger problem then the software if that happens...

p.s. the Sledgehammer pulling team are friends so they won't hit me. ;-)

AWOL: "You should NOT call any form of delay in an interrupt service routine."

OK, coding no-no I presume. Maybe a suggestion to do it otherwise?

I'd probably do the whole thing in a simple loop. 1) see if the last analogue conversion has finished, and if it has, read the result and map it, and start another analogue conversion 2) for i = 0 to 'n'-1 cylinders Busy-wait on the sensor fire injector [n] delay un-fire injector [n]

So this is what I meant?

Depends on which Arduino you are using. The 328-based Arduinos only have two external interrupt pins (pins 2 and 3 that are interrupt #0 and interrupt #1).

int rpm = 9000.0;
int angle = 30.0;

9000 is an int. 9000.0 is a float. Which one do you think you can store in an int?

Why can't I call a delay within the interrupt routine?

Because interrupt routines are supposed to be as fast as possible, so that they don't interfere with other interrupt based things, like the clock. delay(), delayMicroseconds(), or a bunch of nop assembler calls are all wasting time, which you don't want to do in an interrupt service routine.

Ok guys, first thank you for having patience with me and helping out. (my only coding skills where with bash and at age 44 I am not that quick anymore ;)

About adding a decimal to the int, I read somewhere that automatically triggers a float calculation or creates a workaround for rounding up values before calculation was finished so I changed them to float. Also removed the calibration calculation for sake of speed.

Using a while or a for loop is not an option, onboard the puller life is pretty hard and I don't want to risk a complete engine failure because the software is waiting for a signal that is not gonna come due to a broken wire or other damage.

In the past I had an electronic device doing the same thing, an infrared transmitter/receiver combo triggered an NE555 which controlled the duration so that is why I want to use interrupts, intimidate response nomather what. Aren't all digital ports usable as interrupts? Or do I need to add some extra function to accomplish that?

/*
  Cuprum-2.0 simplified common rail injection system.
  Use interrupts for timing injections cycle, a timing disc (alternator) is mounted to camshaft.
  
  Attach the center pin of a potentiometer to pin A0, and the outside pins to +5V and ground, output will be 0-1023.
  Potentiometer used is stereo, second line will be used to control injection pump pressure.
  
  Attach timing signals: cyl1 pin 10, cyl2 pin 11, cyl3 pin 12 and cyl4 pin 13.
  
  Attach injector hardware: cyl1 pin 4, cyl2 pin 5, cyl3 pin 6 and cyl4 pin 7.
*/

// setup pin for throttle potentiometer:
int ThrottlePin = A0;
float ThrottleValue = 0;
// define throttle calibration values:
// 9000rpm = 150rps, 1000000us/150 = 6666us per rotation.
// 6666/360*30 = 555us injection time, 30 crank degrees at 9000rpm.
// 1024/555 = 1.8
float ThrottleCalib = 1.85;
// setup timer pins for interrupts:
int cyl1 = 10;
int cyl2 = 11;
int cyl3 = 12;
int cyl4 = 13;
volatile int state = LOW;
// setup pins for injector drivers:
int inj1 = 4;
int inj2 = 5;
int inj3 = 6;
int inj4 = 7;

void setup()
{
  // attach action when interrupt gets actived:
  pinMode(cyl1, OUTPUT);
  attachInterrupt(1, inject1, RISING);
  pinMode(cyl2, OUTPUT);
  attachInterrupt(2, inject2, RISING);
  pinMode(cyl3, OUTPUT);
  attachInterrupt(3, inject3, RISING);
  pinMode(cyl4, OUTPUT);
  attachInterrupt(4, inject4, RISING);
  // initialize the digital injector pins as an output:
  pinMode(inj1, OUTPUT);     
  pinMode(inj2, OUTPUT);     
  pinMode(inj3, OUTPUT);     
  pinMode(inj4, OUTPUT);
}

void loop()
{
  // read the input on analog pin 0:
  ThrottleValue = analogRead(ThrottlePin);
  // recalculate throttle value to wanted microseconds:
  ThrottleValue = ThrottleValue / ThrottleCalib;
  // check the interrupts:
  digitalWrite(cyl1, state);
  digitalWrite(cyl2, state);
  digitalWrite(cyl3, state);
  digitalWrite(cyl4, state);
}

void inject1()
{
  digitalWrite(inj1, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj1, LOW);
}

void inject2()
{
  digitalWrite(inj2, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj2, LOW);
}

void inject3()
{
  digitalWrite(inj3, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj3, LOW);
}

void inject4()
{
  digitalWrite(inj4, HIGH);
  delayMicroseconds(ThrottleValue);
  digitalWrite(inj4, LOW);
}

Here is a rough sketch, I wrote this based on 2 examples provided with the Arduino IDE "blink without delay", and "Analog Input". The sketch is written to read one trigger sensor, a throttle pot., and be able to fire one injector. You will have to modify the sketch to read 4 injector sensors, and fire 4 injectors.

Perhaps this is enough to get you going. Keep in mind I am a Novice at programming and there is probably lots of room for improvement. :)

/*
BASED ON EXAMPLES:
  Analog Input, Blink without Delay

 */
int cyl1 = 10;               //cam trigger sensor for injector 1
int trigger1 = LOW;          //variable to hold state of sensor for injector 1
int injectorpin = 13;        // the pin for the injector solenoid
int sensorPin = A0;          // the input pin for the throtttle potentiometer
int sensorValue = 0;         // variable to store the value coming from the sensor
long previousMillis = 0;     // will store last time injection time was updated

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000;           // interval at which to fire injectors(milliseconds)


void setup() {
  // declare the injectorpin as an OUTPUT:
  pinMode(injectorpin, OUTPUT);  
  pinMode (cyl1, INPUT);
}

void loop() {

  // read the value from the sensor:
  sensorValue = analogRead(sensorPin);  
  interval = sensorValue;////////////////////////////////////add mapping here or formula
  unsigned long currentMillis = millis(); //update currentMillis
  trigger1 = digitalRead(cyl1);           //read the trigger1 
  
  if(trigger1 == HIGH)
  {
  // turn the injectorpin on
  digitalWrite(injectorpin, HIGH); 
  previousMillis = currentMillis;         //note the time of firing the injector
  }
  
  if (trigger1 == LOW && (currentMillis - previousMillis) > interval)   //if trigger is off and injector has been on long enough, turn off injector
  {          
  // turn the injectorpin off:        
  digitalWrite(injectorpin, LOW);   
  }               
}

Hello boys, thanks for thinking with me.

If using a loop it opens another possibility to me, a rotary encoder for timing: http://nl.rs-online.com/web/p/rotary-encoders/2603780/?searchTerm=260-3780&relevancy-data=636F3D3126696E3D4931384E525353746F636B4E756D6265724D504E266C753D656E266D6D3D6D61746368616C6C26706D3D5E5C647B337D5B5C732D2F255C2E5D5C647B332C347D2426706F3D313426736E3D592673743D52535F53544F434B5F4E554D424552267573743D3236302D333738302677633D4E4F4E4526 (no clue how to hock it up)

int posPin = A1;          // the input pin for position sensor
int posValue = 0;         // variable to store the value from the sensor
int injectorpin1 = 13;        // the pin for the injector solenoid1
int injectorpin2 = 14;        // the pin for the injector solenoid2
int injectorpin3 = 15;        // the pin for the injector solenoid3
int injectorpin4 = 16;        // the pin for the injector solenoid4
int sensorPin = A0;          // the input pin for the throtttle potentiometer
int sensorValue = 0;         // variable to store the value coming from the sensor
long previousMillis = 0;     // will store last time injection time was updated

// the follow variables is a long because the time, measured in miliseconds,
// will quickly become a bigger number than can be stored in an int.
long interval = 1000;           // interval at which to fire injectors(milliseconds)

void setup() {
  // declare the injector pins as an OUTPUT:
  pinMode(injectorpin1, OUTPUT);  
  pinMode(injectorpin2, OUTPUT);  
  pinMode(injectorpin3, OUTPUT);  
  pinMode(injectorpin4, OUTPUT);  
}

void loop() {

  // read the value from the sensor:
  sensorValue = analogRead(sensorPin);  
  interval = sensorValue;////////////////////////////////////add mapping here or formula
  unsigned long currentMillis = millis(); //update currentMillis
  posValue = analogRead(posPin);  
  
  if(posPin == 0) // of camshaft position is 0 degrees then fire cylinder 1
  {
  // turn the injectorpin on
  digitalWrite(injectorpin1, HIGH); 
  previousMillis = currentMillis;         //note the time of firing the injector
  }
  
  if (posPin != 0 && (currentMillis - previousMillis) > interval)   //if pos is not 0 degrees and injector has been on long enough, turn off injector
  {          
  // turn the injectorpin off:        
  digitalWrite(injectorpin1, LOW);   
  }

  if(posPin == 256) // of camshaft position is 90 degrees then fire cylinder 3
  {
  // turn the injectorpin on
  digitalWrite(injectorpin3, HIGH); 
  previousMillis = currentMillis;         //note the time of firing the injector
  }
  
  if (posPin != 256 && (currentMillis - previousMillis) > interval)   //if pos is not 90 degrees and injector has been on long enough, turn off injector
  {          
  // turn the injectorpin off:        
  digitalWrite(injectorpin3, LOW);   
  }
               
  if(posPin == 512) // of camshaft position is 180 degrees then fire cylinder 4
  {
  // turn the injectorpin on
  digitalWrite(injectorpin4, HIGH); 
  previousMillis = currentMillis;         //note the time of firing the injector
  }
  
  if (posPin != 512 && (currentMillis - previousMillis) > interval)   //if pos is not 180 degrees and injector has been on long enough, turn off injector
  {          
  // turn the injectorpin off:        
  digitalWrite(injectorpin4, LOW);   
  }
               
  if(posPin == 768) // of camshaft position is 270 degrees then fire cylinder 2
  {
  // turn the injectorpin on
  digitalWrite(injectorpin2, HIGH); 
  previousMillis = currentMillis;         //note the time of firing the injector
  }
  
  if (posPin != 768 && (currentMillis - previousMillis) > interval)   //if pos is not 270 degrees and injector has been on long enough, turn off injector
  {          
  // turn the injectorpin off:        
  digitalWrite(injectorpin2, LOW);   
  }
                              
}

Is this the proper way to handle things? And will this be possible on a nano?

p.s. the (currentMillis - previousMillis) > interval I don't understand. In bash to would mean "if $currentTime minus $previousTime is larger then Interval (throttle position) stop injecting"???

int posPin = A1;          // the input pin for position sensor
int posValue = 0;         // variable to store the value from the sensor
int injectorpin1 = 13;        // the pin for the injector solenoid1

Consistency is good. If you were consistent, the 3rd variable would be named injectorPin1, which would be much easier to read, in my opinion.

long previousMillis = 0;     // will store last time injection time was updated

Time variables are unsigned long. You don’t normally need to accommodate time running backwards. At least not in our universe.

  if(posPin == 0) // of camshaft position is 0 degrees then fire cylinder 1
  if(posPin == 256) // of camshaft position is 90 degrees then fire cylinder 3
  if(posPin == 512) // of camshaft position is 180 degrees then fire cylinder 4
  if(posPin == 768) // of camshaft position is 270 degrees then fire cylinder 2

I don’t know what kind of analog device you are reading, but depending on exact values from an ADC with a resolution of +/- 1 is not a good idea. Your injectors are going to not fire when they are supposed to, a lot.

When to fire the injector. with respect to cam position, is typically a function of load, engine RPM, and a host of other sensor readings. I fear that you are oversimplifying a very complex process.

I don’t understand.
In bash to would mean “if $currentTime minus $previousTime is larger then Interval (throttle position) stop injecting”???

It means, without the $, exactly the same thing in C.

PaulS: long previousMillis = 0;     // will store last time injection time was updated

Time variables are unsigned long. You don't normally need to accommodate time running backwards. At least not in our universe.

I don't understand. In bash to would mean "if $currentTime minus $previousTime is larger then Interval (throttle position) stop injecting"???

It means, without the $, exactly the same thing in C.

I new that but I wanted to know what cyclegadgets reason was with that...

PaulS:   if(posPin == 0) // of camshaft position is 0 degrees then fire cylinder 1   if(posPin == 256) // of camshaft position is 90 degrees then fire cylinder 3   if(posPin == 512) // of camshaft position is 180 degrees then fire cylinder 4   if(posPin == 768) // of camshaft position is 270 degrees then fire cylinder 2

I don't know what kind of analog device you are reading, but depending on exact values from an ADC with a resolution of +/- 1 is not a good idea. Your injectors are going to not fire when they are supposed to, a lot.

When to fire the injector. with respect to cam position, is typically a function of load, engine RPM, and a host of other sensor readings. I fear that you are oversimplifying a very complex process.

I have know clue how to read a rotary encoder so I presumed that it would give me an output from 0-1023 like a potentiometer would. (the documentation of the automotive encoder also was talking about 1024 steps) You are writing that the injectors will gonna skip firing with that resolution, is that due to the duration of a single encoder step compared to the time the loop lasts? If so a loop won't work with my timing disc either since at 9K rpm the pulse from that is about 1microsecond. (that is why I tried to use interrupts in my first code)

A combustion engine isn't a very complex process at all, current automotive products make it complex due to drivability, environmental issues and so on. In tractor pulling we need an engine that runs at idle and at full throttle, we don't shift but open throttle and hold on. We have a 2.0l diesel engine making 400HP at 8000rpm with a boost pressure of 5bar, when hoocked on we rev up to 4000rpm (3bar of boost) and release the clutch. This is our conventional injected machine: https://picasaweb.google.com/lh/photo/4USFns0LHkMvU5lqaYSGmdMTjNZETYmyPJy0liipFm0?feat=directlink And this is our common-rail, no uC used, ne555, HEF4044 and HEF4081. https://picasaweb.google.com/lh/photo/do7wdb7tZnFQsgpcPFTMmNMTjNZETYmyPJy0liipFm0?feat=directlink

Throttle controls the injection time, the fuel pressure (800-2500bar) and timing advance (-10 at idle and -35 at full throttle) so the only thing the uC has to do is read an input and opens an output for a throttle defined amount of time. (ok, 4 times in a cycle)

If I understand correctly; rotation encoder isn't a good idea. using a loop either. So back to my first thought with using 4 interrupts and a distributor for giving injection pulses?

I have know clue how to read a rotary encoder so I presumed that it would give me an output from 0-1023 like a potentiometer would.

No, it will not. A rotary encoder is a digital device. It is like a whole bunch of little switches. You need to read the encoder fairly often to detect the direction it is moving, and how many times the little switches have been pressed.

There is an article on the playground explaining rotary encoders.

Throttle controls the injection time, the fuel pressure (800-2500bar) and timing advance (-10 at idle and -35 at full throttle) so the only thing the uC has to do is read an input and opens an output for a throttle defined amount of time. (ok, 4 times in a cycle)

So, when does the uC switch from -10 to -35 degrees? Doing that abruptly sounds like a bad idea to me.

If I understand correctly; rotation encoder isn't a good idea.

Using a rotary encoder is a fine idea, if you understand how to read it properly.

So back to my first thought with using 4 interrupts and a distributor for giving injection pulses?

You could do that with a Mega, with more external interrupts. The UNO only has two, and you'll probably want to use those to read the encoder, since that is the critical, time-dependent, input.

Insert Quote
Quote from: PaulS on Today at 07:02:36 AM
Code:

long previousMillis = 0; // will store last time injection time was updated

Time variables are unsigned long. You don’t normally need to accommodate time running backwards. At least not in our universe.
Quote
I don’t understand.
In bash to would mean "if $currentTime minus previousTime is larger then Interval (throttle position) stop injecting"??? It means, without the , exactly the same thing in C.

I new that but I wanted to know what cyclegadgets reason was with that…

That portion of code was based on an example from the Arduino IDE. I mostly just changed variable names. Turns out the example has the error of not using unsigned long for previousMillis and I did not catch it.
Here is the example code link: http://www.arduino.cc/en/Tutorial/BlinkWithoutDelay

PaulS: So, when does the uC switch from -10 to -35 degrees? Doing that abruptly sounds like a bad idea to me.

The uC won't, the throttle cable is (was in the past) connected to distributor so more throttle means more advance. Using throttle for advance timing is discussionable but in practice it has proven to work very well, and gives a very responsive engine. In the pic. the distributor is mounted on the front of the (old) engine directly on the camshaft pulley. (square aluminum box with lots of wires ;) Our previous device and schematics are over here: http://www.japie.deserver.nl/ftp/Cuprum/ if your interested.

I have read the playground encoder stuff and I see what you mean, I was expecting it to give a different value for each step but they only give pulses so code is always needed for calculating the position. (and adding error possibility when a pulse is missed or doubled)

I will use the exact setup as in the past with the alternator and a Mega to replace my electronics, thanks for helping me out...

The uC won't, the throttle cable is (was in the past) connected to distributor so more throttle means more advance. Using throttle for advance timing is discussionable but in practice it has proven to work very well, and gives a very responsive engine.

My point, exactly. The code you posted before had a fixed relationship between cam position and injector firing. The old setup had a variable relationship between cam position and injector firing.

If the uC is to be used, that variable relationship needs to be maintained, or the performance of your engine is going to change. And, probably not for the better.

PaulS: If the uC is to be used, that variable relationship needs to be maintained, or the performance of your engine is going to change. And, probably not for the better.

That's for sure Paul. The fun part is that we don't do it this way because it will gain power but mostly because others don't do it this way at all. Will order a Mega tomorrow and gonna have some fun...

Are there any update's about using Arduino with common rail?

Panther95

For sure there are only 2 External Interrupts. But almost every pin also supports PCINT, whichNick Gammon has a great page on and even shows them as being a little faster: http://www.gammon.com.au/forum/?id=11488

japie: Aren't all digital ports usable as interrupts? Or do I need to add some extra function to accomplish that?

Yes, using Pin Change interrupts, as CrossRoads kindly pointed out, I have a page about that:

http://gammon.com.au/interrupts

Not the way you wrote it though. I certainly think you can react in under 20 uS to an external event, if carefully coded. The interrupt processing itself takes about 3 uS to kick in, and then you need to work out which pin caused the interrupt, if using pin change interrupts.