How to create 3th interrupt

Untested Code !!!

#include <avr/interrupt.h>
#include <avr/io.h>


void setup() {
  pinMode(5, INPUT);
  Serial.begin(9600);

//pin change interrupt on digital pin 5
//this corresponds to PCINT2_vect
 PCICR |= (1 << PCIE2);
 PCMSK2 |= (1 << PCINT21);

}

volatile unsigned long counter = 0;
volatile bool print_flag = false;

ISR(PCINT2_vect)
{
      counter++;
        print_flag = true;
}

void loop()
{
      if(print_flag == true)
      {
            Serial.println(counter);
            print_flag = false;
      }

}

So this should increase the counter variable every-time the change interrupt fires (twice if we go from LOW to HIGH to LOW) if i understand this correctly?

Now how can i check fast if port 5 is HIGH or LOW, because i only want to increment the counter when the pin goes from LOW to HIGH?

I suppose DigitalRead in the ISR is not the way to go? Looking at the print_flag suggestion made by FusiveResonace, maybe I should also set a 2nd flag pinState = ???? inside the ISR, and check inside the loop if pinState is HIGH, only, what goes in place of the ???? ?

I know that's allot of questions, but I do wanna learn this stuff.

If anyone want's to know why I need this, I'll be happy to explain it.

Many greetings and thanks for the wonderful help so far.
EriSan500

PS: westfw, I corrected the comment :wink:

Read here for info on fast read of digital inputs...

You'll replace ???? with 'true'. Also note that this...
if (myValue == true)
when replaced by
if (myValue)
will evaluate as true as long as myValue is != 0.

I thought pointing out this optimization might help a little with the boolean logic concept in discussion here.

What I was looking for is this:

ISR(PCINT2_vect)
{
      counter++;
        print_flag = true;
        pinState = ???? //faster alternative for digitalread(5)
}

So I think what you are saying

You'll replace ???? with 'true'.

is not correct because pinState will allways be TRUE.

EriSan500

Sorry, 'true' that wasn't meant to be a direct injection into your code.

Rather...

ISR(PCINT2_vect)
{
    if (PIND & B00100000) // evaluates to TRUE when PortD bit #5 (Arduino digital pin #5) is HIGH
    {
        counter++;
        print_flag = true;
    }
}

Thanks Mitch_CA,

that was exactly what i was looking for.

To check for LOW instead of HIGH, I do this right? (or is there a leaner way?)

    if (PIND & B00100000 == LOW) // evaluates to TRUE when PortD bit #5 (Arduino digital pin #5) is LOW
    {
        counter++;
        print_flag = true;
    }
if (!(PIND & B00100000))...

Ok, thanks to everyone who replied, i got the below code working:

#include <avr/interrupt.h>
#include <avr/io.h>

void setup() {
  pinMode(5, INPUT);
  Serial.begin(9600);

  //pin change interrupt on digital pin 5
  //this corresponds to PCINT2_vect
  PCICR |= (1 << PCIE2);
  PCMSK2 |= (1 << PCINT21);

}

volatile unsigned long counter1 = 0;

ISR(PCINT2_vect)
{
  if (PIND & B00100000) // evaluates to TRUE when PortD bit #5 (Arduino digital pin #5) is HIGH
  {
    counter1++;
  }
}

void loop()
{
  Serial.println(counter1);
  delay(1000);
}

Now, up to implement the other 2 interrupts. :wink:

Interrupt service routines need to be as short as possible, but you don't need to make them obnoxiously painful. Using digitialRead inside an ISR should be fine, unless your code is time critical for other reasons (for example, SoftwareSerial expects to be able to make a good effort at running LOTS of pins that do stuff at every bit time at rather high bit rates, so I'd expect it to use raw IO. If you're going to add a couple of pin-change interrupts that handle relatively low datarates, you can use whatever makes more sense from a program readability point of view...

You need to disable interrupts when accessing counter1 outside of the interrupt service routine...

void loop()
{
  unsigned long c;
  
  {
    uint8_t SaveSREG = SREG;
    cli();
    c = counter1;
    SREG = SaveSREG;
  }
  
  Serial.println(c);
  delay(1000);
}

@Coding Badly

