advice needed for arduino as diesel injection brain...

In this part of your sketch, (start1micros, TimeFrame, and TimeOld all equal the same micros. You can write a sketch that prints the values and you will see that.

void Fire1()
{
  // set flag
  Fuel1 = true;
  // set start time
  start1micros = micros();
  // calculate how many uS 180 degrees last
  TimeFrame = (micros() - TimeOld);
  TimeOld = TimeFrame;
}

This function needs some time spent on it. The if(currentmicros.......... statements need checked. Using a piece of paper, put numbers in for the variables and ask yourself if the (if) function will result in true or false. Check both if statements using the same values then, values that represent a change in time.

// injection functions
int inject1()
{
  // read time
  currentmicros = micros();

  // if the injector is closed see if it is time to open
  if(currentmicros - start1micros > (TimeFrame - (InjectionAdvance + InjectionDuration)) && InjectorState1 == LOW)
  {
    digitalWrite(inj1,HIGH);
    InjectorState1 = HIGH;
  } 

  // If the injector is open and it is time to close the injector do so.
  if(currentmicros - start1micros > (TimeFrame - InjectionAdvance) && InjectorState1 == HIGH) 
  {
    digitalWrite(inj1, LOW);
    InjectorState1 = LOW;
    // reset the flag that was caused by the interrupt
    Fuel1 = false;
  }
}

I made some changes in the code. I made it more simple with just one injector in the loop and functions. It is still not correct and may not compile maybe it will help. I getting tired so, I need to stop where I am at.

// setup pin for throttle potentiometer:
int ThrottlePin = A0;
unsigned long ThrottleValue = 0;
// define throttle calibration values:
// we like to inject from -45 to -10 TDP making 35 degree injection duration max.
// crank duration in uS: (1000000/(rpm/60))/360*35
// at 10K rpm that is 583uS since the throttle has 1023 steps 1023/583=1.75
float ThrottleCalib = 1.75;
// base idle injectiontime is 50uS
unsigned long IdleDuration = 50;
// define microsecond timeframe between 2 injects = duration 180 degree crankshaft
unsigned long TimeFrame = 0;
unsigned long TimeOld = 0;
// define injection cycle times
unsigned long InjectionAdvance = 0;
unsigned long InjectionDuration = 0;
unsigned long currentmicros = 0;
// state of injectors
int InjectorState1 = LOW;
int InjectorState2 = LOW;
int InjectorState3 = LOW;
int InjectorState4 = LOW;
// injection start times
unsigned long start1micros = 0;
unsigned long start2micros = 0;
unsigned long start3micros = 0;
unsigned long start4micros = 0;
// setup timer pins for interrupts:
int cyl1 = 1;
int cyl2 = 2;
int cyl3 = 3;
int cyl4 = 4;
volatile int state = LOW;
// setup pins for injector drivers:
int inj1 = 2;
int inj2 = 4;
int inj3 = 6;
int inj4 = 8; 
// injection functions flags
volatile int state1 = LOW;
volatile int state2 = LOW;
volatile int state3 = LOW;
volatile int state4 = LOW;
volatile int Fuel1  = false;
volatile int Fuel2  = false;
volatile int Fuel3  = false;
volatile int Fuel4  = false;

void setup()
{
  // initialize the digital injector pins as an output:
  pinMode(inj1, OUTPUT);     
 
  // attach action when interrupt gets actived:
  pinMode(cyl1, INPUT);
  attachInterrupt(1, Fire1, RISING);
 
}

void loop()
{
  // check interupts
  digitalWrite(cyl1, state1);

  // read throttle position
  throttle();
  
  // if the flag is true start the inject function 
  if (Fuel1 == true)
  {
    inject1();
  }
}

/////////////////////////////////////////////////////functions below////////////

// set flags    
void Fire1()
{
  // set flag
  Fuel1 = true;
  // set start time
  start1micros = micros();

}

// injection functions
int inject1()
{
  // read time
  currentmicros = micros();

  // if the injector is closed see if it is time to open
  if(currentmicros - start1micros > (TimeFrame - (InjectionAdvance + InjectionDuration)) && InjectorState1 == LOW)
  {
    digitalWrite(inj1,HIGH);
    InjectorState1 = HIGH;
  } 

  // If the injector is open and it is time to close the injector do so.
  if(currentmicros - start1micros > (TimeFrame - InjectionAdvance) && InjectorState1 == HIGH) 
  {
    digitalWrite(inj1, LOW);
    InjectorState1 = LOW;
    // reset the flag that was caused by the interrupt
    Fuel1 = false;
  }
}

// throttle reading
int throttle()
{
  // read the throttle input on analog pin 0:
  ThrottleValue = analogRead(ThrottlePin);
  // see if we are trying to have fun :-)
  if (ThrottleValue > 5)
    {
    // injection ends uS/360*350 to make sure all juice is injected 10 degree before TDP.
    InjectionAdvance = TimeFrame/360*10;
    // recalculate throttle value to wanted microseconds:
    InjectionDuration = ThrottleValue/ThrottleCalib;
    }
  // it seems we are idling :-(
  else
    {
    // make sure all juice is injected at TDP.
    InjectionAdvance = 0;
     // if we are running 900rpm (33333uS) we reduce injection time
    if (TimeFrame > 33333)
      {
      // reduce injection time
      IdleDuration=IdleDuration-5;
      InjectionDuration = IdleDuration;
      }
    // if we are running 800rpm (37500uS) we inject a bit longer
    else if (TimeFrame < 37500)
      {
      // expand injection time
      IdleDuration=IdleDuration+5;
      InjectionDuration = IdleDuration;
      }
    }
}

Hello cyclegadget, don't make it to late... :wink:

I understand the done thing, but when going for a first dry run I will add serial prints with micros at every step and dump them into a file so I can review afterwards to see how much time is spent on every step. (suppose reading/calculating throttle takes to long I can try to strip that down)
And yes I will go for one cylinder at first step.

void Fire1()
{
  // set flag
  Fuel1 = true;
  // set start time
  start1micros = micros();
  // calculate how many uS 180 degrees last
  TimeFrame = (start1micros - TimeOld);
  TimeOld = TimeFrame;
}

The TimeFrame and TimeOld are common values shared between all 4 interrupts since TimeFrame is the time in uS for 180 degree crank. (did clean it up in above, no need for double read micros(); )
So those may look equal but when looking at more then 1 interrupt/cylinder they are not. (I think :wink:

if(currentmicros - start1micros > (TimeFrame - (InjectionAdvance + InjectionDuration)) && InjectorState1 == LOW)

currentmicros minus start1micros gives me elapsed time since the interrupt.
TimeFrame (180) minus (InjectionDuration (35) plus InjectionAdvance (10)) gives me the "wait time" from interrupt to start_injecting.

if(currentmicros - start1micros > (TimeFrame (180) - InjectionAdvance (10)) && InjectorState1 == HIGH)

TimeFrame minus InjectionAdvance gives the wait_time for stop_injecting.

I really appreciate your help, I am not a coder, give me a piece of aluminum and a lathe and I can make art but besides some bash scripting I am not that good...
Thank you for helping me out!

Schermafbeelding 2013-02-16 om 08.19.35.PNG

cyclegadget:
It is easier for me to write a sketch to show how a return works

Given that the sketch was written to demonstrate the use of return, it's ironic that your sketch ignores the return value.

PeterH is correct, I did not write the sketch properly. I need a signature under my avatar that says "Warning Novice on board!" .

I rewrote the return sketch, I think it is correct this time. Sorry for the mistake I made earlier.

const int ledPin =  13;      // the number of the LED pin

// Variables will change:
int DonesAnswer = 0;           // variable to pass "done" to         

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

long interval = 1200;           // interval at which to blink (milliseconds)

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);   
  Serial.begin(9600);  
}

void loop()
{

  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > interval)
  {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   

   DonesAnswer = whatdonedoes();   // transfer "done" to DonesAnswer using the function

    Serial.print("done = ");
    Serial.println(DonesAnswer);

    digitalWrite(ledPin, DonesAnswer);
  }
}

