Go Down

Topic: use of interrupts for limited time  (Read 146 times) previous topic - next topic

chispas

Hello,

I am trying to read an anemometer for 3 seconds using an interrupt and then move ahead with other tasks (such as read a tachometer for another 3 seconds also with interrupt and then make calculations and some serial print).

The anemometer sends pulses to arduino. In order to calculate wind speed, the frequency of these pulses should be multiplied by a constant (named "slope_anemo") and add a constant (named "intersect_anemo").

I tried the folowing:

Code: [Select]
const byte interruptpin_anemo=3;
const float slope_anemo=0.04577;
const float intersect_anemo=0.267;
volatile unsigned long pulses_anemo;

void setup() {
pinMode(interruptpin_anemo,INPUT);

}

void loop() {
unsigned long Initial_time=millis();
unsigned long Present_time=0;
  pulses_anemo=0;
    while ((Present_time-Initial_time)<3000) {
 attachInterrupt(interruptpin_anemo,anemo_ISR,RISING); 
 Present_time=millis(); 
  }
 
float wind= (pulses_anemo/3000*slope_anemo+intersect_anemo);
  Serial.begin(57600);
  Serial.print("Wind (m/s):");
  Serial.println(wind);
  Serial.end();

}

void anemo_ISR(){
  pulses_anemo++;
detachInterrupt(interruptpin_anemo);
}


But the only thing I get from serial is "Wind (m/s): 0.27" which is almost the value of "intersect_anemo" constant, and I get this printed very fast, not each 3 seconds as I intended so it seems that the WHILE function is not working.

I confirmed circuit (a simple voltage divider actually) is ok using another anemometer reading program.

I would very much welcome your opinions. Thanks!

MorganS

This is a good application for using interrupts. But you are thinking about the interrupt in the wrong way. Think of the interrupt as "free". It doesn't stop your code. It should be invisible to the main code. Like it's not even there. So don't attach and detach the interrupt. That operation isn't free. That's relatively expensive on the Arduino.

Since the interrupt is free to use but printing the result isn't, you need to structure your millis() timing around the printing function. Let the measurement continue by itself. You don't need to start and stop measuring.

Now, let's look at why that millis() timer failed...
Quote
Code: [Select]
 unsigned long Initial_time = millis();
  unsigned long Present_time = 0;
  while ((Present_time - Initial_time) < 3000) {

You aren't thinking unsigned. You are thinking about the mathematics they taught you at school. Unsigned maths works in unexpected ways. Initial_time will be a large number. Like maybe 300 on the first time. Subtract that from zero and you think you get -300. But it's unsigned, innit? There is no negative. So you actually get a really really large number. In the billions or trillions. That is not less than 3000, so the while() never runs.

For a better explanation of how unsigned maths works in this context, see Nick Gammon's tutorial on millis(0 overflow

Here's my attempt to fix your code.

Code: (UNTESTED) [Select]
const byte interruptpin_anemo = 3;
const float slope_anemo = 0.04577;
const float intersect_anemo = 0.267;
volatile unsigned long pulses_anemo;

const unsigned long measurementPeriod = 3000; //milliseconds, only total the measurement and print it this often

void setup() {
  pinMode(interruptpin_anemo, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptpin_anemo), anemo_ISR, RISING);
  
  Serial.begin(57600);
  Serial.println("Anemometer measurer");
  Serial.print("Compiled on ");
  Serial.print(__DATE__);
  Serial.print(" at ");
  Serial.println(__TIME__);
  Serial.println("Starting...");
}

void loop() {
  static unsigned long Initial_time; //make it static so the value is stored between loop()s
  unsigned long Present_time = millis();

  if ((Present_time - Initial_time) > measurementPeriod) {
    noInterrupts(); //temporarily suspend interrupts while we make a clean copy-and-reset
    float currentPulsesAnemo = pulses_anemo;  //note the conversion to float format here
    pulses_anemo = 0;
    Initial_time = millis();
    interrupts(); //let the interrupt keep counting while we do our work

    float wind = (currentPulsesAnemo / measurementPeriod * slope_anemo + intersect_anemo);
    Serial.print("Wind (m/s):");
    Serial.println(wind);
  }


}

void anemo_ISR() {
  pulses_anemo++;
}


Notes:
0) Use digitalPinToInterrupt(). Your version may work on some older Arduinos, but you will have problems later if you don't use the correct function.

1) I created a new constant for the period, so we don't have magic "3000" floating around the code in various places.

2) The big calculation must be done in floating-point. If your code had 2000 pulses in 3000ms, 2000/3000 will give zero, since it's less than 1. So I converted to floating-point when I copied the volatile variable. Don't use float for counting. It's not a counting variable - it's a calculation variable. (Once again, the maths you learned in school is not used inside the Arduino.)

3) Initial_time is a poor name for that variable. I kept the name you had but I'd prefer to re-name it to something like "lastPrinted" or "lastAnemoMeasurementTime".

4) Somebody might scream that you can't use millis() while interrupts are disabled. Not true. But if you stupidly left interrupts disabled for a long period like a whole millisecond, then you would have problems.

5) You don't need to end() Serial. Just begin it once and don't end. Plus I added a little self-documentation at the top, so when you have different versions on different Arduinos, you can plug in the PC and see which version you have actually running.

6) Now the code is non-blocking so you can measure both devices at the same time. Win!
"The problem is in the code you didn't post."

chispas

Many thanks MorganS for your detailed analysis, alternative code and link to Nick Gammon´s tutorial. I am working in order to compare both solutions (2 interrupts simultaneously and 1 interrupt first and then the other as in my original design).

Also thanks for explaining the philosophy behind interrupts and your coment regarding millis() inside interrupts... it was surprising to learn they actually work and for spotting the reason why WHILE was not working..

I will report on my progress!

chispas

Here is my progress report: I sticked to the idea of using interrupts in a limited portion of the code mainly because I was needing precise timing for other purposes. After changing the time keeping variables from "unsigned long" to "long" the program runs like a charm !

MorganS

If you understood Nick's tutorial then you would understand that using signed variables is going to crash your Arduino.

Sometimes/Rarely it is more convenient to subtract two unsigned variables and check if the result is negative. Do the signed conversion as part of that check.
"The problem is in the code you didn't post."

blh64

Unsigned maths works in unexpected ways.
Uh, no. Unsigned math works exactly as expected.  Some people's expectations of unsigned math behavior is not correct.  That is  a learning issue for the person, not a math problem.

Go Up