m-72 arduino ignition

Hi, first of all i want to apologise about my language. I'm polish and some sentences may be gramatically incorrect. I want to eliminate contact breaker in my motorcycle's ignition. Its engine is classic boxer with two cylinders, where the spark fires every 360 degrees of crankshaft turn. I want to control ignition with only one hall sensor. Basing on rpm I'll get ignition phases. I want to sample rpm every two turns of crankshaft. My question in, how does interrupt affect on measuring time with micros()? Does it create any disortions when I take time from micros() being back in the loop after calling interrupt? If it's ok, i think it should be allright. Loop and interrupts are independent so program will crash when interrupt is called during the loop time.

int ignition = 13;
volatile byte rpmcount;
unsigned int rpm;
unsigned long start;
unsigned long dwelltime = 3333;
unsigned long pausetime = 333;
int angle;
boolean kflag = LOW;
extern volatile unsigned long timer0_overflow_count;

void setup()
{
  attachInterrupt(0, fire, RISING);
  pinMode(ignition, OUTPUT);
}

void loop()
{
  if (rpmcount >= 2)
  {
    if(kflag == LOW ) {
      rpm = 1000;
      kflag = HIGH;
    }
    else
    {
      rpm = 60*1000000 / (micros() - start) * rpmcount ;
    }
    timer0_overflow_count = 0;
    start = micros();
    rpmcount = 0;
    dwelltime = 60 * 1000000 / rpm * 20 / 360;
    angle = map(rpm, 1400, 3000, 18, 0);
    pausetime = 60 * 1000000 / rpm * angle / 360;
  }
}


void fire()
{ 
  delayMicroseconds(pausetime);  
  digitalWrite (ignition, HIGH);
  delayMicroseconds(dwelltime);
  digitalWrite (ignition, LOW);
  rpmcount++;
}

You can’t use micros() (or millis()) in an interrupt service routine as interrupts are disabled. Set a flag in the ISR and do the calculations based on flag state.

Yes you can, millis() just don't increment.

micros() is OK because that just reads the timer hardware.

Be aware that micros() can handle one rollover but not two (without interrupts being enabled again). It rolls over every 1024 uS.

delayMicroseconds() works by a simple count, so that will work as well.

Loop and interrupts are independent so program will crash when interrupt is called during the loop time.

What do you mean?

http://www.gammon.com.au/interrupts


extern volatile unsigned long timer0_overflow_count;

What is that for? I don't suggest playing with that.

Read the link above about "critical sections". Doesn't rpmcount need to be larger?

I think you need to restructure a bit. Your main loop seems to be resetting variables used in the ISR.

The main problem I foresee is that at low RPM the crank speed varies substantially during the engine cycle and this solution will require you to measure the average speed over the past revolution and then extrapolate forwards to predict when you will reach the required crankshaft position for ignition. To make this easier, I suggest you work back from the spark event and estimate the dwell time and processing latency to determine the latest point your crankshaft sensor could trigger and still enable you to achieve your maximum ignition advance at max rpm. Then orient the sensor to trigger at that point, so that you are minimising the amount of time you need to extrapolate for. The problem gets quite a bit easier to do if you use a toothed wheel rather than a single trigger since it more or less removes the need to extrapolate engine speed.

Rpmcount is low because of the problem PeterH saw. I think extern volatile unsigned long timer0_overflow_count; is ok, we take start with cleared clock, later when we get in if instruction again, at the beggining of the loop, we take next time with micros() (meaning end of "cycle") to count rpms, reset clock again and so on. Toothed wheel is great idea. I must make some and test it. This engine is really undemanding, with accuracy of russian contact breaker and disadvantages of camshaft, crankshaft, including poor dynamic of getting on rpms i won't be suprised if that bad timing will be enough. "Loop and interrupts are independent so program will crash when interrupt is called during the loop time. " I meant that it's ok until it manages to set timings before next interrupt.

But start = micros(); has no sense, when i reset that clock. It can be deleted, it will be even better.