////////////////////////////////function with a return called done//////////
int whatdonedoes()
{
  static int done = 0;  //set done to static int

  // if the LED is off turn it on and vice-versa:
  if (done == 1)
    done = 0;
  else
    done = 1;

  return done;
}

Nice signature :wink:

However I got the point about the done in this case. (in bash I use it to halt or continue script so this case was new to me)

I did some test with the program, added several "timestamps" and at the end of the loop printed them all. (to prevent serial prints to slow down things)
Very disturbing is the fact that reading analog and writing to pwm takes over 200uS, if this delay happens at the wrong point my injection timing will be 5-10 degree to late or even worse if that happens while injecting it can mean 20-30% more diesel which melts my pistons.
400HP+ with a 4 cylinder 2.0l diesel is a bit on the edge, with 200cc diesel per 1000 injections it will run around 1000 degree Celsius and the pistons will melt at 1100... (with 160cc 800 degree)

To wrap it all together; running a loop and take action based on interrupt created flags inside that loop isn't gonna work since injection moment and duration are both time critical give or take 10uS. (20 total)

So back to my first plan (disapproved by every coder around the world :wink:

A loop that checks and set stuff, throttle, pump pressure, idle, rpm limiter etc.
Interrupts that open injector, wait injection time and close it. (during that time the loop is stalled but who cares)
connect timer to throttle so advance is manually controlled. (0 at idle, -45 at full load)
Main thing is that the time critical functions (opening and closing injectors) are interrupt driven, all other stuff can have some delay the only result of that can be that throttle feels a bit sloppy at high rpm (when times between interrupts get shorter) but in that case I was all ready holding on to my wheel and praying...

japie:
Interrupts that open injector, wait injection time and close it. (during that time the loop is stalled but who cares)

As well as being ugly, I'm not at all convinced that you will get it to work. What would IMO be far better would be to use the timer interrupt to start the injection and at that time configure a timer to trigger another interrupt when the injection needs to end. This way the real time part of your code runs with minimal latency and does not lock the processor up for the duration of the injection, and the background processing in loop() can run and do whatever it needs to. Just ensure that you don't do anything that disables interrupts.

I have been searching for some solution to replace the delay and found Arduino Playground - Timer1

Was thinking about the interrupt opening the injector and starting a timer, the timer does the closing so I suppose you where thinking the same.

shot in the dark:

attachInterrupt(18, Inject1, RISING);

void Inject1()
{
  // calculate how many uS 180 degrees last
  TimeFrame = (micros() - TimeOld);
  TimeOld = TimeFrame;
  // open injector1
  digitalWrite(inj1,HIGH);
  // start timer1
  Timer1.initialize(ThrottleDuration);
  Timer1.attachInterrupt(Close1);
}

void Close1()
{
  // close injector1
  digitalWrite(inj1, LOW);
}

Only I don't think I can share the timer between all 4 injectors but since I use the mega it seems I have 3 extra ones so 1,3,4 and 5 are all 16 bit.
From the docs timer0 is used for millis, micros, delay and PWM pin13 (my pump pressure output) so I won't touch it.

Does something like above work or am I ready for mental care? :wink:

japie:
Only I don't think I can share the timer between all 4 injectors

If you have an even firing engine then you could just generate the interrupt at the total injection frequency rather than the per-cylinder frequency, and cycle through the cylinders in the appropriate order.

I've missed/forgotten how you're going to synchronise the injection with the engine physical position. Do you already have the crank position and engine phase sensing sorted out? Presumably you'll have some sort of phase locked loop tracking where (you think) the engine is.

@japie

Could you post the code that you used to find this

Very disturbing is the fact that reading analog and writing to pwm takes over 200uS,

I am not saying your wrong but, I can bench mark the Uno, Teensy 3.0, and the Due for comparison. I also have a Maple clone but, it is not as easy for me to put it to good use.

With some clever coding and dividing those 2 actions the times can probably be brought down but the main issue is that the time critical stuff is done in the loop, it should be real time...

The injection moment is in sync with the engine trough an alternator, I have a pcb with 4 IR transmitters on it and one with 4 receivers, between those there is a rotating plate with a little hole so at every TBP one of the receivers gets a pulse.
The casing with the PCB's in it can be attached to the throttle to create more advance when giving more throttle. (the above system has been used in the past by me and works flawless)
Nothing fancy, if receiver1 gets a pulse injector1 has to spray.

japie:
The injection moment is in sync with the engine trough an alternator, I have a pcb with 4 IR transmitters on it and one with 4 receivers, between those there is a rotating plate with a little hole so at every TBP one of the receivers gets a pulse.

I don't know what a TBP is. When you say alternator, do you mean the alternator that provides electrical power for the vehicle? How is that connected to the crank?

CrossRoads:
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:
Gammon Forum : Electronics : Microprocessors : Interrupts

Nice link, thanks. Just nit-picking, but I believe that Timer1 Capture is also an external interrupt pin, making for a total of 3.

@cyclegadget

I did timings by adding stuff between lines like:

int readthrottle = micros();

and at the end of the loop print all of them in one line, substracting manually gave me the readings...

@PeterH

Sorry 4 misleading you, I am pretty busy with labour so most of the time I do computer stuff early in the morning before going to work.
I meant TDP or TDC: Top Dead Center, when the piston is in it's most uprising/compression point.
Also I don't mean alternator but distributor.
So I can give an electronical pulse at exactly the moment I want to inject, in the past I used almost the same setup with an NE555 as timer for injection duration but since I got involved with the arduino while building a data logger I thought it was a nice idea to use it for injecting.
In my old setup the distributor :wink: was connected to the throttle so when giving more throttle the timing would advance from -10 to -50 degree TDP.

Actually there is nothing wrong with that, in my current conventional plunger Bosch A-pump I modified the plungers by grinding the top in an angle and the helix flat so when giving more throttle injection moment will advance. (normally the top of the plunger is flat, halfway down there is a groove in an angle (the helix) that is connected via another groove to the top, when the plunger moves upwards the helix will pass a hole and bleeds fuel from the top so at that point the injection stops. Since that helix is under an angle you can turn the plunger so the effective stroke of the plunger is extended and therefore delivering more fuel. http://transportation.centennialcollege.ca/oduffy/fuels/HD%20fuels%203/inline.pdf)

@afremont

The other timers also all connect to a pin so that would mean they are not useable.
When looking at the example code it looks like the timers can be used without using the pin attached to it, no clue.
Also no clue how to use the other 3...

If I can't find out I will use a microsecond delay, bad coding leaving the arduino doing a lot of nothing but see no reason why it should not work.

Hello there, got the basic timer code working:

#include "TimerOne.h"
#include "TimerThree.h"

int ledPin = 13;
volatile int state = LOW;
volatile int InjectionCycle1  = false;

void setup() 
{
  pinMode(ledPin, OUTPUT);
  attachInterrupt(0, Inject1, RISING); // interrupt 0 = pin 2
  Timer1.initialize(1000000);
  Timer1.stop(); //stop the counter
  Timer1.disablePwm(9); // disables PWM on pin 9
  Timer1.disablePwm(10); // disables PWM on pin 10
  Serial.begin(9600);
}
 
void loop()
{
}

void Inject1()
{
  if (InjectionCycle1 == false)
  {
    Serial.print("start injection cycle: ");
    Serial.println (millis());
    InjectionCycle1 = true;
    Timer1.setPeriod(1000000);
    Timer1.attachInterrupt(OpenInjector1);
  }
}

void OpenInjector1()
{
  if (InjectionCycle1 == true)
  {
    Serial.print("open injector: ");
    Serial.println (millis());
    digitalWrite(ledPin, HIGH);
    Timer1.stop();
    Timer1.detachInterrupt();
    Timer1.setPeriod(2000000);
    Timer1.attachInterrupt(CloseInjector1);
  }
}

void CloseInjector1()
{
  if (InjectionCycle1 == true)
  {
    Serial.print("close injector: ");
    Serial.println (millis());
    Serial.println ("");
    digitalWrite(ledPin, LOW);
    InjectionCycle1 = false;
    Timer1.stop();
    Timer1.detachInterrupt();
  }
}

Tested this and it works as expected, next step integrate all other stuff, will port when I have it complete.
In this setup the timer replace delay. (and is not stalling the loop like delay)

edited since in first instance I used Timer1 and Timer3 both for both delays:
Using one timer for the cycle works also good so I can use Timer1 for cylinders 1 and 4 and Timer3 for cylinders 2 and 3.
Since the firing order is 1-3-4-2 there is 180 crank degree extra time between interrupt firing cycles, better would be a per cylinder dedicated timer but rewriting the Timer1 code for the extra timers on the mega is out off my league...

Finished the total code for the injection system based on 2 timers.
Included idle revving, soft rpm limit for over-revving and hard rpm limit in case of driveline breakage.

cuprumV2.ino (10.7 KB)

Japie, where did you download the timerone and timerthree libraries?

I was wanting to do some testing with your code but, I think maybe I am missing the correct library.

I am glad you have been sticking with your project and making it work!

Also, I want to say that you should speed up your serial baud rate to 115,200, which is the maximum that the IDE can use. If you have a terminal program, you maybe able to choose a much higher rate. Serial communication can really hurt the performance of your sketch. I will make a sketch to show you the difference. I will post it here soon.

Test code using 9600 baud:

int ledPin = 13;

//variables to keep track of time of injection code////
unsigned long time1 = 0;
unsigned long time2 = 0;


void setup() 
{
  pinMode(ledPin, OUTPUT); 
  digitalWrite(ledPin, LOW);
  Serial.begin(9600);
}

void loop()
{

  digitalWrite(ledPin, HIGH);
  time1 = micros();
  Serial.print("light the LED for all to see! ");
  Serial.println(time1);

  Serial.print("Turn off the LED it does not need to be ");
  digitalWrite(ledPin, LOW);
  time2 = micros();
  Serial.println(time2);

  Serial.print("total time = ");
  Serial.print(time2 - time1);
  Serial.println("micros");
  delay(5000);  
}

Typical output in micros():

light the LED for all to see! 35358256
Turn off the LED it does not need to be 35373908
total time = 15652micros

Using 115200 baud:

light the LED for all to see! 35029036
Turn off the LED it does not need to be 35030356
total time = 1320micros

Removing all but total time print, code:

int ledPin = 13;

//variables to keep track of time of injection code////
unsigned long time1 = 0;
unsigned long time2 = 0;

void setup() 
{
  pinMode(ledPin, OUTPUT); 
  digitalWrite(ledPin, LOW);
  Serial.begin(115200);
}

void loop()
{
  digitalWrite(ledPin, HIGH);
  time1 = micros();

  digitalWrite(ledPin, LOW);
  time2 = micros();

  Serial.print("micros = ");
  Serial.println(time2 - time1);

  delay(5000);  
}

results:

micros = 12
micros = 12
micros = 8
micros = 8
micros = 8

Hi cyclegadget,

The timer libs came from the playground: Arduino Playground - HomePage

I know about the serial port speed but I have an arduino data logger spitting out it's reading true the serial port to my FriendlyARM dashboard.
The arm reads the serial port with a bash script (simple cat) and writes the data on the screen, when using 115200 I ran into problems on that so I stepped back in speed and somehow kept that for other projects...

If all works out well I remove the serial stuff completely...