How to use port manipulation interrupts?

Hi all

Im looking for a way to count the number of interrupts from a hall effect sensor. I have got it all working with the attatchInterrupt method, but would like to use ports to optimize the code, this is because my application is very time sensitive. I have managed to use port manipulation to turn on some LEDs so i understand how to map digital arduino pins to their respective ports, but cannot work out how to setup an interrupt.

Im using the Arduino Mega 2560 and ideally need to setup 5 interrupts on this method, is this possible? And some advice? Say if i want to setup an interrupt with a mask on Digital Pin 3 on the Arduino, how do i go about doing this?

Here is what i have so far:

setup
attachInterrupt(0, FL, FALLING);

loop
if (millis() - lastmillis > 10){
detachInterrupt(0);
x = count * 60;
FL=0;
attachInterrupt(0, FL, FALLING);
}

void FL(){
  count++;
}

That doesn't compile

Sorry i wrote it short hand

int lastmillis;
int x;
volatile int count;

void setup(){
  attachInterrupt(0, FL, FALLING);

}

void loop(){
  if (millis() - lastmillis > 10){
    detachInterrupt(0);
    x = count * 60;
    count=0;
    attachInterrupt(0, FL, FALLING);
  }
}
void FL(){
  count++;
}

Apart from making count an unsigned byte (and if not unsigned byte then unsigned int) there is no way to speed up the interrupts there is no such thing as "port manipulation" for interrupts. And I don't believe that your program is that time critical that you can't use digitalRead/Write thats just a typical newbie misconception.

Mark

You really shouldn't be detaching the handler everytime, you'll lose events that way.

If you want to read a multibyte variable that's being written by an ISR just go:

  noInterrupts () ;
  int my_count = count ;
  count = 0 ;
  interrupts () ;

So that you don't get it garbled and don't lose interrupts.

Basically the program will be running realtime inside a vehicle, ive made it abstract in the code ive posted. The information gathered from the interrupts connect to a circtuit which switch MOSFETs on/off depending on a simple algorithm denoted by

x = count * 60

Originally I used digitalWrite to turn on LEDs to visually represent the manipulation of the interrupts. This loop ran every 100ms, but reducing that to 50ms caused the LEDs to flicker, im assuming this may of been because I was turning on and off 4 LEDs using digitalWrite. Once i changed this to use ports, i managed to recduce the loop to 10ms and have no flicker at all.

The interrupts are the last thing that i have not optimised, but even if they may not gain me much time as the ISR is very basic, I would like to understand how to do it.

Reading various sources i have found lines like this:

PCICR = _BV(PCIE0); //Enable pin change interrupts
PCMSK0 = 1 // - I believe this is required to work out from which pin on a certain port the interrupt came from
ISR(INT1_vect)
{ // starts the ISR
}

Can something like the above be written to do what i need?

Thanks Mark that will save me some lines too.

You attach the interrupt in setup() so that has no affect on program performance. Bytes take one clock cycle ints two as a rule. Using volatile the compiler should deal with any problems with int being read while it could be changed by the ISR. It does not matter how you set up the ISR with does not affect the speed of the ISR.

When an interrupt occurs the hardware looks at something known as the interrupt vectors ( an array of addresses one for each interrupt). The hardware saves the address the program was at (the return address) when the interrupt took place and the jumps to the address found from the interrupt vector table. When the ISR is completed you return to the address stored by the the hardware, as if nothing had happened. Thing of the interrupt as happening between instruction (machine code instructions).

The hardware turns off all interrupts when it starts to deal with an interrupt and turns them on again when you return from the ISR. Other interrupts which occur while your dealing with on interrupts are stored and dealt with once the current ISR is completed. They are not lost (limit one of each type)

This loop ran every 100ms, but reducing that to 50ms caused the LEDs to flicker, im assuming this may of been because I was turning on and off 4 LEDs using digitalWrite. Once i changed this to use ports, i managed to recduce the loop to 10ms and have no flicker at all.

The above had nothing to do with the speed of digitalWrite() vers DPM. There's just not enough difference for the "human eye"
to see. At aboult 10 frames a second your "eye" (it really the brain) goes from blink to continues (eg - blink to fade) You get very smooth animation at 12 frames per second films use 24 per second.

Would need to see code to work out just what was really happening.

Mark

I’m writing a library to measure counters with pin change interrupts. Perhaps it will help you: http://forum.arduino.cc/index.php?topic=218661.0

elementjj:

PCICR = _BV(PCIE0); //Enable pin change interrupts

PCMSK0 = 1 // - I believe this is required to work out from which pin on a certain port the interrupt came from
ISR(INT1_vect)
{ // starts the ISR
}

Here's how I read that code: The first line enables pin change interrupts on pins PCINT0:7. The second line masks all but PCINT0. By my reading of the schematic and the pin mapping, that will give you a pin change interrupt on digital pin 53 only. Whenever the processor sees a change on pin 53, it'll throw a PCINT0 interrupt, and vector to the memory location associated with that interrupt.