I modified code to be able to predict new rpms in very short perspective. About wednesday I will receive hall sensor and basing on video player engine rotor I'll test circuit. I wanted to perform a test today, but hall sensor built in video player engine i have, doesn't have any signautres and for me it's impossible to figure out pins and required supply voltage so it's useless.

int ignition = 13;
volatile byte rpmcount;
unsigned int rpm;
unsigned int rpm0;
unsigned long dwelltime = 3333;
unsigned long pausetime = 333;
int angle;
boolean kflag = LOW;
extern volatile unsigned long timer0_overflow_count;

void setup()
{
  attachInterrupt(0, fire, RISING);
  pinMode(ignition, OUTPUT);
}

void loop()
{
  if (rpmcount >= 4)
  {
    if(kflag == LOW ) {
      rpm = 1000;
      rpm0 = 1000;
      kflag = HIGH;
    }
    else
    {
      rpm = 60*1000000 / micros() * rpmcount ;
    }
    timer0_overflow_count = 0;
    rpmcount = 0;
    predictedrpm = 2*rpm - rpm0;
    rpm0 = rpm;
    dwelltime = 60 * 1000000 / predictedrpm * 20 / 360;
    angle = map(rpm, 1400, 3000, 18, 0);
    pausetime = 60 * 1000000 / predictedrpm * angle / 360;
  }
}


void fire()
{ 
  delayMicroseconds(pausetime);  
  digitalWrite (ignition, HIGH);
  delayMicroseconds(dwelltime);
  digitalWrite (ignition, LOW);
  rpmcount++;
}

radmor_man: I think extern volatile unsigned long timer0_overflow_count; is ok, we take start with cleared clock, later when we get in if instruction again, at the beggining of the loop, we take next time with micros() (meaning end of "cycle") to count rpms, reset clock again and so on.

That doesn't really clear the clock, it clears the overflow counter. To clear it you would need to do:

TCNT0 = 0;

(and clear the overflow counter).

You shouldn't need to clear the clock. Do you reset your watch to midday every time you need to boil an egg and time it? I think not.

So what's going to happen if I leave if without overflow and any resest, and the time will reach 79minutes (I mean max. for micros()). Will it stop the program or just reset without my help?

You calculate by subtraction and it all works out.

eg.

if ( (micros () - startTime) >= interval) ...

Let's put it this way. If you need to cook pasta for 10 minutes, and it happens to be 11:55 you don't have to reset the clock do you? It "rolls over" and you know that 00:05 is the time to stop cooking.

There was a thread about this recently, can't find it unfortunately. However it does work.

I read an article and now I know that it works because there is no negative numbers in unsigned long and we can't substract them in straight way arithetically. I corrected the code once again and hope it's what you expected. Sorry for my lame question, but I'm beginner. I also changed "dwelltime" on contactbreaker_opentime, because dwell means charging coil, opening contact breaker is discharging in this case so it had no sense.

int ignition = 13;
volatile byte rpmcount;
unsigned int rpm;
unsigned int rpm0;
unsigned int predictedrpm;
unsigned long contactbreaker_opentime = 8000;
unsigned long pausetime = 3000;
unsigned long start;
unsigned long time;

unsigned long halfcamshaftturn_time;
int angle;
boolean kflag = LOW;

void setup()
{
  attachInterrupt(0, fire, RISING);
  pinMode(ignition, OUTPUT);
}

void loop()
{
  if (rpmcount >= 2)
  {
    if(kflag == LOW ) {
      rpm = 1000;
      rpm0 = 1000;
      kflag = HIGH;
    }
    else
    {
      time = micros() - start;
      rpm = 60*1000000 / (time) * rpmcount / 2;
    }
    start = micros();
    rpmcount = 0;
    predictedrpm = 2*rpm - rpm0;
    rpm0 = rpm;
    contactbreaker_opentime = 2 * 60 * 1000000 / predictedrpm * 24 / 360;
    angle = map(rpm, 1400, 3000, 18, 0);
    pausetime = 60 * 1000000 / predictedrpm * angle / 360;
  }
}


