PinChange interrupt?

Started by olivio, February 21, 2014, 01:57:15 PM

olivio

I tried to use PinChangeInt library (https://code.google.com/p/arduino-pinchangeint/) but it doesn't work. Has someone working example of pinchange int?

uChip

Nick Gammon has posted a great write up and tutorial about Arduino interrupts including pinchange on his site.  Here is the URL.

http://gammon.com.au/interrupts

Good luck,
Chip

olivio

His example didin't work for Moteino. Which interrupt should I hood with attachInterrupt when I need pinchange interrupt from pins 3-6 ?

Felix

D3 is a hardware interrupt so you can use that to wake up the atmega. The others are not. So you can probably use the interrupt library to "attach" to those pins. There's no reason why it should not work except for conflicts with the RFM library which does disable hardware interrupts while servicing the radio interrupts.

olivio

It doesn't work on pin 4 either... Any ideas what could be done?

Felix

I think you'll need to provide a lot more detail than "it doesn't work".

olivio

Well.. I do not get any interrupts for pin change using examples of Nick Gammon for pin 4 (or any other pin either). Could someone else try it?

BIA

It doesn't work.

For your additional info, Felix, no interrupts are being raised on PORTC, PORTD or PORTB. Either that or the processor is halting when this happens. When an attempt is made to raise a pin change interrupt, such as with a quad encoder, the processor resets itself. This is not related to a wireless library because it happens when there is no wireless library included in the code. If you want to test it yourself, just use the code from here: http://playground.arduino.cc/Main/PinChangeIntExample.

Felix

It does work, otherwise the transceivers would not work ;)
There are 2 external interrupts on the mega328, D2 and D3. D2 is taken by the radio, if used. D3 is free.

If the MCU resets that sounds like a dip below the 2.7V brownout. That should never happen, except if you're starving the regulator on the Moteino, which is ONLY intended to power the Moteino and not much else (though it can provide a total of 250mA). Or a short or high current inrush somewhere in your circuit which destabilizes the Moteino. You may want to check your power supply to make sure it's stable and keep that regulator happy.

Also mind the fact that external interrupts are not the same as PCINTerrupts which are available on almost all the pins but cannot raise individual software interrupts. We are talking about external interrupts here, and there are only 2 on the mega328 like I said.

BIA

I understand interrupts extremely well, Felix. I'm not talking about external interrupts. I am referring to PIN CHANGE interrupts that the OP indicated was not working for him. I have no idea why external interrupts would work on D2 & D3 but not on PORTD. Obviously, something is happening as the MCU resets itself when a pin change interrupt is triggered using the pinchangeint library.

Have you tried the pin change interrupt example? If it's working for you then I must have a bad batch of Moteinos. My power supply is fine.

Felix

I am sure you understand the interrupts. However what confused me is when you said
QuoteI have no idea why external interrupts would work on D2 & D3 but not on PORTD.
D2 and D3 are indeed on PD2 and PD3.

QuoteIf it's working for you then I must have a bad batch of Moteinos.

I will say that one Moteino having a bad pin is an extremely low possibility. Add D3 as a constraint, and add more Moteinos with the exact same problem and you are nearing negative infinity. Really. They are loaded with the fuses/lockbits/bootloader and tested for RF functionality before shipping. The radio working is proof external interrupts are working.

I wrote this sketch for you to verify the D3 interrupt. Touch a jump wire from 3.3V to D3 and every time you do, you should see the LED blink faster:

boolean interrupt = false;
void blink()
{
  interrupt = true;
}

void setup()
{
  pinMode(LED, OUTPUT);
  attachInterrupt(3,blink,RISING); //external interrupt on D3 of mega328
  Serial.begin(115200);
}

void loop()
{
  if (interrupt)
  {
    Serial.print("X");
    for (byte i=0; i<10; i++)
    {
      digitalWrite(LED, HIGH);
      delay(25);
      digitalWrite(LED, LOW);
      delay(25);
    }
    interrupt = false;
  }
  
  digitalWrite(LED, HIGH);
  delay(500);
  digitalWrite(LED, LOW);
  delay(500);
}

BIA

#11
Felix, I think we must have a major disconnect in communication. To be clear: external interrupts (such as they are) are working. There is no issue with external interrupts.

The issue is with pin change interrupts. Of course I understand D2 & D3 are PORTD pins. I am not concerned with 2 & 3. I am concerned with pins that are not 2 and 3.

Your example uses an external interrupt. Of course they work, although your code does not. To make your code work, line 1 must be changed to volatile boolean interrupt = false; and line 10 to attachInterrupt(1,blink,RISING); There is no interrupt 3 on the Moteino that I'm aware of and it doesn't work with the pin number like Due.

With those changes, external interrupts do work. As you pointed out, your wireless would not work otherwise.

However, if you include #include <PinChangeInt.h> in your code and change line 10 to PCintPort::attachInterrupt(4, blink, RISING); (or any other pin) it does not work. If you raise an external interrupt pin (2 or 3) in this program, I believe you will find that the MCU does indeed reset. The halt is being caused by external interrupts while the library is in use.

In all fairness, when I read the fine print of the wiki for the PinChangeInterrupt library, the author says it is only supported on the Arduino Uno and Duemilanove, although he surmises that it "should" work on any ATmega168 or ATmega328-based processor. I can't speak for the other 31 flavors, but I can state unequivocally it does not work on Moteino.