Why bother saving SREG and then restoring it? Why not just use the matching sei() call with cli()

Secondly, isn't retrieving the value of the counter an indivisible call? It would matter if he were retrieving the value, modifying it, and then writing back, but in this case he is simply retrieving the value. I do not understand why that must portion of code must be atomic.

Why bother saving SREG and then restoring it? Why not just use the matching sei() call with cli()

There are times when sei() is not safe to use. The pattern I presented is always safe to use with a very minimal cost (I believe one more machine instruction). Basically, there's less to remember and understand so mistakes are less likely.

Secondly, isn't retrieving the value of the counter an indivisible call?

No. Anything larger than a byte is not atomic. (That's not entirely true. A few 16 bit operations are atomic. But less to remember + low cost = good so we'll always use the code I presented, right? :D)

It would matter if he were retrieving the value, modifying it, and then writing back, but in this case he is simply retrieving the value.

It is not possible for an 8 bit processor to perform any operation atomicly on a 32 bit value. Just reading the value requires four machine instructions; an interrupt can occur between any of them.

I do not understand why that must portion of code must be atomic.

Does the explanation above help or do I need to provide more details?

OK guys, it seems this is getting pretty complex (at least for me) :-?.

Let me explain a bit what I'm trying to do:

I need to count pulses from 3 sensors:

  • Counter1: idle = low, pulse = high, max 1 pulse every +/- 10 secs, and every hour this pulse stays high for about 60 secs
  • Counter2: idle = low, pulse = high, max 3 pulses in 1 sec
  • Counter3: idle = high, pulse = low, max 2 pulses in 1 sec, pulse can stay low for several hours

In my current setup I just count the pulses in the loop(), push the counter values over serial to my PC every 30 secs and reset them to 0, but people told me that i will miss pulses this way. So I'm looking for a more robust/safer way to count the pulses.

Seen that there are only 2 attachInterrupts, I was looking to or add a 3th interrupt, or don't use attachInterrupt and create the 3 interrupts manually.

I'm getting great help and feedback from this wonderful community but as mentioned at the beginning of this post, it seems to become complex. Now I'm asking myself: Is this complexity needed for what I'm trying to accomplish?

In the near future, I will be replacing the serial/usb connection to my pc with a wireless connection (RFM12B).
I also have 1-Wire devices connected to this arduino, but if you guys suggest me that it would make more sense to move this to another arduino, i will do so.

Thats about what i have at the moment, but lets focus for the moment on the counter part as that is the most important one for me.

Thanks in advance,

EriSan500

Why not use interrupts for the two fastest pulse sources and monitor the slowest in loop, this will work if the pulse duration is always longer than the pulse duration.
What is the duration of the pulses and what else is your application doing in loop?

Mem,

Pulse duration:

  • Counter1: on average 1 sec, with once an hour about 60secs
  • Counter2: 90ms iirc (need to check the datasheet at home)
  • Counter3: variable. This is a pulse contact on a water meter.

For the rest my loop checks if 30 secs are passed, and if so, send the counters to the serial and reset them to 0 again.
I also read a bunch of 1-wire temp sensors (mix of DS18S20 and DS18B20) every 60 secs, and push those readings straight to serial.

Greetings,
EriSan500

Counter 1 looks do-able from within loop. You can monitor the duration of your loop with something like this.

unsigned long startTime = 0;
unsigned long minDuration = 999999;

void loop()
{
  startTime = millis();

  // your loop code here

  unsigned long dur = millis() - startTime;
  if(dur < minDuration) 
  {
    minDuration = dur;
    Serial.print("Minimum loop duration is ");
    Serial.println(dur);    
  }  
}

If the minimum loop duration is well under 1000 (1 second) then you should be ok handling counter 1 in loop

@Coding Badly

Great explanations. Can you please elaborate more on the following or provide some reading material. The AVR libc documentation on sei is rather brief.

There are times when sei() is not safe to use.

I too need an explanation. I found this in interrupt.h...

# define sei()  __asm__ __volatile__ ("sei" ::)

I don't understand how this could be unsafe.

Unfortunately, my replies have turned into a distraction. I'd like to continue the ISR discussion here...

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261124850/0#0