void fire()
{ 
  delayMicroseconds(pausetime);  
  digitalWrite (ignition, HIGH);
  delayMicroseconds(contactbreaker_opentime);
  digitalWrite (ignition, LOW);
  rpmcount++;
}

I stopped working on my project because of my student’s practices. I completed the program, added some sequences to provide easier start. Now I make PCB, I’m not advanced in making PCB’s, but I’m planning to mix THT and SMD, because f.ex. LM1815 I can get only in SMD version, it’s perfect for hall sensor. I’m worried about stable 5V voltage in difficult motorcycle environment. I wanted to mount module outside the engine, but I changed my mind when I realised that the hall sensor wires will be very long and may make some noise on my 5V line. I want to stabilize 5V making LM2675 application from datasheet adding diode and 15V transil + small resistors on the input. Is it necessary to turn on internal pullup on unused pins? What do you think about it? I’m afraid my work will be dump, because of noises from motorcycle.

int ignition = 13; //pin thyristor ignition module

int pin0 = 0;

volatile byte ignitioncount;

unsigned int rpm;
unsigned int rpm0;
unsigned int predictedrpm;

unsigned long contactbreaker_opentime = 16000; //contactbreaker open time in first cycle
unsigned long pausetime = 14500; //delay in first cycle, ignition 2 degrees after TDC
unsigned long start;
unsigned long time;

/*
hall sensor initializes 24 degrees before TDC and after calculating
predicted rpms in next cycle generates ignition timings
*/

int angle;

boolean flag0 = LOW;
boolean flag1 = LOW;

void setup()
{
  attachInterrupt(0, fire, RISING);
  pinMode(ignition, OUTPUT);
  
  pinMode(pin0, INPUT);
  digitalWrite(pin0, HIGH);
}

void loop()
{
  if (ignitioncount >= 2) //calibrates ignition moment every crankshaft turn
  {
    if (flag0 == LOW ) //provides going to starting sequence during engine start after first cycle
      { 
        flag0 = HIGH;
      }
    else
      {
        time = micros() - start; //time of one crankshaft turn
        rpm = 60*1000000 / time ;
        if (flag1 == LOW) //helps keeping timing accurate when we don't have rpms from two cycles
          {
            rpm0 = rpm;
            flag1 = HIGH;
          }     
      }
    start = micros();
    ignitioncount = 0;
    predictedrpm = 2*rpm - rpm0;     //basing on rpm change calculates rpm predicted in next cycle
    rpm0 = rpm;                     
    if (predictedrpm < 500)         //starting sequence, ignition point ~2degrees after TDC
      {
        contactbreaker_opentime = 16000;
        pausetime = map(predictedrpm, 300, 500, 14500, 8700);
      }
    else //normal work mode
      {
        contactbreaker_opentime = 2 * 60 * 1000000 / predictedrpm * 24 / 360;
        angle = map(predictedrpm, 1400, 3000, 18, 0);
        pausetime = 60 * 1000000 / predictedrpm * angle / 360;
      }
  }
}

void fire()
{ 
  delayMicroseconds(pausetime);  
  digitalWrite (ignition, HIGH);
  delayMicroseconds(contactbreaker_opentime);
  digitalWrite (ignition, LOW);
  ignitioncount++;  
}

You need to make sure the power supply and all inputs and outputs that are connected are protected from electrical noise - external wires can pick up radiated noise even if they are not connected to any electrical sources. You can ignore any I/O pins on the Arduino which are not used by the software and not connected to anything.

I want to power hall sensor with screened wire 3x0,75 for inverters, grounding one side to the module, about 15cm will be necessary. Is it efficent way to protect wires with 5V for arduino from noises caused by induction?

Screened wires are a good way to protect from radiated noise. Remember to only ground one end of the screen.

I don't understand what you mean by 'for inverters' - screened cable to carry the analog signals from the hall effect sensors would be sensible. I hope you aren't taking power to or from an inverter in the same cable.

I meant wires that are orginally used for inverters :) Sorry for my english.