Go Down

Topic: Using pin change interrupts (PCINT) (Read 26670 times) previous topic - next topic

cappy2112


Has anyone here used pin change interrupts successfully?

I've read these links, which are pretty helpful.
http://www.arduino.cc/playground/Main/PinChangeInt
http://www.arduino.cc/playground/Main/PcInt

and sections 12-13 of the Atmel  data sheet for the ATMEGA328.

The reason I am considering using them are-

the Arduino in my project is monitoring 5 digital input lines from the hardware being tested.

These lines are fed into several OR gates on my Arduino breakout board. The output of the last OR gate eventually feeds into INT0 on the Arduino.

Additionally, each of the 5 signals are also fed into separate digital input pins on the Arduino, in order to know which pin caused the interrupt. By design, the hardware being monitored by the Arduino won't assert more than 1 signal at a time.

All of the inputs on the OR gates have been used. In order for the Arduino to monitor more signals, I would need
to use more OR gates.

As an alternative to using more OR gates, I thought I'd investigate PCINT.
Since all of the signals being monitored will have the same interrupt handler, I just need to know how to find out which
pin caused the interrupt. It looks like using the PCINT method, you can only define 3 ISRs.

In my current setup, my ISR disables interrupts, then reads PIND using direct port manipulation, for the fastest response.
The signals that the Arduino is monitoring generally last for 10-50ms, so it's unlikely they would be gone by the time the ISR is called.

Looking at the data sheet for the 328, I could not find a register which would tell me which signal caused the PCINT.
Is there one, or do I just need to read PINx (where x is C or D)?

I would expect the PCINT lines to be latched and able to be read later.


I'm trying to learn how to use Eagle so that I can draw a schematic.
If I had one finished, I would post it to make it easier to see what I'm doing.

Thanks

AWOL

Quote
The signals that the Arduino is monitoring generally last for 10-50ms, so it's unlikely they would be gone by the time the ISR is called.

So why are you even considering interrupts?
50ms is 800 000 instruction cycles.
"Pete, it's a fool looks for logic in the chambers of the human heart." Ulysses Everett McGill.
Do not send technical questions via personal messaging - they will be ignored.
I speak for myself, not Arduino.

Grumpy_Mike

Quote
Has anyone here used pin change interrupts successfully?

Yes, used them in this project:-
http://www.thebox.myzen.co.uk/Hardware/Crazy_People.html
Full code available.

ckiick

You don't automagically get told which pin caused the interrupt, but it's not hard to figure it out. What you do is save a copy of the pins before the interrupt and compare it to the pins during the interrupt, and note which ones have changed (masked by the interrupt mask for the port).

If you look at the PCint code closely, you will see how this is done in the ISR using the PCintLast[] variable.  It gets a little more complicated when you want to do FALLING or RISING instead of CHANGE.

In fact, you could just use the PCint code as is, and attach a function to each of the pins you are monitoring, which calls common code with the pin number.  Then you wouldn't have to re-invent anything.  (The other PC library would work as well, and probably be faster and take up less space.)

If you want to just modify your current solution, consider replacing the OR gates with a multiplexer. Then write your own pin change ISR that reads the relevant bits from the port and uses them as the pin number.

cappy2112

#4
Dec 01, 2011, 04:30 pm Last Edit: Dec 01, 2011, 04:37 pm by cappy2112 Reason: 1

You don't automagically get told which pin caused the interrupt, but it's not hard to figure it out. What you do is save a copy of the pins before the interrupt and compare it to the pins during the interrupt, and note which ones have changed (masked by the interrupt mask for the port).


THere must be some register that knows which pin caused the interrupt

Quote

If you look at the PCint code closely, you will see how this is done in the ISR using the PCintLast[] variable.  It gets a little more complicated when you want to do FALLING or RISING instead of CHANGE.

The ISR in my current OR-gate based project gets called on the RISING edge.

Quote

In fact, you could just use the PCint code as is, and attach a function to each of the pins you are monitoring, which calls common code with the pin number.  Then you wouldn't have to re-invent anything.  


The problem with the PCINT- you can only define 3 ISRs. If I need to monitor 6-9 signals, there are only 3 possible ISRs that will get called.

Quote

If you want to just modify your current solution, consider replacing the OR gates with a multiplexer.


I don't have the luxury of space. I've got room for a 74LS02 (4 OR gates??) and a 74LS04. If I need to monitor more signals, then
I need to ditch the OR gates, and add another LS04. The PCINT route effectively takes the place of the OR gates.

When a shield is mounted on an Arduino board, there's barely enough room for 2 sockets. I'm using wire wrap sockets,and the leads a quite long. There isn't much space between the shields & the components on the Arduino where the socket pins won't hit the components on the
Arduino board.

Quote

Then write your own pin change ISR that reads the relevant bits from the port and uses them as the pin number.


It looks like the only solution is to read the port input pins in the ISR, to determine which pin caused the interrupt.
I can do this with one Arduino instruction using direct port manipulation.

