Can I use analogwrite inside an ISR()?

Hi there

I have a sketch to read a CPU fan RPM

Nothing fancy

volatile int rpmcount = 0;
int RPM = 0;
unsigned long lastmillis = 0;

void setup()
{
attachInterrupt (digitalPinToInterrupt(2), rpm_fan, FALLING);
}`




void loop()
{
if (millis() - lastmillis >= 1000) { /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
    detachInterrupt(digitalPinToInterrupt(2));    //Disable interrupt when calculating
    RPM = rpmcount * 60;  /* Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.*/
    rpmcount = 0; // Restart the RPM counter
    lastmillis = millis(); // Uptade lasmillis
    attachInterrupt(digitalPinToInterrupt(2), rpm_fan, FALLING); //enable interrupt
  }
}

void rpm_fan() { /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;

//    analogWrite(PWM, 124);
}

My inquiry is if I can put inside the rpm_fan() function wich is activated in each interrupt call the commented line (analog write).

Im trying to simulate a EFI fuel inyection system, and I´m currently using a PC fan to trick the RPM count and to control a fuel inyector opening in each pulse.

Is it feasible this way? Also, can I change the 124 value to a variable (I will later use a formula to calculate the ms that the PWM needs to be HIGH and will come in handy)

Thanks in advance

On the UNO (you didn't specify another model so you must use an UNO) you can call analogWrite() inside an ISR.

Is it feasible this way?

Not the way the code is organized at the moment. I don't see any need to call it inside the ISR, calling it in the loop is definitely the better way.

I will later use a formula to calculate the ms that the PWM needs to be HIGH and will come in handy

You know that analogWrite() is handling that for you, don't you?

void rpm_fan() { /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;

  analogWrite(PWM, 124);
}

That sets a pin emitting an unchanging pulse pattern.

If you have a pin already analogWrite( pin, 0 ); then you can change a value in a cpu register and the pulse width will change way faster than the first analogWrite() call that set everything up nice and safe for twiddling. You change registers, you change how port pins operate and keep operating until changed again.

Something else, you don't need the attach-detach calls to zero 'rpmcount' every second. Disable interrupts using noInterrupts(), zero the int and enable interrupts using interrupts() is quicker.

255 x 60 = 15300

Could you be using a byte for interrupt count? There would be no need to disable interrupts, 8 bit operations are 1 cycle "atomic".

Never use a bigger variable than you need on an 8 bit machine and limited RAM just makes it worse.

Fabius_rex:
Is it feasible this way? Also, can I change the 124 value to a variable (I will later use a formula to calculate

The value 124 in this code

analogWrite(PWM, 124);

is not a number of millisecs. It is the proportion, relative to 255 (i.e. 124/255ths) of the time when the pulse will be HIGH.

The actual time depends on the PWM frequency. The standard Arduino PWM frequency is 490Hz IIRC. It can be changed.

I can't think of any reason why updating analogWrite() would be so urgent that it needs to happen in an ISR. Are you sure you fully understand what analogWrite() does?

...R

Thanks for your answers

If I absorbed everything right prior and before posting the thread I think I can assume this.

PWM frequency in arduino uno (yes it was an uno sorry for not specify) is 500 Hz.

That means each cycle is 2 ms

I have a variable. RPM, which is, translated to frequency ranged from 20 Hz to 200 hz

So for example at 20Hz rotation (or 20Hz in ISR callings) 50 ms have passed between pulses.
I have 5 sensor polling in the loop function that will calculate the time (in ms) that I need to open a solenoid (fuel inyector), this value will be always between 0.8 and 1.8ms (it can never be greater that the time between interrupts).

So, I use the ISR that counts the rpm, and wanna put inside the analogWrite(INY_PIN, 127)

Since 127 is almost 50% duty cicle, for a frequency of 500Hz that will give 1ms HIGH and 1 ms LOW. (Correct me if Im wrong, Im type-reasoning)

But, as said, I need to be able to change that duty cicle so it goes from

40% -> analogWrite(INY_PIN, 102); (0.8ms HIGH, 1.2ms LOW)
to
90% -> analogWrite(INY_PIN, 230 ); (1.8ms HIGH, 0.2ms LOW)

So, I´m asking if I can put the analogwrite function inside the ISR()
and also

If I can create a variable that will go from 0 to 255 (duty cicle for the analogwrite function) let call it IT (Inyection Time = dutycicle)

So it will be like

int INY_PIN = 10; //(D10)
int Time = 0; //intermediate variable for calculating time
int TI = 0; // Time in MS the PWM pulse need to be HIGH, and using map function translate to 0-255
//RPM
volatile int rpmcount = 0;
int RPM = 0;
unsigned long lastmillis = 0;
//RPM
void setup()
{
attachInterrupt (digitalPinToInterrupt(2), rpm_fan, FALLING);
}



void loop(){


Time = 150/1000 //Set an example that something was calculated using feedback from polling data from sensors
TI = map(Time, 0.8, 1.8 102, 230);

  if (millis() - lastmillis >= 1000) { /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
    noInterrupts()                                   

    RPM = rpmcount * 60;  /* Convert frequency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.*/

    rpmcount = 0; // Restart the RPM counter

    lastmillis = millis(); // Update lastmillis

    interrupts()
  }// Cierra IF
}

void rpm_fan() { /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
  rpmcount++;
analogWrite(INY_PIN, TI);

I also read that I can use any digital pin and make kind-of my own square wave signal using delay. But isn´t that impossible inside the ISR()

Something like this

//RPM
volatile int rpmcount = 0;
int RPM = 0;
unsigned long lastmillis = 0;
//RPM

int TI;

void setup()
{
attachInterrupt (digitalPinToInterrupt(2), rpm_fan, FALLING);
}

void loop()
{
if (millis() - lastmillis >= 1000) { /Uptade every one second, this will be equal to reading frecuency (Hz)./
noInterrupts()
RPM = rpmcount * 60; /* Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.*/
rpmcount = 0; // Restart the RPM counter
lastmillis = millis(); // Uptade lasmillis
interrupts()
}

void rpm_fan() { /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
rpmcount++;

digitalWrite(INY_PIN, HIGH); // I cannot put here how much time I want it HIGH and then shut it down.

}

Also the PWM pulse need to happen always AFTER interrupt number 1, and always BEFORE interrupt number N (between interrupts)

You can change the PWM duty cycle by changing a register. Uno has 2 PWM pins that run 1024 Hz.

As far as determining RPM..... "I have a variable. RPM, which is, translated to frequency ranged from 20 Hz to 200 hz"

At 200 Hz it takes 5 whole milliseconds to turn once. For Arduino that can be a long time.
You will do better to count microseconds per rev than wait 1000 milliseconds and see how many revs were counted, which leaves a fraction of a rev not counted.

Don't use delay(). It will limit what your sketch can do.

Fabius_rex:
So, I´m asking if I can put the analogwrite function inside the ISR()
and also

If I can create a variable that will go from 0 to 255 (duty cicle for the analogwrite function) let call it IT (Inyection Time = dutycicle)

This has all the hallmarks of an XY problem

Step back from the question of HOW to implement this and tell us in English (not code) WHAT needs to happen.

Here are some questions that occur to me - but please feel free to add lots of other information

  • You say the injector needs a pulse between 0.8 and 1.8ms - where do those numbers come from?
  • Does the injector pulse need to be synchronized with the position of the pistons?
  • What is triggering the ISR?

My own feeling is that analogWrite() is not relevant for your problem - but I am quite willing to be proved wrong.

...R

Robin2:
This has all the hallmarks of an XY problem

Step back from the question of HOW to implement this and tell us in English (not code) WHAT needs to happen.

Here are some questions that occur to me - but please feel free to add lots of other information

  • You say the injector needs a pulse between 0.8 and 1.8ms - where do those numbers come from?
  • Does the injector pulse need to be synchronized with the position of the pistons?
  • What is triggering the ISR?

My own feeling is that analogWrite() is not relevant for your problem - but I am quite willing to be proved wrong.

...R

The pulse from 0.8ms to 1.8 comes from using the ideal gas law equation (my sensors are measuring temperature and pressure) the volume is the one of the cylinder. Also I know from the manufacturer that the original injection runs in that frame of time.
The inyector pulse needs to be synced with the PMS (It´s a EFI system so only 1 inyector here triggered in each PMS) for that I´m using for the moment a CPU fan to simulate the RPM pulses.
The ISR is being triggered for each pulse that the cpu fan makes (2 pulses per rotation) that equals 1 turn of the crankshaft, that equals 1 otto cicle per cylinder if we are talking of a 4 cylinder engine, that we are.

So, I´m here, because I think that "Y" (the analog write function inside the ISR) can solve my "X" (will the Inyector be opened for each pulse of the CPU Fan, and in the amount of time (translated to PWM Duty cicle) calculated from an equation that takes into consideration the data being polled by the analog sensors?

GoForSmoke:
You can change the PWM duty cycle by changing a register. Uno has 2 PWM pins that run 1024 Hz.

As far as determining RPM..... "I have a variable. RPM, which is, translated to frequency ranged from 20 Hz to 200 hz"

At 200 Hz it takes 5 whole milliseconds to turn once. For Arduino that can be a long time.
You will do better to count microseconds per rev than wait 1000 milliseconds and see how many revs were counted, which leaves a fraction of a rev not counted.

Don't use delay(). It will limit what your sketch can do.

Thanks GoForSmoke for the insights

I´ve come across the micros() but sincerely I still don´t get the hang too much to figure it out. Also I´ve looked about changing the arduino internal timers, but I think that with the 490 Hz original PWM clock for this particular application is enough, I don´t need to set the PWM high more than 1.8 ms.

Can you explain in toddler language the micros approach? If you have time (and knowledge) to spare with this humble servant?

Thanks

Fabius_rex:
The inyector pulse needs to be synced with the PMS

What is PMS?

So, I´m here, because I think that "Y" (the analog write function inside the ISR) can solve my "X" (will the Inyector be opened for each pulse of the CPU Fan, and in the amount of time (translated to PWM Duty cicle)

I can't help feeling that thinking is all wrong. and you are more likely confusing yourself than moving towards a solution.

The normal purpose of PWM is to control the power going to a motor or an LED - for example to make the LED glow dimly. In that usage changes to the duty cycle happen occasionally compared to the numbers of PWM cycles. The PWM system is not intended to control individual pulses.

...R

Sorry spanish language , PMS is when the piston reaches the top.

But that is the way the fuel injection works, applying a PWM puls to the injector as well as the ignition coil

That is the way it can control injector aperture as well as coil ignition timing, dwell, etc.

Do you have a better way to drive this circuit scenario? Please give, Im always open to a better way to make things

Let try to make it simpler.

Trying to control the speed of a CPU FAN using a PWM signal, and base the speed of the fan in a REICEIVER IR LED that is reading modulated pulses as well. If the IR input is HIGH 1ms your PWM Duty cycle will be for instance 40%. If the IR is high 2ms, the Dutycycle of the fan will be 80%. (just inventing figures)

So basically you will poll the IRsensor data, and map the value from the sensor to the desired PWM Dutycycle, right?

Now, think of it backwards. For each pulse amount of pulses per unit of time. For instance F= 1/T. I want to drive an IR led emitter "X" amount of time, and in between 2 pulses of the fan (1RPM).

How can I do that in a better way that of the one I´m trying to do and explain?

Here some drawings from the High Energy Ignition (H.E.I) module from the injection. This little man gives to the Engine Control Unit PWM pulses for the RPM reference.

With this information de E.C.U does 2 things

Each time the square wave from the R pin of the module goes HIGH, the Fuel inyector Pin goes HIGH.
But for how long? that is calculated using the data available from the car sensors (temperature, pressure, airflow, rpm, speed, etc)

And also the E.C.U sends BACK to the H.E.I module another PWM signal to PIN E to advance or retard the ignition spark, wich MAY or MAY NOT be synced with the PWM pulses coming from the RPM

But RPM here is the daddy of al the other square waves, or PWM pulses.

analogWrite() is ABSOLUTELY the WRONG way to do this.

analogWrite() uses the onboard hardware timers. You can tell it's hardware because it's limited to which pins it can use. There's more PWM pins than there are timers, so some timers control more than one pin. When you call analogWrite(3,127) it doesn't immediately start the square wave right at that moment. The timer might be halfway through its cycle on the other pin, or a quarter or anywhere.

But you can make the timers do what you want. Look at the Timer1 library for some ideas although ultimately you will have to get rid of that library and use your own register manipulations.

Also look for other "Arduino ECU" projects. It has been done successfully for small motors at relatively low RPM.

If you're expecting to run your V8 car on this then, no. Buy a proper ECU chip. Even better, buy the whole ECU so you don't have to build a box and wire up waterproof connectors and everything.

Fabius_rex:
Can you explain in toddler language the micros approach? If you have time (and knowledge) to spare with this humble servant?

Thanks

Start with a round clock with only the hour hand and the number 12 replaced by 0.
The clock goes 0 to 11 and back to zero, I can time up to 11 hours long with this clock.

The neat part is that the start hour can be any hour. if the start hour is 10 and now it is 7, the hand moved 9 hours you can see. If the hand starts at 7 and moves backwards 10 (the start time) then it will end up at 9, the number of hours between 7 and 10.

The numbers on the clock are all positive and roll-over at the top. The unsigned variables we use to time on Arduino are the same only bigger.

Arduino millis() and micros() functions return either of 2 clock values that start and run when the board does, they go around like hours and seconds but much smaller. Millis() is okay but not for short times when it matters.

Using millis() or micros(), always use unsigned integers and the same formula works up to how big the variable can count.

The way to do it is to get a start time (perhaps when a pin changed)

unsigned long startMicros, elapsedMicros = 1000;

....... down in the running the next line executes;
startMicros = micros(); // start time is recorded

........ and later on I want to do something IF a millisecond has passed or at least very close to then

if ( micros() - startMicros >= elapsedMicros )
{
// time is up, put the do something code here
}

If I collect a lot of times quickly in an array I can tell how long between any 2 by subtracting the earlier time from the later time, the round clock nature of unsigned variables makes it work -- it's a great way to compare time-stamped log lines for instance.

Ardiono millis() is good for most things but as it counts, it makes 0 to 249 fit in the low 8 bits of the 32-bit millis() return. The low 8 bits that can hold 256 values has 6 values that it skips. When you do the unsigned subtract with millis, you can be off by 1 so if it's important to right on time, use micros() instead.

Arduino micros() is not perfect but only that the smallest it tells is every 4 microseconds, not 1. I think that is pretty good, it is millisecond/250 with no skipped counts. You just use bigger numbers for the same time, micros() is good for over 71 minute intervals while millis() can count just over 49.7 days.

So you have 2 more tools now?

Fabius_rex:
Sorry spanish language , PMS is when the piston reaches the top.

If you see in an english language the letters TDC I think it is the same thing.
TDC is for Top Dead Center.

Now that we know what PMS is I think we can make progress.

I believe the injection must take place some time before PMS/TDC - in other words, some time after the previous PMS/TDC.

If so then I think your program needs to work like this pseudo code

a pulse is triggered at PMS/TDC
save the value of micros() when the pulse happens
calculate (or look up) the interval in microsecs after PMS/TDC when the injection must start
if (micros() - savedMicros >= interval)
  start the injection
  save micros() again
}
if (micros() - injectionStartMicros >= injectionDuration)
  end the injection
}

No sign of analogWrite() anywhere.

...R

Robin2:
Now that we know what PMS is I think we can make progress.

I believe the injection must take place some time before PMS/TDC - in other words, some time after the previous PMS/TDC.

If so then I think your program needs to work like this pseudo code

a pulse is triggered at PMS/TDC

save the value of micros() when the pulse happens
calculate (or look up) the interval in microsecs after PMS/TDC when the injection must start
if (micros() - savedMicros >= interval)
  start the injection
  save micros() again
}
if (micros() - injectionStartMicros >= injectionDuration)
  end the injection
}





No sign of analogWrite() anywhere.

...R

Nice, thanks before hand for your time and patience.

I got the big picture of the code, but will need to sit down and figure it out ok.

With this piece of code will I be able to make a HIGH pulse (for calculated time) then low, between Interrupt pulses?

Thanks

Sorry more or less figured out but one more question?

Can I put the interval based code in micros INSIDE the ISR? wouldn´t that be more or less a delay?

I just got this idea of setting a bool variable inside the ISR, something like

bool PMS_PULSE = TRUE;

So that in each pulse of RPM the ISR sets the flag to true, then in the loop part, I can ask

if PMS_PULSE = TRUE
while (micros() - savedMicros >= interval)
start the injection
save micros() again
}
if (micros() - injectionStartMicros >= injectionDuration)
end the injection
}
PMS_PULSE = false;
}

What do you guys think?