If the OP is still around and concerned about pin change interrupts, I cobbled together this library from some other code that also did not work on the Moteino. This works, at least on mine:

// Pin change interrupt library that works for Moteino.

#include "Arduino.h"
#include "pins_arduino.h"
/* 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
};

static int PCintMode[24];

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.
 */
 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;

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

  if (port == 1) {
     slot = port * 8 + (pin - 14);
  }
  else {
     slot = port * 8 + (pin % 8);
  }
//
  PCintMode[slot] = mode;
  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);
  }
}

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;
      // Trigger interrupt if mode is CHANGE, or if mode is RISING and
      // the bit is currently high, or if mode is FALLING and bit is low.
      if ((PCintMode[pin] == CHANGE
          || ((PCintMode[pin] == RISING) && (curr & bit))
          || ((PCintMode[pin] == FALLING) && !(curr & bit)))
          && (PCintFunc[pin] != NULL)) {
        PCintFunc[pin]();
      }
    }
  }
}

SIGNAL(PCINT0_vect) {
  PCint(0);
}

SIGNAL(PCINT1_vect) {
  PCint(1);
}
SIGNAL(PCINT2_vect) { // for PIND (pins 0-7)
  PCint(2);
}


Just save the code in your libraries as PCInterrupt.h (or whatever you want to call it) and include it in your code. Here is a sample program that uses the library:

// Sample code for pin change interrupt on Moteino. Raising the pin defined by PIN1 increments a counter, raising the pin defined by PIN2 decrements a counter.

#include <PCInterrupt.h>

#define PIN1  4 // pin for the first interrupt (can be any pin except 0 - 3)
#define PIN2  5 // pin for the second interrupt (can be any pin except 0 - 3)

volatile int ticktocks = 0;

void setup() {
  Serial.begin(115200);
  pinMode(PIN1, INPUT); // pin mode must be set to INPUT or the interrupt will not read
  pinMode(PIN2, INPUT);
  PCattachInterrupt(PIN1, tick, CHANGE); // attach the interrupt (CHANGE, RISING, FALLING, etc.) There is also a PCDetachInterrupt function: PCDetachInterrupt(PIN)
  PCattachInterrupt(Pin2, tock, CHANGE);
  delay(2000);
  Serial.println("Ready.");
}

int priorTicktocks = 0;

void loop() {
  if (ticktocks != priorTicktocks) {
    Serial.println(ticktocks);
    priorTicktocks = ticktocks;
  }
}

void tick(void) { // interrupt on PIN1
  ticktocks++;
}

void tock(void) { // interrupt on PIN2
  ticktocks--;
}

Felix

Yeah that felt like a disconnect. I have not used PCINTs myself much at all. I am more concerned with external interrupts since my devices make use of those. I can't speak much for pin change interrupts, but I know you have to look at a register to determine which pin actually caused the interrupt after it happens, as opposed to the external interrupts which have direct routines attached to them and it's obvious. I think your library is attempting to do just that but I have not tried it.
There is no reason why the chips would not work as advertised by Atmel. I do not modify the chips in any way that should affect the functions of those PCINT pins. In such cases it's typically the code that is bad not the chip hoping this doesn't sound defensive.

BIA

As we all know, not everything that works on a chip works on all implementations of that chip. It appears the PinChangeInt library is very limited in its portability.

However, pin change interrupts are even more critical on the Moteino than other implementations since it is operating with one hand tied behind its back, so to speak, with one of only two external interrupts dedicated to wireless. That chip is woefully short in interrupts to begin with.

Hopefully, Felix, you will get your implementation of the ATmega1284P working. From what I can tell, it has 3 external interrupts?? Not much, but at least it puts the platform back on level footing (interrupt-wise) with other 328P-based platforms. BTW, Felix, I read your post on your progress with the ATmega1284P. There is an issue with interrupts on that chip (or with Arduino libraries, depending on which you want to blame) that causes software serial not to work correctly. The issues you are having may (or not) be related to this. The 2560P would be a much better chip for a mega IMO.

I just put that other library together in haste, yesterday. At this point I can only say that it will detect interrupts on any pin > 3, and is surprisingly fast, however, I can't say how accurate it is. I plan to try it with an encoder and see how well it works. If I find an issue, I'll post it.

Felix

Thanks for the follow up.
On the 1284P - I chose this chip not knowing much about it other than it has a wealth more features and IO than the 328, and just started playing with it once I got my Moteino MEGA prototype PCBs. Yes, just 3 interrupts unfortunately, but another hardware UART which comes in very handy, and bunch more memory in all areas. I assumed the interrupts and 3.3V 16mhz "overclocking" would work the same as the 328. Unfortunately that is not the case. Unless I provide 5V the chip will stall at 16mhz, something I've never seen on the 328, not even once. The external interrupts from the RFM69 radio are also not working, but do work on the RFM12B.
The mega2560 is double in price and more massive to integrate in such a small board (there's the arduino mega for such purposes).
It's sad that atmel doesn't offer a larger 8bit chip that runs at at least 16mhz at 3.3V. They have other lines of chips, the problem is they are not ported to the Arduino environment. People want cheap, quick easy to use, a difficult combination to meet.