Here the code will fail, though. You've created an ISR for INT0, an external interrupt, on pin 21 as I read it. There's no ISR for PCINT0, so the processor will go to some location defined for bad interrupt vectors, and reset. I think you want to establish the ISR with this:ISR(PCINT0_vect) {

Reading between the lines, it looks like you aren't entirelyup to speed on the difference between INT7:0 and PCINT23:0. You might want to read the about "External Interrupts" in the datasheet, and satisfy yourself that you know how these interrupts work, and that you know the difference between between them. You can find a link to the datasheet here: http://arduino.cc/en/Main/ArduinoBoardMega2560#.UwKt7fmQxCI. Then, you might want to do some experiments with simple projects to satisfy yourself that you know how to get the results you want, before trying to integrate these interrupts into an already complicated project.

If you're looking for processing speed, and you don't need the pins for something else, you might want to use INT7:0 for this purpose. You'll have a separate ISR for each pin, so you won't need to determine which pin changed. You'll also be able to select the edge that triggers the interrupt, so you won't have to resolve that, either. With pin change interrupts, you'll have to keep track of the last state of the active pins, and compare it with their new state in order to determine which pin(s) changed.

Ok thanks for the replies, i am progressing a bit, i wrote up this quickly, but still not sure about some areas which i have commented:

//////////////////////////////////////////////////////////////
//ATMEL  PORT                            Arduino Pin        //
//23	PB4 ( OC2A/PCINT4 )		Digital pin 10 (PWM)//
//24	PB5 ( OC1A/PCINT5 )		Digital pin 11 (PWM)//
//25	PB6 ( OC1B/PCINT6 )		Digital pin 12 (PWM)//
//26	PB7 ( OC0A/OC1C/PCINT7 )	Digital pin 13 (PWM)//
//////////////////////////////////////////////////////////////

int x;
int lastmillis;
volatile int count;

void setup() { 
  PCICR = _BV(PCIE0); //Enable interrupts from PCINT0 to PCINT7
  PCMSK0 = ...; //How to set this value to mask Pins in table above???
}
void loop() {
 if (millis() - lastmillis > 30){
  cli(); // switch interrupts off while messing with their settings
  x = count*60;
  count =0;
  lastmillis=0;
  sei(); // turn interrupts back on
 }
}

ISR(PCINT4_vect) { //ISR for PCINT4 digital pin 10
  if (PINB & ... )//some value here to only continue when there is an interrupt on pin 10
  {
    count++;
  }
}

Read the datasheet. The link can be found on this page: http://arduino.cc/en/Main/ArduinoBoardMega2560#.UwN71fmQxCI. Understand the pin change interrupts in chapter 15, "external Interrupts.". Do some experiments. Post some code that compiles and does something. Tell us what it does, and what you expected it to do.

I have read the datasheet, but i am not fully understanding it, also i dont know what the syntax is for things like PCMSK0 = ????

Here is my code, all it is doing is detecting an interrupt on Digital Pin 10 from a hall effect sensor. And then outputs the int count every 1 second. So far it does nothing, which tells me that the interrupt is still not set correctly.

int x;
int lastmillis;
volatile int count;

void setup() { 
  Serial.begin(9600);
  PCICR = _BV(PCIE0); //Enable interrupts from PCINT0 to PCINT7
  PCMSK0 = 1;
}
void loop() {
  if (millis() - lastmillis == 1000){
    Serial.println(count);
    count =0;
    lastmillis=millis();
  }
}

ISR(PCINT4_vect) { //ISR for PCINT4 digital pin 10
  count++;
}

Should this...

ISR(PCINT4_vect) { //ISR for PCINT4 digital pin 10

be..

ISR(PCINT0_vect) { //ISR for PCINT4 digital pin 10

and then you need to check PCMSK0 to see if the interrupt occurred on PCINT4?

This line  PCICR = _BV(PCIE0); //Enable interrupts from PCINT0 to PCINT7enables the pin change 0 interrupt. In order for a pin change interrupt to occur, the appropriate bit(s) in register PCMSK0 have to be set as well.

You want to enable a pin change interrupt on pin 10. From the pin mapping shown here - http://arduino.cc/en/Hacking/PinMapping2560#.UwO36vmQwnM - pin 10 corresponds to PCINT4. First, we check which group PCINT4 falls into, and that's the first group, controlled by PCMSK0. Looking at the register description for PCMSK0, the mask for PCINT4 is bit 4. So, to enable only the interrupt on Pin 10:

PCICR = _BV(PCIE0);
PCMSK0 = _BV(PCINT4);

Other pins might fall into other groups. For example, PCINT10, digital pin 14, falls into the second group, controlled by PCIE1 in PCICR; and PCINT19, on analog pin 11, falls into the third group, controlled by PCIE2.

This ISR will be associated with all eight pin change interrupts controlled by PCIE0, and those are PCINT0 through PCINT7. To get the name of the ISR, we look at the list of interrupt vectors in chapter 14, "Interrupts," of the datasheet. That shows PCINT0 as the name of Pin Change Interrupt Request 0. From my experience, the way to derive the interrupt vector is this: Replace every spaces appearing in the name shown in the interrupt table with an underscore: "_"; then add "_vect" at the end, and that's what goes into the ISR declaration statement. So, for PCINT0, with no spaces, that becomes PCINT0_vect, and the ISR starts withISR(PCINT0_vect) {

Ok that is great, nearly there, the following code allows the interrupt to work, but of course both Digital Pin 10 & 11 cause an interrupts as they are on the same Interrupt Port. How can i differentiate between the 2 pins and call their respective ISR?

int x;
int lastmillis;
volatile int count;
volatile int count1;

void setup() { 
  Serial.begin(9600);
  PCICR = _BV(PCIE0); //Enable interrupts from PCINT0 to PCINT7
  PCMSK0 = _BV(PCINT4) | _BV(PCINT5); //this allows interrupts on pin 10 & 11
}
void loop() {
  if (millis() - lastmillis == 1000){
    Serial.println(count);
    count =0;
    lastmillis=millis();
  }
}

ISR(PCINT0_vect) {
 //check its pin 10
  count++;
}

ISR(PCINT0_vect) {
 //check its pin 11
  count1++;
}

That code doesn't compile. The IDE objects to a duplicate ISR definition.

You can't use the same ISR twice. You can have only one PCINT0 ISR. It responds to a change on either pin. It's the sketch's job to identify the changes.

elementjj:
How can i differentiate between the 2 pins and call their respective ISR?

Answer:

tmd3:
With pin change interrupts, you'll have to keep track of the last state of the active pins, and compare it with their new state in order to determine which pin(s) changed.

  • Read the pins
  • Store their current state
  • When the interrupt happens, compare the previous value to the new value.
  • Determine which pins changed, and how they changed
  • Do something with that information
  • Update the current state of the pins

It was pseudo code, hence the comments. I will continue to try to work out how to do all those steps.

If both pins are from the PCINT0 register, what is the syntax to associate the ISR with a certain pin? This is why i used PCINT4_vect for the ISR on Digital Pin 10 (PCINT4) first time round.

Also i have seen code like this which differentiates interrupts from the same port, i think it uses masks to achieve but not sure how this works:

ISR
 if(PIND & 1)
// do this
ISR
if(PIND & 16)
//do this

Maybe to reduce overhead i should just use seperate ports. But what will be the side effect to all the unused pins on each port i assign as an interrupt?

The ooPinChangeInt library I linked to above does exactly what you are trying to do, including providing a separate interrupt routine for each pin.
ooPinChangeInt is for use from other libraries.
https://code.google.com/p/oopinchangeint/downloads/detail?name=oopinchangeint-v1.03beta.zip&can=2&q=
PinChangeInt is for use from your sketch
https://code.google.com/p/arduino-pinchangeint/wiki/Licensing

turgo:
The ooPinChangeInt library I linked to above does exactly what you are trying to do, including providing a separate interrupt routine for each pin.
ooPinChangeInt is for use from other libraries.
https://code.google.com/p/oopinchangeint/downloads/detail?name=oopinchangeint-v1.03beta.zip&can=2&q=
PinChangeInt is for use from your sketch
https://code.google.com/p/arduino-pinchangeint/wiki/Licensing

Thanks, i found some nice guide that explains how to use this in simple terms, and it works, i just need to know the command to detach the interrupts while i process them int he loop, is this possible?

Edit
Also i seem to only get it to register interrupts on pin 10,11,12,13. I need one more pin but tried 1-9 and didnt work, is there some limitation here?

Also after a small time, the serial port monitor starts to print wildly, rather than after each second, heres the code:

#include <PinChangeInt.h>
#define FRONT 11
#define REAR 12

int x;
int lastmillis=0;
volatile int count1;
volatile int count2;


void setup() { 
  Serial.begin(9600); 
  PCintPort::attachInterrupt(FRONT, Freq1,RISING); 
  PCintPort::attachInterrupt(REAR, Freq2,RISING); 
}

void loop() {
  if (millis() - lastmillis > 1000){

    x = count2*60;
    count2 =0;

    Serial.println(x);
    Serial.println(count1);
    count1 =0;
    lastmillis=millis();

  }
}

void Freq1(){
  count1++;
}

void Freq2(){
  count2++;
}

// On the Mega2560, the available Pin Change Interrupt (PCINT) pins are:
// Arduino Pin [Port] AVR Pin Name
// Digital Pin 10 [Port B] PB4 (OC2A/PCINT4)
// Digital Pin 11 [Port B] PB5 (OC1A/PCINT5)
// Digital Pin 12 [Port B] PB6 (OC1B/PCINT6)
// Digital Pin 13 [Port B] PB7 (OC0A/OC1C/PCINT7)
// Analog Pins A8 - A15 [Port K]

You may also get pins 50-53 to work.

try ‘detachInterrupt(uint8_t pin);’

see the example sketch attached below:

PinChangeIntExampleMega2560.ino (3.58 KB)