Thanks for the idea!!

ckiick

Quote
THere must be some register that knows which pin caused the interrupt


Nope.

Quote
The problem with the PCINT- you can only define 3 ISRs. If I need to monitor 6-9 signals, there are only 3 possible ISRs that will get called.


That's what the PCint code in the playground is for.  It figures out which pin has changed and calls the function you want for you.  Just paste the code in and call PCattachInterrupt(pin, func, RISING) for each pin you want to monitor, with the appropriate function.  Done.

Grumpy_Mike

Just in case you missed it. The change interrupts directs what interrupt service routing gets called. This is the start of the code that was in my project:-

Code: [Select]
/* Crazy People
* By Mike Cook April 2009
* Three RFID readers outputing 26 bit Wiegand code to pins:-
* Reader A (Head) Pins 4 & 5
* Reader B (Body) Pins 6 & 7
* Reader C (Legs) Pins 8 & 9
* Interrupt service routine gathers Wiegand pulses (zero or one) until 26 have been recieved
* Then a sting is sent to processing
*/
#include "pins_arduino.h"
/*
* an extension to the interrupt support for arduino.
* add pin change interrupts to the external interrupts, giving a way
* for users to have interrupts drive off of any pin.
* Refer to avr-gcc header files, arduino source and atmega datasheet.
*/

/*
* Theory: all IO pins on Atmega168 are covered by Pin Change Interrupts.
* The PCINT corresponding to the pin must be enabled and masked, and
* an ISR routine provided.  Since PCINTs are per port, not per pin, the ISR
* must use some logic to actually implement a per-pin interrupt service.
*/

/* Pin to interrupt map:
* D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2
* D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0
* A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1
*/

volatile uint8_t *port_to_pcmask[] = {
  &PCMSK0,
  &PCMSK1,
  &PCMSK2
};

typedef void (*voidFuncPtr)(void);

volatile static voidFuncPtr PCintFunc[24] = {
  NULL };

volatile static uint8_t PCintLast[3];

/*
* attach an interrupt to a specific pin using pin change interrupts.
* First version only supports CHANGE mode.
*/
void PCattachInterrupt(uint8_t pin, void (*userFunc)(void), int mode) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  uint8_t slot;
  volatile uint8_t *pcmask;

  if (mode != CHANGE) {
    return;
  }
  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  }
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }
  slot = port * 8 + (pin % 8);
  PCintFunc[slot] = userFunc;
  // set the mask
  *pcmask |= bit;
  // enable the interrupt
  PCICR |= 0x01 << port;
}

void PCdetachInterrupt(uint8_t pin) {
  uint8_t bit = digitalPinToBitMask(pin);
  uint8_t port = digitalPinToPort(pin);
  volatile uint8_t *pcmask;

  // map pin to PCIR register
  if (port == NOT_A_PORT) {
    return;
  }
  else {
    port -= 2;
    pcmask = port_to_pcmask[port];
  }

  // disable the mask.
  *pcmask &= ~bit;
  // if that's the last one, disable the interrupt.
  if (*pcmask == 0) {
    PCICR &= ~(0x01 << port);
  }
}

// common code for isr handler. "port" is the PCINT number.
// there isn't really a good way to back-map ports and masks to pins.
static void PCint(uint8_t port) {
  uint8_t bit;
  uint8_t curr;
  uint8_t mask;
  uint8_t pin;

  // get the pin states for the indicated port.
  curr = *portInputRegister(port+2);
  mask = curr ^ PCintLast[port];
  PCintLast[port] = curr;
  // mask is pins that have changed. screen out non pcint pins.
  if ((mask &= *port_to_pcmask[port]) == 0) {
    return;
  }
  // mask is pcint pins that have changed.
  for (uint8_t i=0; i < 8; i++) {
    bit = 0x01 << i;
    if (bit & mask) {
      pin = port * 8 + i;
      if (PCintFunc[pin] != NULL) {
        PCintFunc[pin]();
      }
    }
  }
}

SIGNAL(PCINT0_vect) {
  PCint(0);
}
SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) {
  PCint(2);
}

// End of interrupts code and start of the reader code

cappy2112


Just in case you missed it. The change interrupts directs what interrupt service routing gets called. This is the start of the code that was in my project:-


Thanks

It looks to me like PCINTS can only handle 3 new ISRs. If you setup 12 pins for PCints, they will still only call those 3 isrs.
I guess that's ok though. The previous post said the PCInt cod eon the playground figures out which pin was interrupted.

I need to go back and re-read that code again.
Hell, I gotta go to work tomorrow anyway. I may as well run the code to see how it works.


cappy2112


That's what the PCint code in the playground is for.  It figures out which pin has changed and calls the function you want for you.  Just paste the code in and call PCattachInterrupt(pin, func, RISING) for each pin you want to monitor, with the appropriate function.  Done.



Ok- thanks for calling that to my attention.
I'll paste it in and run it.

Thanks

Go Up